universalisos/tools/version.py

112 lines
3.3 KiB
Python
Executable file

#!/usr/bin/env python3
"""Version management for UniversalisOS.
Reads VERSION file and provides version strings for builds, tags, and releases.
Can be called from Makefiles or release scripts.
Usage:
python tools/version.py # full version (1.0.0-alpha.1)
python tools/version.py --major # major only (1)
python tools/version.py --minor # minor only (0)
python tools/version.py --patch # patch only (0)
python tools/version.py --kernel armv7 qemu-arm-virt
# arch.platform suffix
"""
import argparse
import re
import sys
from pathlib import Path
VERSION_FILE = Path(__file__).resolve().parent.parent / "VERSION"
SEMVER_RE = re.compile(
r"^(?P<major>0|[1-9]\d*)"
r"\.(?P<minor>0|[1-9]\d*)"
r"\.(?P<patch>0|[1-9]\d*)"
r"(?:-(?P<pre>[0-9A-Za-z.-]+))?"
r"(?:\+(?P<build>[0-9A-Za-z.-]+))?$"
)
def read_version() -> str:
"""Read and return the raw version string from VERSION file."""
if not VERSION_FILE.exists():
print(f"error: {VERSION_FILE} not found", file=sys.stderr)
sys.exit(1)
return VERSION_FILE.read_text().strip()
def parse_version(raw: str) -> dict:
"""Parse a semver string into components."""
m = SEMVER_RE.match(raw)
if not m:
print(f"error: invalid semver '{raw}'", file=sys.stderr)
sys.exit(1)
return m.groupdict()
def write_version(version: str) -> None:
"""Write a version string to VERSION file."""
VERSION_FILE.write_text(version + "\n")
def bump(version: str, component: str) -> str:
"""Bump a version component and reset lower components."""
v = parse_version(version)
major, minor, patch = int(v["major"]), int(v["minor"]), int(v["patch"])
if component == "major":
major += 1
minor = 0
patch = 0
elif component == "minor":
minor += 1
patch = 0
elif component == "patch":
patch += 1
return f"{major}.{minor}.{patch}"
def main():
parser = argparse.ArgumentParser(description="UniversalisOS version tool")
group = parser.add_mutually_exclusive_group()
group.add_argument("--major", action="store_true", help="Print major version")
group.add_argument("--minor", action="store_true", help="Print minor version")
group.add_argument("--patch", action="store_true", help="Print patch version")
group.add_argument("--bump", choices=["major", "minor", "patch"],
help="Bump a version component and update VERSION file")
parser.add_argument("--kernel", nargs=2, metavar=("ARCH", "PLATFORM"),
help="Append arch.platform suffix (e.g. armv7 qemu-arm-virt)")
parser.add_argument("--set", dest="set_version", metavar="VERSION",
help="Set VERSION file to the given value")
args = parser.parse_args()
raw = read_version()
if args.set_version:
write_version(args.set_version)
raw = args.set_version
if args.bump:
raw = bump(raw, args.bump)
write_version(raw)
v = parse_version(raw)
if args.major:
print(v["major"])
elif args.minor:
print(v["minor"])
elif args.patch:
print(v["patch"])
elif args.kernel:
arch, platform = args.kernel
print(f"{raw}.{arch}.{platform}")
else:
print(raw)
if __name__ == "__main__":
main()