82 lines
2.6 KiB
Python
82 lines
2.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Derive a Debian package version (or channel) from a hermes-agent release tag.
|
|
|
|
Pure function; imported by scripts/termux/build_deb.sh and unit-tested by
|
|
tests/scripts/test_termux_deb_version.py (Task 4 of .hermes/plans/2026-08-31_termux-deb.md).
|
|
|
|
Mapping:
|
|
v1.2.3 -> 1.2.3-1
|
|
v1.2.3+canary.20260831T120000Z -> 1.2.3~canary.20260831T120000Z-1
|
|
|
|
The ``~`` ranks the canary below the corresponding stable in dpkg's version
|
|
ordering.
|
|
|
|
``--channel`` derives the release channel from the SAME tag regex: a tag with
|
|
a nightly timestamp is ``nightly``, everything else is ``stable``. This is the
|
|
single source of truth for the channel; workflows and other tooling must call
|
|
this instead of re-typing a case statement.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import sys
|
|
|
|
_TAG_RE = re.compile(
|
|
r"^v(?P<major>0|[1-9]\d{0,2})\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)"
|
|
r"(?:\+canary\.(?P<ts>20\d{6}T\d{6}Z))?$"
|
|
)
|
|
|
|
|
|
def _match_tag(tag: str) -> re.Match[str]:
|
|
m = _TAG_RE.match(tag)
|
|
if m is None:
|
|
raise ValueError(
|
|
f"malformed release tag {tag!r}: expected v<MAJOR>.<MINOR>.<PATCH> "
|
|
"or v<MAJOR>.<MINOR>.<PATCH>+canary.<UTC timestamp>"
|
|
)
|
|
return m
|
|
|
|
|
|
def deb_version_for_tag(tag: str) -> str:
|
|
"""Map a release tag to its Debian version. Raises ValueError on malformed tags."""
|
|
m = _match_tag(tag)
|
|
base = f"{m.group('major')}.{m.group('minor')}.{m.group('patch')}"
|
|
ts = m.group("ts")
|
|
if ts is None:
|
|
return f"{base}-1"
|
|
return f"{base}~canary.{ts}-1"
|
|
|
|
|
|
def channel_for_tag(tag: str) -> str:
|
|
"""Map a release tag to its channel: 'canary' or 'stable'.
|
|
|
|
Derived from the same _TAG_RE as deb_version_for_tag, so the two can never
|
|
drift: a tag that yields a '~canary' deb version is canary, and the
|
|
malformed-tag rejection is identical.
|
|
"""
|
|
m = _match_tag(tag)
|
|
return "canary" if m.group("ts") else "stable"
|
|
|
|
|
|
def main(argv: list[str]) -> int:
|
|
args = argv[1:]
|
|
channel_mode = False
|
|
if args and args[0] == "--channel":
|
|
channel_mode = True
|
|
args = args[1:]
|
|
if len(args) != 1:
|
|
mode = "deb_version.py --channel <tag>" if channel_mode else "deb_version.py <tag>"
|
|
print(f"usage: {mode}", file=sys.stderr)
|
|
return 2
|
|
try:
|
|
print(channel_for_tag(args[0]) if channel_mode else deb_version_for_tag(args[0]))
|
|
except ValueError as exc:
|
|
print(f"deb_version: {exc}", file=sys.stderr)
|
|
return 1
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main(sys.argv))
|