scripts/dev-build-version.py turns version.txt plus the head commit into 0.7.1-dev.20261019.<hash>: the -dev display string for installer names and the native settings page, and a YYMMDD stamp for the fourth VERSIONINFO field. apply_version.py accepts an optional fourth quad field to carry that stamp; releases leave it at 0, so their behaviour is unchanged. The settings page's version parser strips the -dev suffix and compares numbers, which is what keeps the update check quiet on dev installs -- no opt-out flag needed.
101 lines
3.8 KiB
Python
101 lines
3.8 KiB
Python
#!/usr/bin/env python3
|
||
"""Derive the dev-build version from version.txt.
|
||
|
||
The release owns version.txt (release-please), and dev builds must never masquerade as one.
|
||
The dev number keeps the release base for human recognition and appends a date and the short
|
||
commit: 0.7.1-dev.20261019.7fa6fb1a.
|
||
|
||
The strings go to the installer name and the settings page. The TSF DLL's VERSIONINFO quad has
|
||
four 16-bit fields, so the date compresses to YYMMDD in the fourth slot: 0.7.1.261019. yy > 79
|
||
sorts above every year this product exists, and any real release leaves that slot at 0, so a dev
|
||
DLL always reads as newer than its own base and older than the next release. The settings page's
|
||
parseVersion strips the -dev suffix and compares 0.7.1, which is why the update check stays quiet.
|
||
|
||
Windows filenames and Inno Setup accept the dots in the short hash; a detached HEAD (workflow
|
||
dispatch on a raw SHA) falls back to the first twelve hex digits.
|
||
|
||
python scripts/dev-build-version.py # all three outputs, one per line
|
||
python scripts/dev-build-version.py --display # 0.7.1-dev.20261019.7fa6fb1a
|
||
python scripts/dev-build-version.py --base # 0.7.1
|
||
python scripts/dev-build-version.py --date # 261019
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import datetime
|
||
import re
|
||
import subprocess
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||
VERSION_FILE = REPO_ROOT / "version.txt"
|
||
|
||
SEMVER = re.compile(r"^(\d+)\.(\d+)\.(\d+)$")
|
||
SHORT_SHA = re.compile(r"^[0-9a-f]{7,40}$")
|
||
|
||
|
||
def read_base() -> tuple[str, str, str]:
|
||
if not VERSION_FILE.is_file():
|
||
raise SystemExit(f"error: {VERSION_FILE} is missing")
|
||
raw = VERSION_FILE.read_text(encoding="utf-8").strip()
|
||
match = SEMVER.match(raw)
|
||
if not match:
|
||
raise SystemExit(f"error: version.txt must hold MAJOR.MINOR.PATCH, got {raw!r}")
|
||
return match.groups()
|
||
|
||
|
||
def read_short_sha(explicit: str | None) -> str:
|
||
if explicit:
|
||
sha = explicit.strip().lower()
|
||
if not SHORT_SHA.match(sha):
|
||
raise SystemExit(f"error: --commit must be hex digits, got {explicit!r}")
|
||
return sha[:12]
|
||
result = subprocess.run(
|
||
["git", "rev-parse", "--short=12", "HEAD"],
|
||
cwd=REPO_ROOT,
|
||
capture_output=True,
|
||
text=True,
|
||
)
|
||
if result.returncode != 0:
|
||
raise SystemExit(f"error: cannot read the commit: {result.stderr.strip()}")
|
||
return result.stdout.strip()
|
||
|
||
|
||
def build(base: tuple[str, str, str], today: datetime.date, sha: str) -> tuple[str, str]:
|
||
major, minor, patch = base
|
||
display = f"{major}.{minor}.{patch}-dev.{today:%Y%m%d}.{sha}"
|
||
# 四段 quad 的末位放 YYMMDD:YY > 79 恒大于本产品存在的一切年份,任何正式版该位是 0,
|
||
# 所以 dev DLL 恒读作比基线新、比下一个发布旧。
|
||
date = today.strftime("%y%m%d")
|
||
quad = f"{major}.{minor}.{patch}.{date}"
|
||
return display, date
|
||
|
||
|
||
def main() -> int:
|
||
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||
output = parser.add_mutually_exclusive_group()
|
||
output.add_argument("--display", action="store_true", help="print the display version only")
|
||
output.add_argument("--base", action="store_true", help="print the base version only")
|
||
output.add_argument("--date", action="store_true", help="print the YYMMDD stamp only")
|
||
parser.add_argument("--commit", metavar="SHA", help="use this short commit instead of reading git")
|
||
args = parser.parse_args()
|
||
|
||
base = read_base()
|
||
display, date = build(base, datetime.date.today(), read_short_sha(args.commit))
|
||
if args.display:
|
||
print(display)
|
||
elif args.base:
|
||
print(".".join(base))
|
||
elif args.date:
|
||
print(date)
|
||
else:
|
||
print(display)
|
||
print(date)
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|