Files
t 6de5d52363
build / build (push) Successful in 51s
feat(version): 开发号随提交自动递增 + 首个正式版本 0.1.0
按使用者要求定的版本规则(自动升的是**开发标识**,发布版本与 tag 仍由人批准):

- **开发号自动递增**:display_version(development) = `<正式版本的主.次>.<自 v<正式版本> 以来的提交数>`
  (正式 0.1.0 之后第 1 个提交 0.1.1、第 12 个 0.1.12;恰在发布提交上 = 正式号)。
  它**不写进 CHANGELOG**(发布记录只在发版时动),由 git 算出来 ⇒ 每个提交都有自己的号、数字只增。
- **浅克隆不猜号**:`--depth` 会把条数算少 ⇒ 浅仓库里拒绝算号,退回 `<版本>-dev+g<短SHA>`;
  检出步骤在仓库有 tag 时 `--unshallow` 补全历史。
- 接线三处齐全:`version.py --identity` → 门禁 `-X …CommitsSinceRelease=<n>`;Makefile 的
  `COMMITS_SINCE_RELEASE`;Dockerfile 同名 build-arg;Go 侧新增 `CommitsSinceRelease` 与
  DisplayVersion() 新规则;契约测试**交叉核对**"注入的提交数 == git 算出的"(防注入错号)。
- **首个正式版本 0.1.0**:CHANGELOG 的 `[Unreleased]` 提成 `## [0.1.0] - 2026-10-13`;
  头部补"版本怎么走"两行;RELEASE_RUNBOOK 补递增规则与"下一次发布把次版本加一"。
- 发版预检新增:打印**当前开发号**,并挡住"候选正式版本 <= 当前开发号"(会把开发号当正式号;建议次版本加一)。

验证:version.py --self-test(含 tag 后第 2 个提交 = 0.2.2、恰在 tag 上 = 0.2.0、无 tag 不猜号);
release_preflight --self-test(21 分支,含开发号两条);go test 本地三态 + 临时仓库 tag+2 提交实测
(Python 与 Go 都得出 0.2.2);反向夹具 85/0;make verify 15/15 success。
2026-10-13 16:45:56 +08:00

