131 lines
4.4 KiB
Python
131 lines
4.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Fail when backend, manifest, frontend, lock, or release-ref versions drift."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
from dataclasses import asdict
|
|
import json
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
|
|
META_ROOT = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(META_ROOT / "tools" / "release"))
|
|
|
|
from govoplan_release.version_alignment import ( # noqa: E402
|
|
release_composition_issues,
|
|
repository_version_issues,
|
|
selected_repository_version_issues,
|
|
)
|
|
from govoplan_release.workspace import ( # noqa: E402
|
|
load_repository_specs,
|
|
resolve_repo_path,
|
|
resolve_workspace_root,
|
|
)
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--workspace-root", type=Path, default=None)
|
|
parser.add_argument("--repo", action="append", default=[], help="Repository name to check; may be repeated.")
|
|
parser.add_argument(
|
|
"--repo-version",
|
|
action="append",
|
|
default=[],
|
|
metavar="REPO=VERSION",
|
|
help="Require a repository's aligned source metadata to equal VERSION; may be repeated.",
|
|
)
|
|
parser.add_argument(
|
|
"--release-composition",
|
|
action="store_true",
|
|
help="Also compare tagged backend and WebUI references in the release composition.",
|
|
)
|
|
parser.add_argument(
|
|
"--source-metadata-only",
|
|
action="store_true",
|
|
help="Skip lockfile checks for the pre-commit source gate; never use for publication.",
|
|
)
|
|
parser.add_argument("--json", action="store_true", help="Print machine-readable output.")
|
|
args = parser.parse_args()
|
|
|
|
workspace_root = resolve_workspace_root(args.workspace_root)
|
|
expected_versions = _parse_repo_versions(parser, args.repo_version)
|
|
selected = {*args.repo, *expected_versions}
|
|
known = {spec.name for spec in load_repository_specs()}
|
|
unknown = sorted(selected - known)
|
|
if unknown:
|
|
parser.error(f"unknown repository name(s): {', '.join(unknown)}")
|
|
|
|
checked: list[str] = []
|
|
issues = list(
|
|
selected_repository_version_issues(
|
|
repo_versions=expected_versions,
|
|
workspace=workspace_root,
|
|
include_lockfiles=not args.source_metadata_only,
|
|
)
|
|
)
|
|
for spec in load_repository_specs():
|
|
if selected and spec.name not in selected:
|
|
continue
|
|
repo_path = resolve_repo_path(spec, workspace_root)
|
|
if not repo_path.exists():
|
|
continue
|
|
checked.append(spec.name)
|
|
if spec.name in expected_versions:
|
|
continue
|
|
issues.extend(
|
|
repository_version_issues(
|
|
repo_path,
|
|
include_lockfiles=not args.source_metadata_only,
|
|
)
|
|
)
|
|
|
|
if args.release_composition:
|
|
issues.extend(
|
|
release_composition_issues(
|
|
META_ROOT,
|
|
core_root=workspace_root / "govoplan-core",
|
|
)
|
|
)
|
|
|
|
payload = {
|
|
"workspace_root": str(workspace_root),
|
|
"repositories_checked": checked,
|
|
"release_composition_checked": args.release_composition,
|
|
"issues": [asdict(issue) for issue in issues],
|
|
}
|
|
if args.json:
|
|
print(json.dumps(payload, indent=2, sort_keys=True))
|
|
elif issues:
|
|
print("Version alignment failed:", file=sys.stderr)
|
|
for issue in issues:
|
|
print(
|
|
f"- {issue.repo}: {issue.source}: {issue.actual!r}; expected {issue.expected!r} "
|
|
f"({issue.message})",
|
|
file=sys.stderr,
|
|
)
|
|
else:
|
|
suffix = " including release composition" if args.release_composition else ""
|
|
print(f"Version alignment passed for {len(checked)} repositories{suffix}.")
|
|
return 1 if issues else 0
|
|
|
|
|
|
def _parse_repo_versions(parser: argparse.ArgumentParser, values: list[str]) -> dict[str, str]:
|
|
result: dict[str, str] = {}
|
|
for value in values:
|
|
if "=" not in value:
|
|
parser.error(f"--repo-version must use REPO=VERSION: {value}")
|
|
repo, version = (item.strip() for item in value.split("=", 1))
|
|
version = version.removeprefix("v")
|
|
if not repo or not version:
|
|
parser.error(f"--repo-version must use REPO=VERSION: {value}")
|
|
if repo in result and result[repo] != version:
|
|
parser.error(f"conflicting versions requested for {repo}")
|
|
result[repo] = version
|
|
return result
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|