Files
hermes-agent/scripts/termux/deb_version.py
ethernet cb7c688171 test: mirror the source tree for 27 misfiled root tests; one PM home fixture
Root-level tests/ is for root-level modules; 19 files testing scripts/, 3 testing
pm/ and 5 testing hermes_cli/ move to the mirrored directory (workflow file lists
and cross-imports updated; path arithmetic bumped one level).

tests/pm gains a conftest with the isolated_machine_home fixture that seven
modules had copy-pasted verbatim.
2026-09-18 20:13:26 -04:00

87 lines
2.9 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.2026083112 -> 1.2.3~canary.2026083112-1
The ``~`` ranks the nightly below the corresponding stable in dpkg's version
ordering. No digit cap on any component: the repo's CalVer line (v2026.9.x)
is a valid major.
``--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
# The canary timestamp shape MUST match the canonical _CANARY_TAG_RE in
# hermes_cli/update_channel.py (exactly 8 or 14 digits, 20-prefixed) and
# channel_for_tag in scripts/releases/r2.py. Cross-referenced by
# tests/scripts/test_termux_deb_version.py::test_canary_tag_shape_matches_canonical.
_TAG_RE = re.compile(
r"^v(?P<major>0|[1-9]\d*)\.(?P<minor>\d+)\.(?P<patch>\d+)"
r"(?:-canary\.(?P<ts>20\d{6}(?:\d{6})?))?$"
)
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.<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") is not None 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))