fix(builder): 版本时间精确到分钟,修复同日重构建的 apt 排序

同日同分支的测试版本前缀完全相同,只剩 +g<sha> 可区分;Debian 版本比较把
hex 当普通文本,新构建可能小于旧构建,apt upgrade 因此拿不到最新包。

build_date() → build_stamp(),改为 YYYYMMDDHHMM,时间与日期拼成一段数字
才能按数值比较。不能写成 20260918T1722:Debian 里字母排在标点之前,带 T
的版本小于同日旧格式包的 20260918.…,修复当天仍抢不过已上传的旧包。

补回归测试,用 dpkg --compare-versions 锁定同日重建更大、且压过旧格式同日包。
This commit is contained in:
2026-09-19 13:23:52 +08:00
parent b476176f3b
commit eb2d0ce651
4 changed files with 97 additions and 33 deletions
+53 -10
View File
@@ -1,6 +1,7 @@
from __future__ import annotations
import os
import shutil
import stat
import subprocess
import tempfile
@@ -14,9 +15,9 @@ VERSION_SH = SCRIPTS / "version.sh"
PUBLISH_DOCKER = SCRIPTS / "publish_docker.sh"
VERSION_MK = SCRIPTS / "version.mk"
# Frozen build date so test versions are deterministic.
# Frozen build instant so test versions are deterministic.
SOURCE_DATE_EPOCH = "1704067200" # 2024-01-01T00:00:00Z
BUILD_DATE = "20240101"
BUILD_STAMP = "202401010000"
def git(cwd: Path, *args: str, env: dict[str, str] | None = None) -> str:
@@ -106,12 +107,12 @@ class BuilderVersionTests(unittest.TestCase):
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(
result.stdout.strip(),
f"1.2.1~{BUILD_DATE}.main.1+g{sha}",
f"1.2.1~{BUILD_STAMP}.main.1+g{sha}",
)
docker = version_sh("--docker", cwd=repo)
self.assertEqual(
docker.stdout.strip(),
f"1.2.1-{BUILD_DATE}.main.1.g{sha}",
f"1.2.1-{BUILD_STAMP}.main.1.g{sha}",
)
def test_test_version_uses_ancestor_not_global_max(self) -> None:
@@ -141,12 +142,12 @@ class BuilderVersionTests(unittest.TestCase):
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(
result.stdout.strip(),
f"1.4.3~{BUILD_DATE}.feat-login-v2.2+g{feature_sha}",
f"1.4.3~{BUILD_STAMP}.feat-login-v2.2+g{feature_sha}",
)
docker = version_sh("--docker", cwd=repo)
self.assertEqual(
docker.stdout.strip(),
f"1.4.3-{BUILD_DATE}.feat-login-v2.2.g{feature_sha}",
f"1.4.3-{BUILD_STAMP}.feat-login-v2.2.g{feature_sha}",
)
def test_ignores_prerelease_and_app_tags(self) -> None:
@@ -164,7 +165,7 @@ class BuilderVersionTests(unittest.TestCase):
sha = sha7(repo)
result = version_sh(cwd=repo)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(result.stdout.strip(), f"1.0.1~{BUILD_DATE}.main.1+g{sha}")
self.assertEqual(result.stdout.strip(), f"1.0.1~{BUILD_STAMP}.main.1+g{sha}")
def test_no_stable_tag(self) -> None:
with tempfile.TemporaryDirectory() as temp:
@@ -176,7 +177,49 @@ class BuilderVersionTests(unittest.TestCase):
sha = sha7(repo)
result = version_sh(cwd=repo)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(result.stdout.strip(), f"0.0.0~{BUILD_DATE}.main+g{sha}")
self.assertEqual(result.stdout.strip(), f"0.0.0~{BUILD_STAMP}.main+g{sha}")
def test_same_day_rebuild_sorts_higher(self) -> None:
"""同一天重建必须比上一包大,否则 apt 永远拿不到最新构建。
只写到日期时同分支同日的版本前缀完全相同,只剩 +g<sha> 可区分,
Debian 版本比较把哈希当普通文本,新包可能被判成比旧包小。带分钟的
时间必须与日期拼在同一段数字里:字母排在标点之前,YYYYMMDDTHHMM
这种带字母分隔的写法会小于同日旧格式包的 YYYYMMDD.…。
"""
if shutil.which("dpkg") is None:
self.skipTest("dpkg not available")
with tempfile.TemporaryDirectory() as temp:
repo = Path(temp) / "repo"
repo.mkdir()
init_repo(repo)
(repo / "a").write_text("1\n", encoding="utf-8")
commit(repo, "init")
early = version_sh(cwd=repo, env={"SOURCE_DATE_EPOCH": "1704067200"}) # 00:00Z
later = version_sh(cwd=repo, env={"SOURCE_DATE_EPOCH": "1704103200"}) # 10:00Z
self.assertEqual(early.returncode, 0, early.stderr)
self.assertEqual(later.returncode, 0, later.stderr)
first, second = early.stdout.strip(), later.stdout.strip()
self.assertEqual(first.rsplit(".", 1)[0].split("~")[1].split(".")[0], "202401010000")
self.assertEqual(second.rsplit(".", 1)[0].split("~")[1].split(".")[0], "202401011000")
self.assertEqual(
second,
f"0.0.0~202401011000.main+g{sha7(repo)}",
"时间必须与日期拼成一段数字,不能插入字母分隔符",
)
for label, a, b in (
("同日重建", second, first),
("已上传的旧格式同日包", second, "0.0.0~20240101.main+gaaaaaaa"),
("次日新格式包", "0.0.0~202401021000.main+gccccccc", second),
("下一正式版", "0.1.0", second),
):
compared = subprocess.run(
["dpkg", "--compare-versions", a, "gt", b],
text=True,
capture_output=True,
check=False,
)
self.assertEqual(compared.returncode, 0, f"{label}:{a} 应大于 {b}")
def test_detached_uses_env_branch(self) -> None:
with tempfile.TemporaryDirectory() as temp:
@@ -192,7 +235,7 @@ class BuilderVersionTests(unittest.TestCase):
git(repo, "checkout", "--detach", "HEAD")
result = version_sh(cwd=repo, env={"BUILD_BRANCH": "ci_job"})
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(result.stdout.strip(), f"0.1.1~{BUILD_DATE}.ci-job.1+g{sha}")
self.assertEqual(result.stdout.strip(), f"0.1.1~{BUILD_STAMP}.ci-job.1+g{sha}")
def test_source_date_epoch_changes_test_date(self) -> None:
with tempfile.TemporaryDirectory() as temp:
@@ -207,7 +250,7 @@ class BuilderVersionTests(unittest.TestCase):
sha = sha7(repo)
later = version_sh(cwd=repo, env={"SOURCE_DATE_EPOCH": "1725148800"})
self.assertEqual(later.returncode, 0, later.stderr)
self.assertEqual(later.stdout.strip(), f"1.0.1~20240901.main.1+g{sha}")
self.assertEqual(later.stdout.strip(), f"1.0.1~202409010000.main.1+g{sha}")
def test_official_version_ignores_build_date(self) -> None:
with tempfile.TemporaryDirectory() as temp: