6de5d52363
build / build (push) Successful in 51s
按使用者要求定的版本规则(自动升的是**开发标识**,发布版本与 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。
99 lines
5.4 KiB
Makefile
99 lines
5.4 KiB
Makefile
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) .
|