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) .