#!/usr/bin/env python3 """ Update the DUMPER_VERSION placeholder in cpp_python_debug/core/gdb_dumper.py. Usage: python tools/update_dumper_version.py [--version X.Y.Z] [--commit] Behavior: - If --version is provided, that version will be used. - Otherwise the script will try, in order: 1) read cpp_python_debug/_version.py for __version__ 2) run `git describe --tags --always` in the repository root (searching upwards) - The script replaces the first occurrence of DUMPER_VERSION = "..." in `cpp_python_debug/core/gdb_dumper.py` with the chosen version string. - If --commit is passed and the repo is a git repo, it will stage and commit the updated file with a message. Note: This script does not assume the dumper will be executed in the repo; it writes the version directly into the dumper file so that copies of the file still carry a concrete version string. """ import argparse import os import re import subprocess import sys ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) DUMPER_PATH = os.path.join(ROOT, "cpp_python_debug", "core", "gdb_dumper.py") VERSION_FILE = os.path.join(ROOT, "cpp_python_debug", "_version.py") def read_version_from_file(): if not os.path.exists(VERSION_FILE): return None content = open(VERSION_FILE, "r", encoding="utf-8").read() m = re.search(r"^__version__\s*=\s*['\"]([^'\"]+)['\"]", content, re.M) return m.group(1) if m else None def git_describe_from(path): # Walk upwards looking for a .git directory cur = os.path.abspath(path) for _ in range(50): if os.path.isdir(os.path.join(cur, ".git")): try: out = subprocess.check_output(["git", "-C", cur, "describe", "--tags", "--always"], stderr=subprocess.DEVNULL) return out.decode().strip() except Exception: return None parent = os.path.dirname(cur) if parent == cur: break cur = parent # As a last resort, try running git describe in the provided path try: out = subprocess.check_output(["git", "describe", "--tags", "--always"], cwd=path, stderr=subprocess.DEVNULL) return out.decode().strip() except Exception: return None def replace_dumper_version(new_version: str, commit: bool = False): if not os.path.exists(DUMPER_PATH): print(f"ERROR: dumper file not found at {DUMPER_PATH}") return 2 txt = open(DUMPER_PATH, "r", encoding="utf-8").read() # Replace the first occurrence of DUMPER_VERSION = "..." new_txt, count = re.subn(r"DUMPER_VERSION\s*=\s*\"[^\"]*\"", f'DUMPER_VERSION = "{new_version}"', txt, count=1) if count == 0: print("WARNING: placeholder not found; inserting at top of file") # Prepend definition after imports block heuristically new_txt = txt.replace("from typing import Optional, Dict, Any, List, Tuple\n", "from typing import Optional, Dict, Any, List, Tuple\n\n# DUMPER_VERSION injected by tools/update_dumper_version.py\nDUMPER_VERSION = \"{0}\"\n".format(new_version), 1) if new_txt == txt: print("No changes needed (version already set).") return 0 open(DUMPER_PATH, "w", encoding="utf-8").write(new_txt) print(f"Updated {DUMPER_PATH} -> DUMPER_VERSION = {new_version}") if commit: try: subprocess.check_call(["git", "add", DUMPER_PATH], cwd=ROOT) subprocess.check_call(["git", "commit", "-m", f"chore: update dumper version to {new_version}"], cwd=ROOT) print("Committed change to git.") except Exception as e: print(f"Failed to commit: {e}") return 3 return 0 def main(): ap = argparse.ArgumentParser() ap.add_argument("--version", help="explicit version to write") ap.add_argument("--commit", action="store_true", help="stage and commit the change") args = ap.parse_args() version = args.version if not version: version = read_version_from_file() if not version: version = git_describe_from(ROOT) if not version: print("Could not determine version from _version.py or git; please pass --version") return 10 return replace_dumper_version(version, commit=args.commit) if __name__ == "__main__": sys.exit(main())