283 lines
11 KiB
Python
Executable File
283 lines
11 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
import re
|
|
import subprocess
|
|
from pathlib import Path
|
|
from datetime import date, datetime
|
|
|
|
# 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"
|
|
PYPROJECT = BASE_DIR / "pyproject.toml"
|
|
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"
|
|
|
|
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_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_app_py(path: Path, old: str, new: str) -> bool:
|
|
"""
|
|
Update __app_version__ 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:
|
|
"""
|
|
Update [Unreleased] to [new] - YYYY-MM-DD in CHANGELOG.md
|
|
"""
|
|
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')
|
|
new_text, count = pattern.subn(f"## [{new}] - {current_date}", text)
|
|
if count:
|
|
path.write_text(new_text, encoding='utf-8')
|
|
return bool(count)
|
|
|
|
def bump_debian_changelog(path: Path, old: str, new: str) -> bool:
|
|
"""
|
|
Update debian/changelog with new version
|
|
"""
|
|
if not path.exists():
|
|
return False
|
|
|
|
# Extract changelog entries from CHANGELOG.md for this version
|
|
changelog_md_path = BASE_DIR / "CHANGELOG.md"
|
|
changelog_entries = []
|
|
changelog_date = None
|
|
|
|
if changelog_md_path.exists():
|
|
changelog_text = changelog_md_path.read_text(encoding='utf-8')
|
|
lines = changelog_text.splitlines()
|
|
|
|
# Find the section for the new version and extract the date
|
|
start_reading = False
|
|
end_reading = False
|
|
in_contributors_section = False
|
|
|
|
for line in lines:
|
|
if line.startswith(f"## [{new}]"):
|
|
# Extract date from line like "## [0.1.9] - 2025-12-08"
|
|
date_match = re.search(r'\[.+\] - (\d{4}-\d{2}-\d{2})', line)
|
|
if date_match:
|
|
changelog_date_str = date_match.group(1)
|
|
# Convert to the expected Debian format
|
|
date_obj = datetime.strptime(changelog_date_str, '%Y-%m-%d')
|
|
changelog_date = date_obj.strftime('%a, %d %b %Y') + " 00:00:00 +0000"
|
|
|
|
start_reading = True
|
|
in_contributors_section = False
|
|
continue
|
|
elif line.startswith("## [") and start_reading:
|
|
end_reading = True
|
|
break
|
|
elif line.strip().lower() == "### contributors":
|
|
# Start of contributors section - skip following lines until next section
|
|
in_contributors_section = True
|
|
continue
|
|
|
|
# Skip section headers and contributor sections
|
|
if start_reading and not end_reading and not in_contributors_section:
|
|
stripped_line = line.strip()
|
|
if stripped_line and not line.startswith("#") and not line.startswith("[") and not line.lower().startswith("###"):
|
|
# Check if this line is a list item with changes
|
|
if re.match(r'^\s*[*-]\s+', line):
|
|
# Remove markdown list formatting and add proper Debian format
|
|
clean_line = re.sub(r'^\s*[*-]\s+', ' * ', line.rstrip())
|
|
# Remove common markdown formatting like backticks
|
|
clean_line = re.sub(r'`([^`]+)`', r'"\1"', clean_line) # Replace `code` with "code"
|
|
changelog_entries.append(clean_line)
|
|
# Also include lines that are sub-items (indented changes)
|
|
elif line.startswith(" ") and re.match(r'^\s*[*-]\s+', line[4:]):
|
|
clean_line = re.sub(r'^\s*[*-]\s+', ' * ', line[4:].rstrip())
|
|
clean_line = " " + clean_line # Add extra indentation
|
|
# Remove common markdown formatting
|
|
clean_line = re.sub(r'`([^`]+)`', r'"\1"', clean_line)
|
|
changelog_entries.append(clean_line)
|
|
|
|
# If no specific entries found for this version, use generic message
|
|
if not changelog_entries:
|
|
changelog_entries = [" * New upstream release"]
|
|
|
|
# Use changelog date if available, otherwise use current time
|
|
current_time = changelog_date if changelog_date else datetime.now().strftime('%a, %d %b %Y %H:%M:%S +0000')
|
|
|
|
# Read the existing changelog to get maintainer info and other fields
|
|
text = path.read_text(encoding='utf-8')
|
|
|
|
# If the file is empty or doesn't contain proper maintainer info, use a default
|
|
lines = text.splitlines()
|
|
|
|
if not lines or not any(line.startswith(" -- ") for line in lines):
|
|
# Create a default changelog entry with proper format
|
|
package_name = "portprotonqt"
|
|
new_version_line = f"{package_name} ({new}-1) unstable; urgency=medium"
|
|
|
|
# Default maintainer info from the original file
|
|
default_maintainer = "Boris Yumankulov <boria138@altlinux.org>"
|
|
maintainer_line = f" -- {default_maintainer} {current_time}"
|
|
|
|
new_content = new_version_line + "\n\n" + "\n".join(changelog_entries) + "\n\n" + maintainer_line + "\n"
|
|
else:
|
|
# Extract the header template from the current first entry
|
|
header_parts = []
|
|
entry_end_index = 0
|
|
|
|
for i, line in enumerate(lines):
|
|
header_parts.append(line)
|
|
if line.startswith(" -- "):
|
|
entry_end_index = i + 1
|
|
break
|
|
|
|
# Construct new changelog entry
|
|
new_entry_lines = []
|
|
|
|
if header_parts:
|
|
# Parse the first line to extract package name (before the version)
|
|
first_line = header_parts[0]
|
|
# Extract package name by getting everything before the opening parenthesis
|
|
if '(' in first_line:
|
|
package_name = first_line.split('(')[0].strip()
|
|
new_version_line = f"{package_name} ({new}-1) unstable; urgency=medium"
|
|
else:
|
|
# Fallback: if no parentheses found, use a default format
|
|
new_version_line = f"portprotonqt ({new}-1) unstable; urgency=medium"
|
|
|
|
new_entry_lines.append(new_version_line)
|
|
|
|
# Add the changelog entries
|
|
new_entry_lines.extend(changelog_entries)
|
|
|
|
# Add the maintainer info and timestamp
|
|
for j in range(1, len(header_parts)):
|
|
if header_parts[j].startswith(" -- "):
|
|
# Extract the maintainer information (everything after "-- ")
|
|
maintainer_part = header_parts[j][4:] # Remove leading " -- "
|
|
# Extract only the name and email, ignore timestamp
|
|
maintainer_info = maintainer_part.split(' ')[0].strip()
|
|
new_entry_lines.append(f" -- {maintainer_info} {current_time}")
|
|
elif not header_parts[j].startswith(" *"): # Skip existing changes since we added new ones
|
|
new_entry_lines.append(header_parts[j])
|
|
|
|
# Reconstruct the file with new entry at the top followed by the rest
|
|
new_content = '\n'.join(new_entry_lines) + '\n' + '\n'.join(lines[entry_end_index:])
|
|
|
|
path.write_text(new_content, encoding='utf-8')
|
|
return True
|
|
|
|
|
|
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),
|
|
(PYPROJECT, bump_pyproject),
|
|
(APP_PY, bump_app_py),
|
|
(GITEA_WORKFLOW, bump_workflow),
|
|
(CHANGELOG, bump_changelog),
|
|
(DEBIAN_CHANGELOG, bump_debian_changelog)
|
|
]
|
|
|
|
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()
|