Files
2026-07-21 14:08:18 +05:00

248 lines
8.4 KiB
Python
Executable File

#!/usr/bin/env python3
import argparse
import re
import subprocess
from pathlib import Path
from datetime import date
# Base directory of the project
BASE_DIR = Path(__file__).parent.parent
# Specific project files
APPIMAGE_RECIPE = BASE_DIR / "build-aux" / "AppImageBuilder.yml"
ARCH_PKGBUILD = BASE_DIR / "build-aux" / "PKGBUILD"
FEDORA_SPEC = BASE_DIR / "build-aux" / "fedora.spec"
FEDORA_GIT_SPEC = BASE_DIR / "build-aux" / "fedora-git.spec"
PYPROJECT = BASE_DIR / "pyproject.toml"
MESON_BUILD = BASE_DIR / "meson.build"
APP_PY = BASE_DIR / "portprotonqt" / "app.py"
GITEA_WORKFLOW = BASE_DIR / ".gitea" / "workflows" / "build.yml"
CHANGELOG = BASE_DIR / "CHANGELOG.md"
DEBIAN_CHANGELOG = BASE_DIR / "debian" / "changelog"
METAINFO = BASE_DIR / "build-aux" / "share" / "metainfo" / "ru.linux_gaming.PortProtonQt.metainfo.xml"
def bump_appimage(path: Path, old: str, new: str) -> bool:
"""
Update only the 'version' field under app_info in AppImageBuilder.yml
"""
if not path.exists():
return False
text = path.read_text(encoding='utf-8')
pattern = re.compile(r"(?m)^(\s*version:\s*)" + re.escape(old) + r"$")
new_text, count = pattern.subn(lambda m: m.group(1) + new, text)
if count:
path.write_text(new_text, encoding='utf-8')
return bool(count)
def bump_arch(path: Path, old: str, new: str) -> bool:
"""
Update pkgver in PKGBUILD
"""
if not path.exists():
return False
text = path.read_text(encoding='utf-8')
pattern = re.compile(r"(?m)^(pkgver=)" + re.escape(old) + r"$")
new_text, count = pattern.subn(lambda m: m.group(1) + new, text)
if count:
path.write_text(new_text, encoding='utf-8')
return bool(count)
def bump_debian(path: Path, old: str, new: str) -> bool:
"""
Prepend a new entry to debian/changelog
"""
if not path.exists():
return False
text = path.read_text(encoding='utf-8')
# Maintainer info from the original entry
maintainer = "Ivan Mazhukin <vanyamajukin@outlook.com>"
# Debian date format: Mon, 23 May 2026 13:32:57 +0300
# Using subprocess to get the exact format via 'date -R'
try:
timestamp = subprocess.check_output(["date", "-R"]).decode().strip()
except (subprocess.CalledProcessError, FileNotFoundError):
timestamp = date.today().strftime('%a, %d %b %Y %H:%M:%S +0000')
new_entry = (
f"portprotonqt ({new}) unstable; urgency=medium\n\n"
f" * New upstream release.\n\n"
f" -- {maintainer} {timestamp}\n\n"
)
path.write_text(new_entry + text, encoding='utf-8')
return True
def bump_fedora(path: Path, old: str, new: str) -> bool:
"""
Update only the '%global pypi_version' line in fedora.spec
"""
if not path.exists():
return False
text = path.read_text(encoding='utf-8')
pattern = re.compile(r"(?m)^(%global\s+pypi_version\s+)" + re.escape(old) + r"$")
new_text, count = pattern.subn(lambda m: m.group(1) + new, text)
if count:
path.write_text(new_text, encoding='utf-8')
return bool(count)
def bump_pyproject(path: Path, old: str, new: str) -> bool:
"""
Update version in pyproject.toml under [project]
"""
if not path.exists():
return False
text = path.read_text(encoding='utf-8')
pattern = re.compile(r"(?m)^(version\s*=\s*)\"" + re.escape(old) + r"\"$")
new_text, count = pattern.subn(lambda m: m.group(1) + f'"{new}"', text)
if count:
path.write_text(new_text, encoding='utf-8')
return bool(count)
def bump_meson(path: Path, old: str, new: str) -> bool:
"""
Update version in meson.build
"""
if not path.exists():
return False
text = path.read_text(encoding='utf-8')
pattern = re.compile(r"(version:\s*)'" + re.escape(old) + r"'")
new_text, count = pattern.subn(lambda m: m.group(1) + f"'{new}'", text)
if count:
path.write_text(new_text, encoding='utf-8')
return bool(count)
def bump_app_py(path: Path, old: str, new: str) -> bool:
"""
Update APP_VERSION fallback in app.py
"""
if not path.exists():
return False
text = path.read_text(encoding='utf-8')
pattern = re.compile(r"(?m)^(\s*APP_VERSION\s*=\s*)\"" + re.escape(old) + r"\"$")
new_text, count = pattern.subn(lambda m: m.group(1) + f'"{new}"', text)
if count:
path.write_text(new_text, encoding='utf-8')
return bool(count)
def bump_workflow(path: Path, old: str, new: str) -> bool:
"""
Update VERSION in Gitea Actions workflow
"""
if not path.exists():
return False
text = path.read_text(encoding='utf-8')
pattern = re.compile(r"(?m)^(\s*VERSION:\s*)" + re.escape(old) + r"$")
new_text, count = pattern.subn(lambda m: m.group(1) + new, text)
if count:
path.write_text(new_text, encoding='utf-8')
return bool(count)
def bump_changelog(path: Path, old: str, new: str) -> bool:
"""
Create a new [Unreleased] section and release the current one
"""
if not path.exists():
return False
text = path.read_text(encoding='utf-8')
pattern = re.compile(r"(?m)^##\s*\[Unreleased\]$")
current_date = date.today().strftime('%Y-%m-%d')
release_header = (
"## [Unreleased]\n\n"
"### Added\n\n"
"### Changed\n\n"
"### Fixed\n\n"
"### Contributors\n\n"
"---\n\n"
f"## [{new}] - {current_date}"
)
new_text, count = pattern.subn(release_header, text)
if count:
path.write_text(new_text, encoding='utf-8')
return bool(count)
def bump_metainfo(path: Path, old: str, new: str) -> bool:
"""
Update releases in metainfo:
1. Change old version's <description></description> to <description/>
2. Prepend new version with <description></description>
"""
if not path.exists():
return False
text = path.read_text(encoding='utf-8')
# 1. Update the old version entry to use self-closing description
# Match tag starting with <release version="OLD" and ending with >
old_pattern = re.compile(
r'(<release version="' + re.escape(old) + r'"[^>]*>)\s*<description></description>\s*(</release>)',
re.DOTALL
)
current_date = date.today().strftime('%Y-%m-%d')
type_attr = ' type="development"' if new.startswith('0.') else ''
new_entry = (
f' <release version="{new}" date="{current_date}"{type_attr}>\n'
f' <description></description>\n'
f' </release>'
)
# Replace old entry's description with self-closing
new_text, count = old_pattern.subn(r'\1\n <description/>\n \2', text, count=1)
if count:
# 2. Insert the new entry at the beginning of the <releases> tag
new_text = new_text.replace('<releases>', f'<releases>\n{new_entry}')
path.write_text(new_text, encoding='utf-8')
return True
# Fallback if old version wasn't found with expected pattern
if f'version="{new}"' not in text:
new_text = text.replace('<releases>', f'<releases>\n{new_entry}')
path.write_text(new_text, encoding='utf-8')
return True
return False
def main():
parser = argparse.ArgumentParser(description='Bump project version in specific files')
parser.add_argument('old', help='Old version string')
parser.add_argument('new', help='New version string')
args = parser.parse_args()
old, new = args.old, args.new
tasks = [
(APPIMAGE_RECIPE, bump_appimage),
(ARCH_PKGBUILD, bump_arch),
(FEDORA_SPEC, bump_fedora),
(FEDORA_GIT_SPEC, bump_fedora),
(PYPROJECT, bump_pyproject),
(MESON_BUILD, bump_meson),
(APP_PY, bump_app_py),
(GITEA_WORKFLOW, bump_workflow),
(CHANGELOG, bump_changelog),
(DEBIAN_CHANGELOG, bump_debian),
(METAINFO, bump_metainfo)
]
updated = []
for path, func in tasks:
if func(path, old, new):
updated.append(path.relative_to(BASE_DIR))
if updated:
print(f"Updated version from {old} to {new} in {len(updated)} files:")
for p in sorted(updated):
print(f" - {p}")
try:
subprocess.run(["uv", "lock"], check=True)
print("Regenerated uv.lock")
except subprocess.CalledProcessError as e:
print(f"Failed to regenerate uv.lock: {e}")
else:
print(f"No occurrences of version {old} found in specified files.")
if __name__ == '__main__':
main()