99 lines
5.4 KiB
Makefile
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
GO ?= go
PY ?= python3
# 版本号的唯一来源是仓库根的 CHANGELOG.md(正本 §6.1:版本号只有一个来源)。
# 刻意**不**在构建时跑 `git describe --tags`:日常检出是浅克隆、拿不到 tag,
# 那会让版本号在本地正常、在 CI 上静默退化(见 docs/agents/traps.md trap-5)。
# **正式 tag** 由人批准后创建(注释 tag `vX.Y.Z`),CI 只核验「tag 版本 == 该提交里的版本记录」。
VERSION ?= $(shell $(PY) scripts/version.py 2>/dev/null || echo dev)
RELEASE_VERSION ?= $(shell $(PY) scripts/version.py 2>/dev/null || echo)
BUILD_KIND ?= development
# 自 v<正式版本> 以来的提交数(开发号 = <主>.<次>.<它>;取不到就空 ⇒ 构建侧退回 -dev+g<短SHA>)
COMMITS_SINCE_RELEASE ?= $(shell $(PY) -c "import json,subprocess;print(json.loads(subprocess.run(['$(PY)','scripts/version.py','--identity'],capture_output=True,text=True).stdout).get('commits_since_release') or '')" 2>/dev/null)
COMMIT_SHA ?= $(shell git rev-parse --short=12 HEAD 2>/dev/null || echo unknown)
FULL_SHA ?= $(shell git rev-parse HEAD 2>/dev/null || echo unknown)
BUILD_TIME ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
.PHONY: verify checks list \
format static-go static-web deps-install build-go build-web \
tests-go tests-web contracts security manifests-locked \
test vet build web-test web-typecheck web-build \
version changelog-check version-contract image
# ── 唯一权威门禁入口(正本 §3.1)────────────────────────────────────────
# 一条命令跑完全部必需检查、**串行**、失败即停,并产出 ci-report.json。
# CI 的 build 作业跑的就是这一条(`.gitea/workflows/build.yml` 里的 `make verify`)——
# 本地与 CI 同一份逻辑,不存在"两边各写一套,迟早只改一边"的问题。
verify:
$(PY) scripts/ci_checks.py
# 清单:哪些是必需项、在哪个档案(local / ci)下必需
list:
$(PY) scripts/ci_checks.py --list
# 门禁自身的反向自检(汇总逻辑:必需项未运行/被跳过时**不得**报全绿)
checks:
$(PY) scripts/ci_checks.py --self-test
# ── 单项(调试用)──────────────────────────────────────────────────────
# ⚠️ `make <单项>` 只跑子集,**不算全绿**:单项命令会自己在输出里说明这一点。
# 写这一组是为了"红了要知道红在哪一项",不是为了跳过整个门禁。
format:
$(PY) scripts/ci_checks.py --only format
static-go:
$(PY) scripts/ci_checks.py --only static-go
static-web:
$(PY) scripts/ci_checks.py --only static-web
deps-install:
$(PY) scripts/ci_checks.py --only deps-install
build-go:
$(PY) scripts/ci_checks.py --only build-go
build-web:
$(PY) scripts/ci_checks.py --only build-web
tests-go:
$(PY) scripts/ci_checks.py --only tests-go
tests-web:
$(PY) scripts/ci_checks.py --only tests-web
contracts:
$(PY) scripts/ci_checks.py --only contracts
security:
$(PY) scripts/ci_checks.py --only security-secrets,security-deps
manifests-locked:
$(PY) scripts/ci_checks.py --only manifests-locked
# ── 旧名字保留为别名:文档和习惯都还在引用它们 ─────────────────────────
test: tests-go
vet: static-go
build: build-go
web-test: tests-web
web-typecheck: static-web
web-build: build-web
# 构建身份(JSON):release_version / build_kind / commit_sha / display_version / built_at
# 开发构建也能用:没有正式版本时是 dev+g<短SHA>,不会退化成"上一正式版本号"。
version:
@$(PY) scripts/version.py --identity
# 变更记录结构自检:小节只认 Added/Changed/Fixed/Security(分类可省略、不许空栏目),
# 破坏性变化必须在 Changed 里带发布文档链接。加 --tags 再核「tag 版本 == 该提交里的版本记录」。
changelog-check:
@$(PY) scripts/check_changelog.py
# 版本契约:注入进二进制的身份必须与 CHANGELOG 对账,且**开发构建不许自称正式版**。
# ⚠️ 不注入时这两条用例会 SKIP —— 那是"本地不想被迫先构建"的便利,但**CI 必须断言它们真的 PASS**
# (门禁的 contracts 项就是这么做的,并额外跑一条"自称正式版 ⇒ 必须红"的负例)。
version-contract:
$(GO) test -ldflags "-X knowledge-governance/internal/version.Number=$(VERSION) \
-X knowledge-governance/internal/version.ReleaseVersion=$(RELEASE_VERSION) \
-X knowledge-governance/internal/version.BuildKind=$(BUILD_KIND) \
-X knowledge-governance/internal/version.CommitsSinceRelease=$(COMMITS_SINCE_RELEASE) \
-X knowledge-governance/internal/version.CommitSHA=$(FULL_SHA)" \
-count=1 -run Test -v ./internal/version/...
# 构建镜像。build-arg 必须显式传入 —— 不传就落成 dev/unknown(故意的:
# 一个显眼的 dev 好过一个看起来正常的假版本号)。compose.yaml 的 args 透传见 PR TODO。
image:
docker build --build-arg VERSION=$(VERSION) --build-arg RELEASE_VERSION=$(RELEASE_VERSION) \
--build-arg BUILD_KIND=$(BUILD_KIND) --build-arg COMMITS_SINCE_RELEASE=$(COMMITS_SINCE_RELEASE) \
--build-arg COMMIT_SHA=$(COMMIT_SHA) \
--build-arg BUILD_TIME=$(BUILD_TIME) -t knowledge-governance-p1:$(VERSION)-$(COMMIT_SHA) .