227 lines
11 KiB
Markdown
227 lines
11 KiB
Markdown
# builder
|
||
|
||
按统一契约完成项目的 DEB 包、Docker 镜像、Android APK / iOS IPA 构建和发布,
|
||
或把产物拷到目录 / 容器。规范本体见
|
||
[references/contract.md](references/contract.md),`scripts/check.py` 是契约的
|
||
可执行校验器。
|
||
|
||
构建执行位置使用共享 `.pouch/agent.yaml` 的任意 `builder.<name>` binding 配置;本地
|
||
Android、远程 Mac iOS 或未来其它平台都可以各自绑定 environment/project/path/profile。
|
||
|
||
## 什么时候使用
|
||
|
||
- "用 builder 初始化这个项目"
|
||
- "帮我构建这个项目的 DEB / Docker 镜像 / APK"
|
||
- "把 1.2.3 发布到包仓库 / 镜像仓库 / File Browser"
|
||
- "检查这个项目的 makefile.builder 是否符合 builder 契约"
|
||
- "看看项目现在的发布流程"
|
||
|
||
只构建不上传时明确说明即可;上传永远需要你显式授权。
|
||
|
||
## 安装
|
||
|
||
```bash
|
||
pouch add builder # 当前项目
|
||
pouch add builder -g # 全局
|
||
```
|
||
|
||
不要 `pouch init builder`。对新项目说「用 builder 初始化」。
|
||
|
||
## 使用前准备
|
||
|
||
- Python 3 和 `make`。
|
||
- DEB 轨道需要 `dpkg-deb`;Docker 轨道需要 Docker 和 buildx。
|
||
- 发布时在项目根 `.env.builder` 填写仓库地址和 token(不要写进 `.env`,也不要进 git)。
|
||
- 正式版本号由 [releaser](../releaser/README.md) 打 annotated tag;builder 只读当前 HEAD。
|
||
- 脏工作树默认拒绝发布。
|
||
|
||
## 项目接入契约
|
||
|
||
对新项目说「用 builder 初始化」。Agent 会探测轨道、按
|
||
`templates/makefile.builder` 写出项目根 `makefile.builder`,并列出缺的发布配置。
|
||
不改用户已有的 `Makefile` 或 `.env`。
|
||
|
||
配置跟随 `makefile.builder` 所在的 Git 根。如果 APK/IPA 位于 Git submodule,
|
||
就在 submodule 内配置 `.env.builder` 和 `.pouch/builder/`;父仓库只固定 submodule commit
|
||
并负责上层编排。
|
||
|
||
1. `makefile.builder` 目标:`help/build/clean/version` + 条件 `deb/docker/push*`,
|
||
变量 `ARCHES/ARCH/VERSION/DIST_DIR/PROJECT_NAME`。
|
||
`VERSION` 通过 `include <builder>/scripts/version.mk` 从 Git 推导。
|
||
多架构清单是 `ARCHES`,见下方「多架构」。
|
||
2. 运行 `python3 -I -S <builder>/scripts/check.py . --ready` 直到构建项 PASS。
|
||
缺发布键只挡住上传,不挡住构建。
|
||
3. 在项目根 `.env.builder` 配置发布环境变量(不要写进 `.env`)。项目内 APK/IPA
|
||
分发脚本的路径写在 `makefile.builder` 的 `COPY_SCRIPT`,脚本接收产物路径作为位置参数;
|
||
未配置项目脚本时使用 `COPY_DEST` 的本地目录或容器目录后备逻辑:
|
||
|
||
```text
|
||
DEB_SERVER_URL=https://deb.example.com
|
||
DEB_REPOSITORY=main
|
||
DEB_TOKEN=<token> # 只放 .env.builder 或密钥系统,不进 git
|
||
DOCKER_REGISTRY=registry.example.com
|
||
COPY_DEST=file-server:/srv
|
||
COPY_AS=latest.apk
|
||
# COPY_SCRIPT= # 仅外部/临时脚本覆盖;项目内脚本写 makefile.builder
|
||
```
|
||
|
||
4. 日常发布就是两条命令:
|
||
`make -f makefile.builder deb && make -f makefile.builder push-deb`、
|
||
`make -f makefile.builder push-docker`。
|
||
写了 `ARCHES=amd64 arm64` 时,这两条会打出并上传两个 DEB;镜像一次推多平台。
|
||
|
||
跨机器或需要继续查看的构建先用 `agent-runtime` 精确解析 binding 和 Orca worktree;
|
||
builder 只在授权后执行对应 `makefile.builder` target,不自动上传。
|
||
|
||
## 多架构
|
||
|
||
项目要发哪些 CPU 架构,写在**已提交的** `makefile.builder` 里,不要写进
|
||
`.env.builder`(那是密钥,换机器会丢)。
|
||
|
||
```makefile
|
||
ARCHES ?= amd64 arm64
|
||
```
|
||
|
||
`ARCH` 是「这一次」:默认等于清单第一项。不传 `ARCH=` 时,`deb` / `push-deb`
|
||
按清单逐个做完;`push-docker` 把清单映射成 `linux/amd64,linux/arm64`。
|
||
只做其中一个:`make -f makefile.builder deb ARCH=arm64`。
|
||
|
||
合法值只有 `amd64` 和 `arm64`(空格分隔)。不要默认写成双架构:本机编不出
|
||
另一边时,只留一项。
|
||
|
||
```bash
|
||
make -s -f makefile.builder arches # 打印清单,例如 amd64 arm64
|
||
make -f makefile.builder deb # 打出两个 .deb
|
||
make -f makefile.builder push-deb # 两个都上传(需你授权发布)
|
||
make -f makefile.builder push-docker # 一次 buildx 推两个平台
|
||
```
|
||
|
||
产物:
|
||
|
||
```text
|
||
dist/<name>_<version>_amd64.deb
|
||
dist/<name>_<version>_arm64.deb
|
||
```
|
||
|
||
`build` 配方必须用 `$(ARCH)`,否则两个包内容会一样。初始化时 Agent 按仓库里
|
||
已有命令来写(Go 一般是 `GOOS=linux GOARCH=$(ARCH) go build …`),不另做一套
|
||
运行时探测脚本。看不出交叉编法就不要写双架构。
|
||
|
||
`packaging/DEBIAN/control` 的 `Architecture:` 必须随 `$(ARCH)` 变,不要写死
|
||
`amd64`。模板会先拷到 `dist/packaging-$(ARCH)` 再改这一行。
|
||
|
||
本机临时只推一个平台,可以在 `.env.builder` 设 `PLATFORMS=linux/amd64`,这是
|
||
覆盖不是清单。项目清单始终以 `ARCHES` 为准。
|
||
|
||
存量 `makefile.builder` 没有 `ARCHES` 时行为与以前相同(默认 `amd64`,
|
||
`ARCH=arm64` 仍合法)。要改成双架构:加上面那一行,确认 `build` 吃 `$(ARCH)`,
|
||
再跑 `python3 -I -S <builder>/scripts/check.py . --ready`。
|
||
|
||
## 版本号
|
||
|
||
产物版本从 **当前 HEAD 的 Git 祖先** 推导,不调用 releaser。
|
||
正式 tag 仍由 releaser 创建;builder 只读取。完整规则见
|
||
[contract.md「版本号」](references/contract.md#版本号)。
|
||
|
||
| 谁 | 做什么 |
|
||
| --- | --- |
|
||
| releaser | 选定下一个正式 SemVer,打 annotated tag `vX.Y.Z` |
|
||
| builder | 读 HEAD:落在稳定 tag 上则打正式产物,否则打测试产物 |
|
||
| `make -f makefile.builder version` | 输出一行规范版本(无 `v` 前缀),DEB / Docker 都从它渲染 |
|
||
|
||
**不要**用 `git tag \| sort -V \| tail -1` 取全仓库最大号,也不要把
|
||
`git describe --dirty` 的原始字符串写进 DEB 或镜像 tag。
|
||
|
||
### 正式(HEAD 恰好是稳定 tag `v1.4.2`)
|
||
|
||
```text
|
||
Git tag v1.4.2
|
||
规范版本 1.4.2
|
||
DEB foo_1.4.2_amd64.deb Version: 1.4.2
|
||
Docker registry/ns/foo:1.4.2
|
||
```
|
||
|
||
稳定 tag 仅匹配 `v<major>.<minor>.<patch>`,不含 `-rc`、`-app-N` 等后缀。
|
||
不是 exact-match 就不是正式包。
|
||
|
||
### 测试(其它任何 commit)
|
||
|
||
基线 = 祖先上最近的那颗稳定 tag(没有则为 `0.0.0`)。有基线时测试号把
|
||
patch 机械 +1,再加构建时间 `YYYYMMDDTHHMM`、清洗后的分支名、相对距离、短 SHA。
|
||
这是排序键,不是在选定下一正式 SemVer。测试与正式进**同一个** apt /
|
||
Docker 仓库,所以 DEB 必须用 `~` 挂在下一 patch 上:`1.4.2` <
|
||
`1.4.3~20260918T1430…` < `1.4.3`。已装上一正式版的机器可以升到 bugfix 测试包;
|
||
同号正式版仍盖住测试。
|
||
|
||
分支 `feat/login-v2`,相对 `v1.4.2` 第 7 个 commit,SHA `abc1234`,
|
||
构建时间 `20260918T1430`(带分钟,同一天重建才排得对):
|
||
|
||
```text
|
||
规范 / DEB 1.4.3~20260918T1430.feat-login-v2.7+gabc1234
|
||
文件名 foo_1.4.3~20260918T1430.feat-login-v2.7+gabc1234_amd64.deb
|
||
Docker registry/ns/foo:1.4.3-20260918T1430.feat-login-v2.7.gabc1234
|
||
```
|
||
|
||
Docker tag 由规范版本映射:`~` → `-`,`+g` → `.g`(Docker 不允许 `~`)。
|
||
无参 `apt upgrade` 会从上一正式版升到下一 patch 的测试包,但不会盖住同号
|
||
正式包。生产若不能自动吃到测试包,应拆仓库/组件。构建时间取本机当前本地
|
||
时间(含分钟);设了 `SOURCE_DATE_EPOCH` 则用该时刻的 UTC 时间。`+g<sha>`
|
||
只用于记录 commit、不参与排序:只写到日期时,同一天重复构建的版本只差
|
||
`+g<sha>`,Debian 版本比较把哈希当普通文本比,会把新包判成比旧包小。
|
||
注意切换格式当天:Debian 把字母排在标点之前,`20260918T1430…` 小于旧格式
|
||
的 `20260918.…`,同一天已上传的旧格式包仍会压住新包,需要在仓库里把那些
|
||
旧包移除或停用;从第二天起新格式始终更大。
|
||
|
||
### 分支名清洗
|
||
|
||
小写;`/`、`_` 改为 `-`;去掉其它非法字符;压缩连续 `-`;过长截断。
|
||
detached HEAD 用 `detached`,CI 可注入 `BUILD_BRANCH` / `CI_COMMIT_BRANCH` /
|
||
`GITHUB_REF_NAME`。脏工作树不把 `-dirty` 写进版本(发布本身会拒绝)。
|
||
|
||
## 使用示例
|
||
|
||
```text
|
||
用 builder 初始化这个项目。
|
||
用 builder 检查这个项目的 makefile.builder 是否符合契约。
|
||
用 builder 构建当前版本的 DEB 和镜像,先不要上传。
|
||
用 builder 把当前 commit 的产物发布到项目已配置的仓库。
|
||
用 builder 发布多平台 linux/amd64,linux/arm64 镜像。
|
||
用 builder 把当前版本的 amd64 和 arm64 DEB 打出来并发布。
|
||
用 builder 打一份测试 APK 并拷到 File Browser。
|
||
```
|
||
|
||
## Agent 会做什么
|
||
|
||
1. 初始化:探测 DEB / Docker / Android / iOS / copy 轨道,写出 `makefile.builder` 和 `.env.builder` 骨架,按仓库证据填 `ARCHES`(不默认双架构),不改你已有的 `Makefile` 或 `.env`。
|
||
2. 检查:只跑 `scripts/check.py --ready`,不改文件。
|
||
3. 构建:走 `make -f makefile.builder`;只获构建授权时不走会自动上传的目标。
|
||
4. 发布:先校验产物,再上传。缺发布键、未授权、或可能覆盖同版本时停下问你。
|
||
|
||
Docker 只有你点名镜像时才走。token 不会写进输出、提交或回复。
|
||
|
||
## 如何判断完成
|
||
|
||
- 初始化:`check.py --ready` 的构建项 PASS。只缺发布键算部分完成,仍可构建、不能上传。
|
||
- 构建:产物路径、规范版本、架构与 `make -f makefile.builder version` 一致。
|
||
- 发布:HTTP 成功或远端 digest 对得上;回复里有包名或镜像引用、SHA-256 / digest、源 commit。
|
||
- 测试包不会通过无参 `apt upgrade` 盖住同号正式包;会作为下一 patch 候选盖住上一正式包。
|
||
|
||
## 脚本一览
|
||
|
||
| 脚本 | 用途 |
|
||
|------|------|
|
||
| `scripts/check.py` | 校验契约(`--ready` 含工具链与发布键名,`--build` 实构核对产物) |
|
||
| `templates/makefile.builder` | 初始化用的契约文件骨架,拷到项目根 |
|
||
| `templates/env.builder` | 初始化用的 `.env.builder` 骨架(注释键,不含值) |
|
||
| `scripts/version.sh` | 从 Git 祖先稳定 tag 推导规范版本 / Docker tag |
|
||
| `scripts/version.mk` | `makefile.builder` `include`,设置 `VERSION` 与 `IMAGE_TAG` |
|
||
| `scripts/upload_deb.sh` | 上传 `.deb` 到 HTTP 包仓库(multipart package/token/repository_name) |
|
||
| `scripts/publish_docker.sh` | buildx 构建 + 推送镜像,远端 digest 验证 |
|
||
| `scripts/verify_deb.sh` | 核对包元数据、内容与 SHA-256 |
|
||
| `scripts/copy_artifact.sh` | 按 `COPY_SCRIPT` 分发产物,未配置时拷到 `COPY_DEST` |
|
||
| `templates/makefile.android.builder` | Android APK + copy 轨道骨架 |
|
||
| `templates/makefile.ios.builder` | iOS IPA + copy 轨道骨架 |
|
||
|
||
环境变量契约、脚本解析顺序(`$BUILDER_SKILL_DIR` → `~/.pouch/skills/builder/scripts/`)、
|
||
脏工作树策略等完整规则见 contract.md。
|