feat(builder): append YYYYMMDD to test versions
This commit is contained in:
@@ -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 日期。
|
||||
|
||||
### 分支名清洗
|
||||
|
||||
|
||||
@@ -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`。
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user