xworkmate-app/scripts/ci/build_version.py
2026-04-14 13:48:04 +08:00

109 lines
3.0 KiB
Python

#!/usr/bin/env python3
"""Resolve platform-safe build versions from pubspec.yaml.
This repo keeps prerelease semantics in the pubspec version, for example:
1.0.0-beta.2+4
Packaging metadata across Apple/Linux/Windows must use the numeric release
portion only, while display-facing surfaces can keep the prerelease label.
"""
from __future__ import annotations
import argparse
import json
import re
import shlex
import sys
from pathlib import Path
VERSION_PATTERN = re.compile(r"^version:\s*([^\n]+)", re.MULTILINE)
RELEASE_PATTERN = re.compile(r"^\d+\.\d+\.\d+$")
def parse_pubspec_version(pubspec_path: Path) -> dict[str, str]:
text = pubspec_path.read_text(encoding="utf-8")
match = VERSION_PATTERN.search(text)
if match is None:
raise ValueError(f"Unable to find version in {pubspec_path}")
raw_version = match.group(1).strip()
if not raw_version:
raise ValueError(f"Version in {pubspec_path} is empty")
if "+" in raw_version:
display_version, build_number = raw_version.split("+", 1)
else:
display_version, build_number = raw_version, "1"
display_version = display_version.strip()
build_number = build_number.strip()
platform_release_version = display_version.split("-", 1)[0].strip()
if not RELEASE_PATTERN.fullmatch(platform_release_version):
raise ValueError(
"Expected pubspec version to expose a three-part numeric release "
f"prefix before prerelease/build metadata, got: {raw_version}"
)
if not build_number.isdigit():
raise ValueError(f"Expected numeric build number in pubspec version, got: {raw_version}")
return {
"raw_version": raw_version,
"display_version": display_version,
"platform_release_version": platform_release_version,
"build_number": build_number,
}
def emit_shell(values: dict[str, str]) -> None:
for key, value in values.items():
env_key = key.upper()
print(f"{env_key}={shlex.quote(value)}")
def emit_json(values: dict[str, str]) -> None:
print(json.dumps(values))
def emit_github_output(values: dict[str, str]) -> None:
for key, value in values.items():
print(f"{key}={value}")
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
"--pubspec",
default="pubspec.yaml",
help="Path to pubspec.yaml",
)
parser.add_argument(
"--format",
choices=("shell", "json", "github-output"),
default="json",
help="Output format",
)
args = parser.parse_args()
values = parse_pubspec_version(Path(args.pubspec))
if args.format == "shell":
emit_shell(values)
elif args.format == "github-output":
emit_github_output(values)
else:
emit_json(values)
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except Exception as exc: # pragma: no cover - CLI failure path
print(str(exc), file=sys.stderr)
raise SystemExit(1)