Files
.pouch/skills/builder/references/contract.md
T

14 KiB
Raw Blame History

Builder 构建发布契约 v1

本契约是 builder skill 的规范本体。scripts/check.py 是它的可执行形态:改契约先改 check.py,本文档跟随。所有接入项目按同一套 make 目标、产物形状和环境变量执行, builder 脚本只做发布,不做项目特定的构建逻辑。

配置边界按 Git 项目根生效:makefile.builder 所在仓库就是 builder 项目根。 如果父仓库通过 submodule 引入另一个项目,submodule 的构建、.env.builder 和 .pouch/builder/ 分发脚本属于 submodule;父仓库不得替它承载这些配置。

分工原则:make 管构建(项目内、确定性),skill 脚本管发布(跨项目 SSOT), Agent 只保留授权判断和歧义处理。 构建在哪台机器执行、使用哪个 CLI/model 和 session 如何保存,由共享 agent-runtime 的 binding 决定;makefile 不能承载主机或 Agent 选择。

builder.android、builder.ios、builder.harmony 都是合法的可配置 binding。新增平台 只增加 binding 和对应 make target,不改 builder skill 的固定枚举。

契约文件固定为项目根 makefile.builder,调用方式:

make -f makefile.builder <target>

不要把 builder 目标写进用户的 Makefile 或 makefile。check.py 只读 makefile.builder。

发布凭据固定为项目根 .env.builder。不要把这些键写进用户的 .env。脚本不读取 .env。

1. Make 目标

必备目标(所有项目)

目标 要求
help 分组列出全部目标;首屏含当前版本
version 输出一行版本号,适合脚本消费
clean 只删除明确、受限的构建产物目录
build 编译/打包主产物;尊重 ARCH(单架构原语);不得内含任何上传动作

条件目标

目标 适用 要求
deb 有 DEB 产物的项目 产出 $(DIST_DIR)/<name>_<version>_<arch>.deb;只构建不上传。未在命令行/环境指定 ARCH= 且 ARCHES 多于一项时,按清单逐个递归构建;指定了 ARCH= 则只打那一个
docker 有镜像的项目 构建本地单平台镜像 linux/$(ARCH);禁止 --push、禁止多平台
android 有 Android APK/AAB 的项目 可与 build 相同。产出 $(DIST_DIR)/<name>_<version>_<arch>.apk(或 .aab);只构建不上传。ARCHES 是构建机 ISA;fat APK 只留本机一项
ios 有 iOS IPA 的项目 可与 build 相同。产出 $(DIST_DIR)/<name>_<version>_<arch>.ipa;只构建不上传
arches 可选(新模板必备) 打印一行空格分隔的 ARCHES;无此目标视为存量 makefile,隐式单架构
push-deb 同时有多种发布轨道的项目 仅调 builder 的 upload_deb.sh。未指定 ARCH= 时上传 ARCHES 中每一项对应的 .deb;指定了则只上传那一个
push-docker 同时有多种发布轨道的项目 仅调 builder 的 publish_docker.sh
push-copy 同时有多种发布轨道的项目 仅调 builder 的 copy_artifact.sh;由它按配置选择内置拷贝或自定义脚本
push 单一产物类型时必备;多产物项目为聚合 依序调用对应 push-* 或直接调脚本;是发布的唯一 make 入口

规则:

  1. 项目有 DEB 产物的判据:makefile.builder 配方以独立命令调用 dpkg-deb/debuild 或产出 .deb; 不能把 xcodebuild 中包含的字符误判成 debuild。 有镜像的判据:项目根存在 Dockerfile。 Android 判据:配方产出 .apk / .aab。iOS 判据:配方产出 .ipa。 拷贝发布判据:配方调用 copy_artifact.sh。copy_artifact.sh 是稳定入口,具体分发可由 COPY_SCRIPT 接管。
  2. 多于一种发布轨道必须拆 push-deb / push-docker / push-copy,push 依序聚合; 单轨道项目一个 push 即可。
  3. docker 目标只能本地构建。多平台镜像无法拆成"make 构建 + 单独推送" (buildx --push 是一步),因此多平台发布只能走 publish_docker.sh。 未指定 ARCH= / PLATFORMS 时,publish_docker.sh 把 ARCHES 映射为 linux/<arch>,…。
  4. push 类目标必须是薄包装:解析脚本路径后委托,不内联 curl/token/端点/docker cp。
  5. build 保持单架构,供 deb 循环调用。不要让 build 自己遍历 ARCHES。 build 禁止 docker cp、copy_artifact.sh、curl/scp。

2. 变量

配置来源按变量的稳定性分层:makefile.builder 是项目构建与发布轨道的可迁移事实, 包括 ARCHES、产物命名、目标和项目内 COPY_SCRIPT;.pouch/builder/local.mk 是 不提交的机器工具链覆盖;.env.builder 是不提交的凭据、发布端点、目标目录和本轮别名。 命令行/环境变量可以覆盖前两者,但不应把项目契约依赖在本机环境上。

