mcpp-release · git:20260731.f4a7568 · 2026-07-31 · sha256 7c36ed5095d00469
mcpp-release git:20260731.f4a7568A
Immutable. This exact content is served forever at /api/v1/blob/7c36ed5095d00469.
--- name: mcpp-release description: Use when releasing a new version of mcpp — bumps version, creates tag, triggers release CI, and monitors until all platforms succeed. Covers the full release checklist to avoid common pitfalls like version string mismatches. --- # mcpp 版本发布流程 ## 版本号规范 **格式:`YYYY.M.D.N`**(日期版本,月/日不补零),例如 `2026.7.27.1`。 自 `2026.7.27.1` 起启用,此前为 `0.0.x`。与 xlings 生态一致(xlings 于同日从 `0.4.70` 迁入)。 第 4 段的语义: > **`.0` 保留给正式版本 / 稳定版本。日常迭代默认从 `.1` 开始。** 即一天内可发 `.1`、`.2`、`.3` …… 若干次常规版本;`.0` 只在该版本被认定为正式 release 或稳定版时使用。 跨方案的序是单调的:`0.0.109` < `2026.7.27.1`,第一段从 `0` 变 `2026`,不存在回退。 > 比较逻辑见 `src/version_req.cppm`。它支持 4 段;**改动那里时务必保证 `str()` 精确回写**, > `src/pm/resolver.cppm` 用它重建依赖版本串,会流向 lock 文件与 xlings wire 地址。 > 尤其 `.0` 结尾的版本不能塌成三段。 ## Overview mcpp 的版本号存在于 **四个位置**,但它们分属**两组,在发布流程的两个不同时刻更新**。 把四处一起 bump 是一个会让全部 CI 变红的经典错误 —— 见下面第二组的解释。 **第一组:正在构建的版本**(发布时改,走 bump PR) 1. `mcpp.toml` → `[package].version` — 构建系统读取的项目版本,release.yml 由它推导 tag 2. `src/toolchain/fingerprint.cppm` → `MCPP_VERSION` — 编译期硬编码常量(`--version` 输出、BMI 指纹、E0006 索引底线比较) 这两处必须**在同一个 commit 里**一起改:`tests/e2e/01_help_and_version.sh` 交叉比对 `mcpp.toml` 与 `mcpp --version`,只改一处 CI 立刻红。 **第二组:bootstrap pin —— CI 用哪个 mcpp 来自举**(发布并进索引之后才改) 3. `.xlings.json` → `workspace.mcpp` — CI bootstrap 装哪个 mcpp 4. `.github/workflows/ci-fresh-install.yml` → `MCPP_PIN` — 全新安装验证的目标版本 这两处指向的是一个**已经发布、且已经进了索引**的版本。在 bump PR 里把它们一起挪到新版, 等于让每一个 CI job 去 `xlings install` 一个还不存在的 mcpp —— 全线红。 所以它们在 bump PR 里保持**上一个已发布版本**不动,直到发布收尾那一步才前移 (见「发布后的收尾」第 3 步)。`check_version_pins.sh` 正是按这个语义校验的:它只要求 两处 pin **彼此相等**、且**不得新于**正在构建的版本,并不要求等于它。 对照最近一次发布:`fd27314`(bump 到 2026.7.29.1)只动了第一组两个文件,第二组仍停在 2026.7.28.2;`fde3b70` 才在发布、镜像、进索引之后把 pin 推到 2026.7.29.1。 **版本不一致会导致 release smoke test 失败**(CI 检查 `mcpp --version` 是否匹配 tag)。 第二组历史上多次漂移(`MCPP_PIN` 曾落后五个版本),所以现在有机器校验: ```bash bash .github/tools/check_version_pins.sh ``` 它同时校验第二组不变量:**`.github/` 下所有 xlings pin 必须等于 `src/xlings.cppm` 的 `pinned::kXlingsVersion`**(当前 16 个 pin 点、7 个文件,含 release.yml 里三处硬编码的 aarch64 tarball 字面量)。`kXlingsVersion` 是唯一真源,也是 release 打进 `<install>/registry/bin/xlings` 的那一份。改 xlings 版本只改常量,然后跑这个脚本找出其余落点。 ## 发布步骤 ### 1. 确认 main 分支状态 ```bash git checkout main && git pull origin main # 确认 CI 全部通过 gh run list --branch main --limit 3 ``` 所有 CI(ci / ci-macos / ci-windows)必须为 `success`。不要在 CI 红的时候发版。 ### 2. bump 版本号(第一组两处,单个 commit,走 PR) **只改第一组的两个文件**,并且在同一个 commit 里。bootstrap pin(`.xlings.json`、 `MCPP_PIN`)**不要动** —— 它们指向上一个已发布版本,见 Overview。 ```bash # 日期版本:当天序号从 .1 起;.0 仅用于正式/稳定版 NEW_VERSION="2026.7.27.1" git checkout -b "chore/bump-$NEW_VERSION" sed -i "s/^version.*=.*/version = \"$NEW_VERSION\"/" mcpp.toml sed -i "s/MCPP_VERSION = \".*\"/MCPP_VERSION = \"$NEW_VERSION\"/" src/toolchain/fingerprint.cppm # 机器校验(building 是新版、bootstrap pin 仍是旧版,是预期状态) bash .github/tools/check_version_pins.sh # 自查:构建产物真的报新版本。注意 target/ 目录名带指纹哈希, # 版本一变就是新目录 —— 用 `ls -dt` 取最新的那个,`head -1` 会拿到旧二进制。 mcpp build && "$(ls -dt target/*/*/bin/mcpp | head -1)" --version git commit -am "chore: bump version to $NEW_VERSION" git push -u origin "chore/bump-$NEW_VERSION" gh pr create --title "chore: bump version to $NEW_VERSION" --body "..." # CI 绿后合入;版本 bump 同样禁止直推 main(见 mcpp-contributing) ``` ### 3. 创建并推送 tag ```bash git tag "v$NEW_VERSION" git push origin "v$NEW_VERSION" ``` Tag push 会自动触发 `release.yml` workflow。 ### 4. 监控 Release CI Release workflow 包含**四个平台**的构建,外加一个生态发布 job: | Job | 平台 | 产物 | 依赖 | |-----|------|------|------| | `build-release` | Linux x86_64 | `mcpp-X.Y.Z-linux-x86_64.tar.gz` | 无(先执行) | | `build-linux-aarch64` | Linux aarch64(交叉) | `mcpp-X.Y.Z-linux-aarch64.tar.gz` | 等 Linux x86_64 完成 | | `build-macos` | macOS ARM64 | `mcpp-X.Y.Z-macosx-arm64.tar.gz` | 等 Linux 完成 | | `build-windows` | Windows x86_64 | `mcpp-X.Y.Z-windows-x86_64.zip` | 等 Linux 完成 | | `publish-ecosystem` | — | 镜像到 xlings-res 双端 + 开索引 bump PR | 等**全部四个**构建完成 | `build-linux-aarch64` 是两段式的(bootstrap 先构出本 release 的 x86_64 mcpp,再用它交叉构建 aarch64),因为 bootstrap 装的是**上一个已发布版本**,可能不认新特性。 ```bash # 监控 release workflow gh run list --workflow release.yml --limit 1 # 查看详细步骤状态 gh run view <run-id> # 如果失败,下载日志分析 gh api repos/mcpp-community/mcpp/actions/runs/<run-id>/logs \ -H "Accept: application/vnd.github+json" > /tmp/release-logs.zip unzip -p /tmp/release-logs.zip "build + upload (linux _ x86_64)/8_Smoke-test the bundled tarball.txt" ``` ### 5. 验证 Release 产物 ```bash gh release view "v$NEW_VERSION" ``` 确认以下产物全部存在: - `mcpp-X.Y.Z-linux-x86_64.tar.gz` + `.sha256` - `mcpp-X.Y.Z-linux-aarch64.tar.gz` + `.sha256` - `mcpp-X.Y.Z-macosx-arm64.tar.gz` + `.sha256` - `mcpp-X.Y.Z-windows-x86_64.zip` + `.sha256` - 上述四个的**无版本号别名**(`mcpp-linux-x86_64.tar.gz` 等)+ `.sha256` - `mcpp-X.Y.Z.tar.gz`(源码包) - `mcpp.lua`(xpkg 描述) - `install.sh` - `SHA256SUMS` **顺带核对体积**(2026.7.29.1 起,见下方"载荷瘦身"):linux 两个 tarball 应在 **5MB 上下**。如果又回到 30MB 量级,说明 strip 断言被绕过了,先查再发。 ## Release CI 详解 ### Smoke Test 检查项 每个平台的 smoke test 验证: 1. 二进制可执行 (`test -x`) 2. Linux: 静态链接 (`file ... | grep 'statically linked'`) 3. `mcpp --version` 输出包含版本号 4. `mcpp --help` 正常输出 5. Linux: `mcpp self env` 中 MCPP_HOME 正确解析 6. xlings 二进制已捆绑 ### 载荷瘦身(2026.7.29.1 起) 每个 linux 平台在**打包后、打 tar 前**调用 `.github/tools/slim_linux_payload.sh`, strip `bin/mcpp` 与 `registry/bin/xlings` 并**断言结果**(`file` 不得再含 `not stripped`)。 为什么必须断言:在此之前,vendored 的 xlings 从来没被 strip 过(97.3MB,带 `debug_info`),而 x86_64 那句 `strip` 跑在 `mcpp pack` **之前** —— pack 会重建 二进制把它覆盖掉,于是直到 2026.7.28.2 发布的 `bin/mcpp` 一直是未 strip 的。 一个不校验效果的 `strip` 等于注释。修完 linux-x86_64 tarball 从 **34.81MB 降到 4.62MB(7.5×)**。 macOS / Windows **故意不做**:载荷本来就 6.1MB / 4.2MB,且 strip Mach-O 会让 ad-hoc 签名失效。 ### publish-ecosystem:镜像 + 索引(发布的后半程) 四个构建 job 全绿后自动执行,做两件事: 1. **镜像到 `xlings-res/mcpp` 双端**(GitHub + GitCode),由 `.github/tools/mirror_res.sh` 完成 —— 单 leg 内资产**并发上传** (`MIRROR_MAX_PARALLEL`,默认 8),预算是**整条 leg 的 deadline** (`MIRROR_LEG_DEADLINE_GH` 600s / `_GTC` 2400s),不是 per-asset cap。 2. **向 `openxlings/xim-pkgindex` 开 bump PR**(带每平台 sha256)。 **为什么不是 per-asset cap**:实测(探针 PR #301)GitHub US runner 上传到 `file.gitcode.com`(单 IP 华为云北京)只有 **0.012 MB/s** —— 而同一台 runner 从同一个 IP **下载**有 3.87 MB/s、传 GitHub 有 16 MB/s、大陆本机传它有 1.84 MB/s。被限的是**国际入境方向**,且速率有 ~4.6× 抖动,所以任何固定 per-asset 值都不可能既安全又有用。限速是 **per-connection** 的(1/4/8 并发 = 76/80/93s 墙钟),所以并发能叠加;但预签名是 OBS **单次 PUT** 签名,无 multipart/无断点续传,**单文件拆不开** —— 这正是必须先把载荷 strip 小的原因。 **`gtc` 的退出码两个方向都会撒谎**:PUT 头里的 `x-obs-callback` 让 OBS 存完对象 再回调 GitCode API,回调失败就返回 `code:400 ... EOF`,而**对象其实已落盘**。 判定上传成功**只能靠回探下载 URL**,脚本就是这么做的。 ### 发布后的收尾(必须做完,否则用户装不到) ```bash # 1) 索引 PR:CI 绿后合入,合入即自动发布索引 artifact gh pr merge <n> --repo openxlings/xim-pkgindex --squash --admin # 2) 真实验证(注意:不带 @版本 不会升级已装的旧版) xlings update && xlings install mcpp@$NEW_VERSION -y # 3) bootstrap pin 收尾 —— 第二组两处,到这一步才前移 # 新版此时已发布、已镜像、已进索引,CI 装得到,pin 才可以指向它 sed -i "s/\"mcpp\": \"[^\"]*\"/\"mcpp\": \"$NEW_VERSION\"/" .xlings.json sed -i "s/MCPP_PIN: '[^']*'/MCPP_PIN: '$NEW_VERSION'/" .github/workflows/ci-fresh-install.yml bash .github/tools/check_version_pins.sh git commit -am "ci: workspace mcpp bootstrap pin -> $NEW_VERSION (released, mirrored, indexed)" ``` **顺序不能反**:pin 一旦领先于"索引里真实存在的版本",每个 CI job 的 bootstrap 都会 `package 'mcpp@X.Y.Z' not found`。这就是 bump PR 里不许碰这两处的原因。 **索引传播有滞后**:索引 artifact 发布后,`latest` tag 上的指针文件在 GitHub 资产 CDN 上可能还要几分钟才更新。紧接着跑的 CI 可能仍拿到旧索引并报 `package 'mcpp@X.Y.Z' not found` —— 这不是 release 坏了,等指针稳定后重跑即可。 ### 下游分发渠道(都是自动的,只需核验) 两条渠道都挂在 `release` workflow 的 `workflow_run: completed` 上,不需要人工推: | 渠道 | workflow | 核验 | |------|----------|------| | AUR (`mcpp-bin` / `mcpp-m`) | `.github/workflows/aur-publish.yml` | `gh run list --workflow aur-publish.yml --limit 1` | | Homebrew tap (`mcpp-m`) | `.github/workflows/homebrew-publish.yml` → ping [`mcpp-community/homebrew-mcpp`](https://github.com/mcpp-community/homebrew-mcpp) | `gh api repos/mcpp-community/homebrew-mcpp/contents/Formula/mcpp-m.rb --jq .content \| base64 -d \| grep '^ version'` | Homebrew 那条**不写公式**,只发一个 `repository_dispatch`;公式重写由 tap 仓库自己的 `bump-formula.yml` 完成(它读 release 的 `.sha256` 边车)。这条 ping 依赖仓库 secret `HOMEBREW_TAP_TOKEN`;**没配也不会让发布失败** —— tap 有每日 schedule 兜底,24h 内自己跟上。 想立刻跟上就手动触发一次: ```bash gh workflow run bump-formula.yml -R mcpp-community/homebrew-mcpp ``` ### 常见失败原因 | 症状 | 原因 | 修复 | |------|------|------| | `mcpp X.Y.Z-1` 但 tag 是 `vX.Y.Z` | `fingerprint.cppm` 版本未更新 | 更新 `MCPP_VERSION`,重新打 tag | | bump PR 里**所有** CI job 都红在 bootstrap,报 `package 'mcpp@X.Y.Z' not found` | 把第二组的 bootstrap pin 也一起 bump 了,CI 去装一个还没发布的版本 | 把 `.xlings.json` / `MCPP_PIN` 回退到上一个已发布版本,发布收尾时再前移 | | 自查 `--version` 显示旧版本,但源码已改 | `target/<triple>/<指纹>/` 的指纹随版本变,`ls \| head -1` 取到了上一次构建的目录 | 用 `ls -dt … \| head -1` 取最新构建 | | Smoke test 输出旧版本 | CI 缓存了旧的 sandbox/target | 删除 GitHub Actions cache 后重跑 | | e2e `01_help_and_version.sh` 挂 | 只改了 `mcpp.toml` 没改 `fingerprint.cppm`(它把两者交叉比对) | 同步四处版本;注意这个 e2e 只在部分分片里跑,可能表现为"只有某个平台红" | | xlings bootstrap 失败 | xlings 版本不兼容 | 改 `src/xlings.cppm::kXlingsVersion`(**唯一真源**)后跑 `check_version_pins.sh` 找出其余 15 个 pin 点 | | macOS/Windows 构建失败 | 需要等 Linux job 先完成 | 检查 Linux job 是否成功 | | `slim: FAIL: ... still not stripped` | strip 工具没生效/被 pack 覆盖 | 别绕过断言——它就是为了拦住 34.8MB 的 tarball 再次发出去 | | mirror leg 报 `missing/unverified` | 资产没传上去或还没传播 | 先 GET 核验(**必须 GET,`curl -I` 会骗你**),gitcode 用 `gitcode.com` 直链而非 `api.` 主机;确认缺件后本地补传再 `gh run rerun --failed`(脚本幂等,已验证的资产会跳过) | ### 缓存管理 Release CI 使用多层缓存加速。如果怀疑缓存问题: ```bash # 列出缓存 gh cache list | grep release # 删除特定缓存 gh cache delete <cache-id> # 重跑 release(不改 tag) gh workflow run release.yml --ref "v$NEW_VERSION" ``` ## 紧急修复发版 如果发版后发现问题需要紧急修复: ```bash # 1. 在 main 上修复 git checkout main && git pull # ... 修改代码 ... git commit -m "fix: 描述" git push origin main # 2. 更新 tag 指向新 commit(包含修复) git tag -d "v$NEW_VERSION" git tag "v$NEW_VERSION" git push origin "v$NEW_VERSION" --force # 3. 删除旧 release(如果已创建) gh release delete "v$NEW_VERSION" --yes # 4. 重跑 release gh workflow run release.yml --ref "v$NEW_VERSION" ``` **注意**:如果修复涉及版本号变化,应该 bump 到新的 patch 版本而不是覆盖旧 tag。 ## 文件清单 | 文件 | 版本相关内容 | |------|-------------| | `mcpp.toml` | `version = "X.Y.Z"` — 项目版本,release.yml 由它推导 tag | | `src/toolchain/fingerprint.cppm` | `MCPP_VERSION = "X.Y.Z"` — 编译期版本常量 | | `.xlings.json` | `workspace.mcpp` — CI bootstrap 装哪个 mcpp(发布**后**才 bump) | | `.github/workflows/ci-fresh-install.yml` | `MCPP_PIN` — 全新安装验证目标(发布**后**才 bump) | | `src/xlings.cppm` | `kXlingsVersion` — xlings pin 的**唯一真源**(其余 15 处由脚本校验) | | `.github/tools/check_version_pins.sh` | 机器校验上述两组不变量,别靠肉眼 | | `.github/tools/slim_linux_payload.sh` | linux 载荷 strip + 断言 | | `.github/tools/mirror_res.sh` | 双端镜像(并发上传 + leg deadline + 完整性 gate) | | `.github/tools/gtc` | GitCode CLI(release create/upload、PR) | | `.github/workflows/release.yml` | Release workflow 定义(四平台 + publish-ecosystem) | | `install.sh` | 安装脚本(随 release 发布) | | `CHANGELOG.md` | Release notes 来源(按 `## [X.Y.Z]` 提取) | > **注意版本 bump 的两个阶段**:`mcpp.toml` + `fingerprint.cppm` 在发版**前**改 > (它们定义要发什么);`.xlings.json` + `MCPP_PIN` 在发版**成功后**改(它们指向 > bootstrap 用哪个已发布版本)。`check_version_pins.sh` 认得这个差异,不会因为 > bootstrap pin 落后一版就报错。