Something went wrong. Try again.
The open source OpenXR runtime
Something went wrong. Try again.
6.6 kB · 211 lines
Python
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212#!/usr/bin/env python3# Copyright 2026, Beyley Cardellio# SPDX-License-Identifier: BSL-1.0
"""Export versioned OpenVR headers from git tags."""
from __future__ import annotations
import argparseimport jsonfrom pathlib import Pathimport reimport subprocessimport sys
TARGETS = ( ("headers/openvr.h", "openvr_{version}.h"), ("headers/openvr_api.json", "openvr_api_{version}.json"), ("headers/openvr_capi.h", "openvr_capi_{version}.h"),)JSON_OUTPUT_PATTERN = "openvr_api_{version}.json"HEADER_OUTPUT_PATTERN = "openvr_{version}.h"
def run_git(repo_root: Path, args: list[str], check: bool = True) -> subprocess.CompletedProcess[bytes]: return subprocess.run( ["git", *args], cwd=repo_root, check=check, stdout=subprocess.PIPE, stderr=subprocess.PIPE, )
def normalize_version(tag: str) -> str: version = re.sub(r"^v[._-]*", "", tag.strip(), flags=re.IGNORECASE) if not version: raise ValueError(f"Cannot derive a version from tag '{tag}'") return f"v{version}"
def version_sort_key(version: str) -> tuple[tuple[int | str, ...], str]: normalized = version[1:] if version.startswith("v") else version parts = re.findall(r"\d+|[A-Za-z]+", normalized) key = tuple(int(part) if part.isdigit() else part.lower() for part in parts) return key, normalized.lower()
def collect_tags(repo_root: Path) -> list[tuple[str, str]]: result = run_git(repo_root, ["tag", "--list", "--sort=version:refname"]) selected_tags: dict[str, str] = {}
for raw_tag in result.stdout.decode("utf-8").splitlines(): tag = raw_tag.strip() if not tag: continue selected_tags[normalize_version(tag)] = tag
return [(version, tag) for version, tag in selected_tags.items()]
def read_tagged_file(repo_root: Path, tag: str, repo_path: str) -> bytes | None: result = run_git(repo_root, ["show", f"{tag}:{repo_path}"], check=False) if result.returncode != 0: return None return result.stdout
def export_tag(repo_root: Path, version: str, tag: str, output_dir: Path) -> bool: exported_any = False
for repo_path, output_pattern in TARGETS: content = read_tagged_file(repo_root, tag, repo_path) if content is None: print(f"Skipping missing {repo_path} at tag {tag}", file=sys.stderr) continue
output_path = output_dir / output_pattern.format(version=version) output_path.write_bytes(content) exported_any = True
return exported_any
def load_interface_versions(json_path: Path) -> dict[str, str] | None: try: payload = json.loads(json_path.read_text(encoding="utf-8")) except (OSError, json.JSONDecodeError): return None
class_names = { method.get("classname", "").split("::")[-1] for method in payload.get("methods", []) if method.get("classname") } if not class_names: return None
const_versions = { const.get("constname")[:-len("_Version")]: const.get("constval") for const in payload.get("consts", []) if const.get("constname", "").endswith("_Version") and const.get("constval") }
interface_versions = { class_name: const_versions[class_name] for class_name in sorted(class_names) if class_name in const_versions } if len(interface_versions) != len(class_names): return None
return interface_versions
def deduplicate_output_dir(output_dir: Path, exported_versions: list[str]) -> int: signatures: dict[tuple[tuple[str, str], ...], str] = {} deleted_versions: list[str] = []
for version in sorted(exported_versions, key=version_sort_key): json_path = output_dir / JSON_OUTPUT_PATTERN.format(version=version) if not json_path.exists(): continue
interface_versions = load_interface_versions(json_path) if interface_versions is None: continue
signature = tuple(sorted(interface_versions.items())) previous_version = signatures.get(signature) signatures[signature] = version if previous_version is None: continue
for path in ( output_dir / HEADER_OUTPUT_PATTERN.format(version=previous_version), output_dir / JSON_OUTPUT_PATTERN.format(version=previous_version), ): try: path.unlink() except FileNotFoundError: pass
deleted_versions.append(previous_version) print( f"Removed duplicate interface version set from {previous_version}; kept {version}", file=sys.stderr, )
return len(deleted_versions)
def main() -> int: parser = argparse.ArgumentParser( description="Export versioned openvr.h and openvr_api.json files from all git tags." ) parser.add_argument( "repo_dir", type=Path, help="Path to the OpenVR git repository.", ) parser.add_argument( "-o", "--output-dir", type=Path, default=None, help="Directory to write exported files into (default: <repo_dir>/headers/versioned)", ) parser.add_argument( "--limit", type=int, default=None, help="Only export the first N normalized versions after sorting, for testing.", ) args = parser.parse_args()
repo_root = args.repo_dir.resolve() if not (repo_root / ".git").exists(): parser.error(f"{repo_root} does not look like a git repository")
output_dir = ( args.output_dir.resolve() if args.output_dir is not None else repo_root / "headers" / "versioned" ) output_dir.mkdir(parents=True, exist_ok=True)
tags = collect_tags(repo_root) if args.limit is not None: tags = tags[: args.limit]
exported_versions = 0 written_versions: list[str] = [] for version, tag in tags: if export_tag(repo_root, version, tag, output_dir): exported_versions += 1 written_versions.append(version) print(f"Exported {version} from {tag}") else: print(f"Skipped {tag}: neither target file exists", file=sys.stderr)
removed_duplicates = deduplicate_output_dir(output_dir, written_versions) print(f"Wrote files for {exported_versions} version(s) into {output_dir}") if removed_duplicates: print(f"Removed {removed_duplicates} older duplicate version set(s)") return 0
if __name__ == "__main__": raise SystemExit(main())