变量 默认 说明
ARCHES amd64 本项目要发布的架构清单,空格分隔,子集只能是 amd64 arm64。写在 makefile.builder 里并提交;不要放进 .env.builder。缺省或无 arches 目标时视为单架构 amd64。不要默认写成双架构
ARCH $(firstword $(ARCHES)) 这一次构建的架构。必须落在 ARCHES 里;非法值必须 $(error) 并提示合法值。命令行/环境传入时只构建/上传这一项
VERSION `` (空) 为空时按「版本号」节从 Git 祖先稳定 tag 推导;禁止 sort -V 取全局最新
DIST_DIR dist 产物目录(deb / apk / ipa)
PROJECT_NAME git 仓库名 包名/镜像名主体

ARCHES 是「这个项目发哪些架构」;ARCH 是「这一次编哪个」。宇宙集合仍是 amd64 | arm64,不开放其它 ISA。

deb 配方在打包前必须把 packaging/DEBIAN/control(或等价文件)的 Architecture: 写成当前 $(ARCH),不要写死 amd64。推荐先拷到 $(DIST_DIR)/packaging-$(ARCH) 再改,避免弄脏源树。

build 配方必须使用 $(ARCH)(例如 GOARCH=$(ARCH)、--platform=linux/$(ARCH))。配方若忽略 ARCH,双架构会打出两个文件名不同、内容却都是宿主机架构的包。Builder 不安装 qemu 或交叉编译器;看不出交叉编法时 ARCHES 只留本机一项。

版本号

make -f makefile.builder version 输出一行规范版本(无 v 前缀)。DEB 的 Version 与文件名直接用它;Docker tag 由它渲染。推导入口是 scripts/version.sh (makefile.builder 通过 scripts/version.mk 引用);Builder 只读取 Git 状态,不调用 releaser 选定下一正式 SemVer。测试号把祖先基线的 patch 机械 +1 作为排序键 (v1.4.2 之后 → 1.4.3~…),不是在宣布下一正式号就是 1.4.3。正式 tag 由 releaser 事先打好。version.sh 不执行 git fetch。

推导前 git fetch --tags(本地 linked worktree 共享 tags,不必再 fetch 才 能看见其它 worktree 新打的 tag)。基线是 HEAD 祖先上最近的稳定 tag, 不是全仓库 sort -V 的最大号。稳定 tag 仅 v<major>.<minor>.<patch>。

判定 规范版本 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.3~<date>.<branch>.<n>+g<sha> name_1.4.3~<date>.<branch>.<n>+g<sha>_<arch>.deb 1.4.3-<date>.<branch>.<n>.g<sha>
祖先中没有稳定 tag 0.0.0~<date>.<branch>+g<sha> 同上替换规范版本 同上映射

测试与正式进入同一 apt / Docker 仓库。测试 DEB 必须用 ~ 挂在基线的下一 patch 上:1.4.2 < 1.4.3~… < 1.4.3。已装上一正式版的机器可以升到 bugfix 测试包;同号正式版仍盖住测试。不要写成 1.4.2~…(小于已发布的 1.4.2),不要写成 1.4.2+<date> / 1.4.3-beta(Debian 里 -beta 大于 1.4.3)。日期必须写在 ~ 之后。Docker tag 不得含 ~、/、:,由规范 版本把 ~ → -、+g → .g。

<date>:YYYYMMDDTHHMM,构建的日期加分钟。加分钟是为了同一天重复构建 也能排序:只写到日期时,同分支同日的版本前缀完全相同,只剩 +g<sha> 可 区分,而 Debian 把哈希当普通文本比较,新构建可能排到旧构建下面( apt upgrade 因此看不到最新包)。已设 SOURCE_DATE_EPOCH 时用该时刻的 UTC 时间,否则用本机当前时间。正式 X.Y.Z 不加日期。切换格式当天注意: Debian 把字母排在标点之前,YYYYMMDDT… 小于旧格式的 YYYYMMDD.…,同日 已上传的旧格式包会压住新包,需要把它们在仓库里移除或停用;次日恢复正常。 <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)。 <n> 为基线 tag 到 HEAD 的 commit 数;<sha> 为 7 位短哈希。不要把 --dirty 写入版本;脏树发布仍走既有门禁。显式 VERSION= / IMAGE_TAG= 可覆盖推导,但不得把非 exact-match 的 commit 标成正式 X.Y.Z。

makefile.builder 不要内联 git describe 或 sort -V,include 本 skill 的 scripts/version.mk:

include $(HOME)/.pouch/skills/builder/scripts/version.mk

3. 发布环境变量

DEB 轨道

变量 必填 说明
DEB_SERVER_URL 是 仓库服务地址
DEB_TOKEN 是 认证令牌;只从环境读取,绝不进 argv/日志/git
DEB_REPOSITORY 是 目标仓库名
DEB_UPLOAD_PATH 否 覆盖默认上传路径 /api/v2/upload/package

拷贝轨道

变量 必填 说明
COPY_DEST 与 COPY_SCRIPT 至少配置一个 内置后备目标:本地目录,或 Docker 目标 container:/absolute/dir(docker cp)
COPY_SCRIPT 与 COPY_DEST 至少配置一个 项目内 .pouch/builder/ 脚本的路径优先写在 makefile.builder 并提交;也可用绝对路径或环境/.env.builder 覆盖;必须是可执行文件,同时配置时优先于 COPY_DEST
COPY_AS 否 第一份文件的额外 basename,逗号或空格分隔

copy_artifact.sh FILE [FILE ...] 是 APK/IPA 等产物的统一分发入口。不读 .env,脏工作树默认拒绝, ALLOW_UNCOMMITTED=1 放行。

未配置 COPY_SCRIPT 时,入口把文件拷到 COPY_DEST,校验目标存在后打印 SHA-256。 配置 COPY_SCRIPT=.pouch/builder/publish-apk.sh 时(项目内脚本通常由 makefile.builder 传入),入口按项目根解析、先校验每个文件存在,然后直接执行:

/absolute/path/to/publish-script FILE [FILE ...]

相对路径只允许位于项目的 .pouch/builder/ 下;绝对路径仍支持外部脚本。不经过 shell 字符串求值, 不把 token 放进参数。脚本通过位置参数接收原始产物绝对路径,自行负责重命名、上传、远端校验和通知; 当前项目根以 BUILDER_PROJECT_ROOT 环境变量提供。 脚本返回非零即视为分发失败;成功后入口只报告脚本成功和本地 SHA-256,不宣称远端已校验。 --dry-run 只打印将调用的脚本与文件,不执行自定义脚本。

Docker 轨道

变量 必填 说明
DOCKER_REGISTRY 是 registry 主机,无 scheme
DOCKER_REPOSITORY 否 默认取 git 仓库名
IMAGE_TAG 否 默认由规范版本渲染:正式为 X.Y.Z;测试将 ~ 换成 -、+g 换成 .g
PLATFORMS 否 本机/本轮覆盖。未设置时由 ARCHES 映射为 linux/<arch>,…;再没有则 linux/amd64。不要把项目的架构清单只写在这里(.env.builder 不进 git)

PLATFORMS 优先级(高 → 低):用户这轮 --platform 或已 export 的 PLATFORMS → 命令行 ARCH= 映射的 linux/$(ARCH)(由 push-docker 传入)→ .env.builder 的 PLATFORMS → makefile.builder 的 ARCHES → linux/amd64。

配置来源优先级:shell 已显式设置的值 > 项目根 .env.builder > 失败并询问用户。 .env.builder 由 builder 脚本加载(不回显任何值);当前 shell 已设置的值优先。 不要读取或改写用户 .env。空值视为未配置。不要提交 .env.builder。

工作区安全

脏工作树(有未提交修改)默认拒绝发布;ALLOW_UNCOMMITTED=1 显式放行并在汇报中 注明镜像/包包含哪些未提交修改。该门在 builder 脚本层实现,不在 make 层。

4. 脚本解析顺序

push 目标与 version.sh 定位 builder 脚本时按以下顺序,命中即用,不做静默兜底:

  1. $BUILDER_SKILL_DIR/scripts/(特殊安装位置)
  2. $HOME/.pouch/skills/builder/scripts/(标准 clone 位)

两个位置都不可用时必须失败并提示:设置 BUILDER_SKILL_DIR,或把 pouch 仓库 clone 到 ~/.pouch。

5. 校验

python3 -I -S <builder-scripts>/check.py <project-dir> [--build] 对本项目逐条检查 上述要求,任一 FAIL 退出码非零,可直接挂 CI。--build 额外实构 make -f makefile.builder deb 并核对产物元数据(默认只静态检查配方)。--ready 额外检查轨道工具链,以及 .env.builder / 环境中的发布键名是否存在且非空(不读取、 不打印值;缺键只挡住发布)。校验失败时的修复路径:按 templates/makefile.builder 补齐或 修正 makefile.builder,再跑 check.py,不要绕过校验器,不要改用户 Makefile, 不要用 create-makefile(版本推导与本契约冲突)。

6. 存量项目(legacy fallback)

未接入契约的项目:builder 仍可按发现流程工作——从用户 Makefile、CI 配置、debian/ 与发布文档中找已有构建/上传入口,优先复用;上传仍使用 builder 脚本。从项目根查找, 不预设文件位置:

rg -n -i --hidden --glob '!.git' \
  'build-deb|upload-deb|publish-deb|dpkg-deb|debuild|curl.*deb|\.deb\b|aptly|reprepro'

完成一次成功交付后应引导用户按 templates/makefile.builder 写入项目根 makefile.builder,之后以 check.py 为准。不把契约目标合并进用户 Makefile。