#!/usr/bin/env python3 """Update the authoritative launcher version and its project metadata mirror.""" from __future__ import annotations import argparse import os from pathlib import Path import re import sys PROJECT_ROOT = Path(__file__).resolve().parents[1] if str(PROJECT_ROOT) not in sys.path: sys.path.insert(0, str(PROJECT_ROOT)) from scripts.release_lib import ReleaseToolError, load_config # noqa: E402 from ptlinux.update_resolver import SemanticVersion SOURCE_PATTERN = re.compile( r'(?m)^__version__ = "[0-9]+\.[0-9]+\.[0-9]+(?:-(?:beta|rc)\.[0-9]+)?"$' ) PROJECT_PATTERN = re.compile( r'(?m)^version = "[0-9]+\.[0-9]+\.[0-9]+(?:-(?:beta|rc)\.[0-9]+)?"$' ) def replace_once(path: Path, pattern: re.Pattern[str], replacement: str) -> None: try: original = path.read_text(encoding="utf-8") except (OSError, UnicodeError) as exc: raise ReleaseToolError(f"cannot read version file {path}: {exc}") from exc updated, count = pattern.subn(replacement, original) if count != 1: raise ReleaseToolError( f"expected exactly one version declaration in {path}, found {count}" ) if updated == original: return temporary = path.with_name(f".{path.name}.{os.getpid()}.tmp") try: with temporary.open("x", encoding="utf-8", newline="\n") as handle: handle.write(updated) handle.flush() os.fsync(handle.fileno()) temporary.chmod(path.stat().st_mode & 0o777) os.replace(temporary, path) finally: temporary.unlink(missing_ok=True) def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("version") arguments = parser.parse_args() try: SemanticVersion.parse(arguments.version) config = load_config() version_source = config.get("version_source") if not isinstance(version_source, str): raise ReleaseToolError("version_source must be configured") replace_once( PROJECT_ROOT / version_source, SOURCE_PATTERN, f'__version__ = "{arguments.version}"', ) mirrors = config.get("version_mirrors") if not isinstance(mirrors, list) or not all(isinstance(item, str) for item in mirrors): raise ReleaseToolError("version_mirrors must be configured") for mirror in mirrors: replace_once( PROJECT_ROOT / mirror, PROJECT_PATTERN, f'version = "{arguments.version}"', ) except (OSError, ValueError, ReleaseToolError) as exc: print(f"Version update failed: {exc}", file=sys.stderr) return 1 return 0 if __name__ == "__main__": raise SystemExit(main())