feat(builder): append YYYYMMDD to test versions

This commit is contained in:
2026-09-08 10:59:17 +08:00
parent 83deb886b5
commit 852dee3a69
4 changed files with 74 additions and 22 deletions
+10 -7
View File
@@ -84,20 +84,23 @@ Docker registry/ns/foo:1.4.2
### 测试(其它任何 commit)
基线 = 祖先上最近的那颗稳定 tag(没有则为 `0.0.0`),再加上清洗后的
分支名、相对距离、短 SHA。测试与正式进**同一个** apt / Docker 仓库,
所以 DEB 必须用 `~`,保证测试包不会 `apt upgrade` 盖住正式包。
基线 = 祖先上最近的那颗稳定 tag(没有则为 `0.0.0`),再加上构建日
`YYYYMMDD`、清洗后的分支名、相对距离、短 SHA。测试与正式进**同一个**
apt / Docker 仓库,所以 DEB 必须用 `~`,保证测试包不会 `apt upgrade`
盖住正式包。日期写在 `~` 后面,这样 `1.4.2~20260908…` 仍小于 `1.4.2`。
分支 `feat/login-v2`,相对 `v1.4.2` 第 7 个 commit,SHA `abc1234`:
分支 `feat/login-v2`,相对 `v1.4.2` 第 7 个 commit,SHA `abc1234`,
构建日 `20260908`:
```text
规范 / DEB 1.4.2~feat-login-v2.7+gabc1234
文件名 foo_1.4.2~feat-login-v2.7+gabc1234_amd64.deb
Docker registry/ns/foo:1.4.2-feat-login-v2.7.gabc1234
规范 / DEB 1.4.2~20260908.feat-login-v2.7+gabc1234
文件名 foo_1.4.2~20260908.feat-login-v2.7+gabc1234_amd64.deb
Docker registry/ns/foo:1.4.2-20260908.feat-login-v2.7.gabc1234
```
Docker tag 由规范版本映射:`~` → `-`,`+g` → `.g`(Docker 不允许 `~`)。
安装测试包必须显式指定版本或完整 tag,不能靠无参 `apt upgrade`。
构建日取本机当前日期;设了 `SOURCE_DATE_EPOCH` 则用该时刻的 UTC 日期。
### 分支名清洗
+8 -5
View File
@@ -74,17 +74,20 @@ manage-release,不猜测下一个正式 SemVer。正式 tag 由 manage-release
| 判定 | 规范版本 | DEB 文件 | Docker tag |
| --- | --- | --- | --- |
| HEAD exact-match 稳定 tag `v1.4.2` | `1.4.2` | `name_1.4.2_<arch>.deb` | `1.4.2` |
| 其它 commit;祖先最近稳定 tag `v1.4.2` | `1.4.2~<branch>.<n>+g<sha>` | `name_1.4.2~<branch>.<n>+g<sha>_<arch>.deb` | `1.4.2-<branch>.<n>.g<sha>` |
| 祖先中没有稳定 tag | `0.0.0~<branch>+g<sha>` | 同上替换规范版本 | 同上映射 |
| 其它 commit;祖先最近稳定 tag `v1.4.2` | `1.4.2~<date>.<branch>.<n>+g<sha>` | `name_1.4.2~<date>.<branch>.<n>+g<sha>_<arch>.deb` | `1.4.2-<date>.<branch>.<n>.g<sha>` |
| 祖先中没有稳定 tag | `0.0.0~<date>.<branch>+g<sha>` | 同上替换规范版本 | 同上映射 |
测试与正式进入同一 apt / Docker 仓库。测试 DEB 必须用 `~`,使
`1.4.2~…` < `1.4.2`,`apt upgrade` 不会装上测试包。Docker tag 不得含
`~`、`/`、`:`,由规范版本把 `~` → `-`、`+g` → `.g`。
`1.4.2~…` < `1.4.2`,`apt upgrade` 不会装上测试包。日期必须写在 `~`
之后,不能写成 `1.4.2.<date>` / `1.4.2+<date>`,否则测试包会大于正式
包。Docker tag 不得含 `~`、`/`、`:`,由规范版本把 `~` → `-`、`+g` → `.g`。
`<date>`:构建日 `YYYYMMDD`。已设 `SOURCE_DATE_EPOCH` 时用该时刻的 UTC
日期,否则用本机当前日期。正式 `X.Y.Z` 不加日期。
`<branch>`:`git rev-parse --abbrev-ref HEAD`,detached 时用
`BUILD_BRANCH` / `CI_COMMIT_BRANCH` / `GITHUB_REF_NAME`,再没有则
`detached`。清洗:小写;`/`、`_` → `-`;去掉非 `[a-z0-9-]`;压缩连续
`-`;过长截断(给 base、距离、SHA 留位置;Docker tag 上限 128)。
`-`;过长截断(给 base、日期、距离、SHA 留位置;Docker tag 上限 128)。
`<n>` 为基线 tag 到 HEAD 的 commit 数;`<sha>` 为 7 位短哈希。不要把
`--dirty` 写入版本;脏树发布仍走既有门禁。显式 `VERSION=` / `IMAGE_TAG=`
可覆盖推导,但不得把非 exact-match 的 commit 标成正式 `X.Y.Z`。
+19 -5
View File
@@ -11,8 +11,11 @@ With --docker, print the Docker tag rendering of that version.
Derivation (builder contract «版本号»):
official HEAD exact-match of vX.Y.Z → X.Y.Z
test nearest ancestor stable tag → X.Y.Z~branch.n+gSHA
no tag no stable tag reachable from HEAD → 0.0.0~branch+gSHA
test nearest ancestor stable tag → X.Y.Z~YYYYMMDD.branch.n+gSHA
no tag no stable tag reachable from HEAD → 0.0.0~YYYYMMDD.branch+gSHA
YYYYMMDD is the build date. SOURCE_DATE_EPOCH, if set, is that instant in
UTC; otherwise the local calendar date. Official X.Y.Z never gets a date.
Stable tags match v<major>.<minor>.<patch> only. Baseline is ancestry, not
the highest version in the repository. This script does not fetch tags.
@@ -100,6 +103,16 @@ short_sha() {
gitc rev-parse HEAD | cut -c1-7
}
# Build-day YYYYMMDD. SOURCE_DATE_EPOCH (UTC) wins when set, for reproducible
# test packages; otherwise the machine's current local date.
build_date() {
if [[ -n "${SOURCE_DATE_EPOCH:-}" ]]; then
date -u -d "@${SOURCE_DATE_EPOCH}" +%Y%m%d
else
date +%Y%m%d
fi
}
# Highest stable tag pointing at HEAD, or empty.
official_tag_at_head() {
local tag best=
@@ -135,7 +148,7 @@ nearest_stable_tag() {
}
derive_canonical() {
local tag base n sha branch
local tag base n sha branch ymd
tag=$(official_tag_at_head)
if [[ -n "$tag" ]]; then
printf '%s' "${tag#v}"
@@ -143,13 +156,14 @@ derive_canonical() {
fi
sha=$(short_sha)
branch=$(branch_slug)
ymd=$(build_date)
base=$(nearest_stable_tag)
if [[ -z "$base" ]]; then
printf '0.0.0~%s+g%s' "$branch" "$sha"
printf '0.0.0~%s.%s+g%s' "$ymd" "$branch" "$sha"
return
fi
n=$(gitc rev-list --count "${base}..HEAD")
printf '%s~%s.%s+g%s' "${base#v}" "$branch" "$n" "$sha"
printf '%s~%s.%s.%s+g%s' "${base#v}" "$ymd" "$branch" "$n" "$sha"
}
to_docker() {
+37 -5
View File
@@ -14,6 +14,10 @@ VERSION_SH = SCRIPTS / "version.sh"
PUBLISH_DOCKER = SCRIPTS / "publish_docker.sh"
VERSION_MK = SCRIPTS / "version.mk"
# Frozen build date so test versions are deterministic.
SOURCE_DATE_EPOCH = "1704067200" # 2024-01-01T00:00:00Z
BUILD_DATE = "20240101"
def git(cwd: Path, *args: str, env: dict[str, str] | None = None) -> str:
merged = os.environ.copy()
@@ -48,6 +52,7 @@ def sha7(cwd: Path) -> str:
def version_sh(*args: str, cwd: Path, env: dict[str, str] | None = None) -> subprocess.CompletedProcess[str]:
merged = os.environ.copy()
merged["SOURCE_DATE_EPOCH"] = SOURCE_DATE_EPOCH
if env:
merged.update(env)
return subprocess.run(
@@ -113,12 +118,12 @@ class BuilderVersionTests(unittest.TestCase):
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(
result.stdout.strip(),
f"1.4.2~feat-login-v2.2+g{feature_sha}",
f"1.4.2~{BUILD_DATE}.feat-login-v2.2+g{feature_sha}",
)
docker = version_sh("--docker", cwd=repo)
self.assertEqual(
docker.stdout.strip(),
f"1.4.2-feat-login-v2.2.g{feature_sha}",
f"1.4.2-{BUILD_DATE}.feat-login-v2.2.g{feature_sha}",
)
def test_ignores_prerelease_and_app_tags(self) -> None:
@@ -136,7 +141,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.0~main.1+g{sha}")
self.assertEqual(result.stdout.strip(), f"1.0.0~{BUILD_DATE}.main.1+g{sha}")
def test_no_stable_tag(self) -> None:
with tempfile.TemporaryDirectory() as temp:
@@ -148,7 +153,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"0.0.0~main+g{sha}")
self.assertEqual(result.stdout.strip(), f"0.0.0~{BUILD_DATE}.main+g{sha}")
def test_detached_uses_env_branch(self) -> None:
with tempfile.TemporaryDirectory() as temp:
@@ -164,7 +169,34 @@ 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.0~ci-job.1+g{sha}")
self.assertEqual(result.stdout.strip(), f"0.1.0~{BUILD_DATE}.ci-job.1+g{sha}")
def test_source_date_epoch_changes_test_date(self) -> None:
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")
git(repo, "tag", "v1.0.0")
(repo / "b").write_text("2\n", encoding="utf-8")
commit(repo, "next")
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.0~20240901.main.1+g{sha}")
def test_official_version_ignores_build_date(self) -> None:
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")
git(repo, "tag", "-a", "v1.4.2", "-m", "1.4.2")
result = version_sh(cwd=repo, env={"SOURCE_DATE_EPOCH": "1725148800"})
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(result.stdout.strip(), "1.4.2")
def test_override_official_rejected_off_tag(self) -> None:
with tempfile.TemporaryDirectory() as temp: