状态扫描:README 落后两批 —— ★ 而落后的正是「挡不挡上线」那一句 #14
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 门禁(2026-08-21 批 13,owner 拍板:GitHub Actions 跑**全量**) | |
| # | |
| # ★ ★ ★ **这里只有一条命令,而且与本机那条逐字相同**:`bash tests/m0/docker-run.sh`。 | |
| # 这不是省事,是判据纪律:CI 若自己拼一套「等价的」步骤,就会长出**第二套判据**, | |
| # 而两套判据迟早分家 —— 那时「CI 绿」与「门禁绿」是两件事, | |
| # 却没有任何东西会说出来。本仓库为这个形状栽过好几次。 | |
| # | |
| # ⚠ ⚠ **有意不写 `paths-ignore`。** 「只改文档就跳过 CI」看起来是白捡的省钱, | |
| # 实际是错的:`crates/fulcrum-config/tests/doc_contract.rs` 用 `include_str!` | |
| # 读 `docs/architecture/dsl-reference.md`,`crates/fulcrum/tests/supply_gates.rs` | |
| # 读根 `Cargo.toml`/`Cargo.lock`,而批 11 那道门还反向断言 | |
| # **已接线的能力不许出现在 `dsl-reference.md` 里**。 | |
| # ⇒ **一次纯文档改动是真的能把门禁弄红的**,跳过它就等于把这类缺陷放行, | |
| # 而现象是「CI 一直绿」。省钱要从别处省(见下面的 concurrency 与 timeout)。 | |
| # | |
| # ⚠ **本仓库是私有仓库 ⇒ Actions 分钟数是计费的。** 两条止血阀都在下面: | |
| # · `concurrency` + `cancel-in-progress`:连着推几次时,只有最后一次跑到底; | |
| # · `timeout-minutes`:卡死的任务最多烧这么多,而不是默认的 6 小时。 | |
| name: gate | |
| on: | |
| # ★ 只在 main 上跑:owner 的口径是「直接提交 main,不开分支不开 PR」。 | |
| push: | |
| branches: [main] | |
| # 手工触发,用于改完 workflow 自身之后重跑。 | |
| workflow_dispatch: | |
| concurrency: | |
| group: gate-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| gate: | |
| # ★ 钉住大版本,不用 `ubuntu-latest`:M1 的 systemd 场景依赖宿主机是 **cgroup v2**, | |
| # 而 `ubuntu-latest` 换代时不会有任何一行输出告诉我们环境变了。 | |
| # 与 G36 钉构建镜像、Dockerfile.systemd 钉 systemd 版本是同一条纪律。 | |
| runs-on: ubuntu-24.04 | |
| # 冷缓存下要整编 pingora fork + 全部依赖,再跑十四个场景。 | |
| # ⚠ 这个上限是**计费止血阀**,不是预期耗时;真实耗时见每次运行的记录。 | |
| timeout-minutes: 120 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # ★ ★ runner 上 `/` 只有二十几 GB 空余,而这里要放:rust 构建镜像、 | |
| # systemd 测试镜像、cargo registry、**release 模式的整个 target**。 | |
| # ⚠ 磁盘满的报错形态五花八门(链接器 OOM、docker 写层失败), | |
| # 而没有一条会说「磁盘满了」。先腾出来,比事后猜便宜。 | |
| - name: 腾出磁盘 | |
| run: | | |
| echo "── 清理前 ──"; df -h / | |
| sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc \ | |
| /usr/local/share/boost /usr/local/.ghcup "$AGENT_TOOLSDIRECTORY" || true | |
| echo "── 清理后 ──"; df -h / | |
| # ★ 每次都把环境打出来。§8 要求可复现,而可复现的第一步是**看得见跑的是什么**。 | |
| # ⚠ cgroup 那一行是判据相关的:M1 的 `ExitType=cgroup` 要求容器侧 systemd ≥ 250 | |
| # 且宿主机是 cgroup v2 统一层级。它变成 v1 的那天,M1 场景会以一种 | |
| # 看起来像产品缺陷的方式红。 | |
| - name: 环境快照 | |
| run: | | |
| echo "── docker ──"; docker version | |
| echo "── 内核 ──"; uname -a | |
| echo "── cgroup 层级 ──"; stat -fc %T /sys/fs/cgroup | |
| echo "── git 行尾配置(CRLF 门的前提)──" | |
| git config --get core.autocrlf || echo "core.autocrlf 未设(Linux 上的默认,正是要的)" | |
| # ── 构建缓存(owner 2026-08-21 拍板加)───────────────────────────── | |
| # | |
| # ★ 实测数字(本机量的):`fulcrum-cargo` 684MB、`fulcrum-target-*` **6.3GB** | |
| # (vendor 3.0G + debug 2.8G + release 580M);抽样压缩比 gzip -1 已有 2.8x, | |
| # zstd 更高 ⇒ 归档约 2GB 上下,装得进 GitHub 每仓 10GB 的缓存预算。 | |
| # ★ 冷缓存下一次全量 CI 实测 **21m44s**(32441478101,第一次全绿的那次)。 | |
| # | |
| # ⚠ ⚠ **缓存最坏的失效方式不是「没命中」,是「命中了一份不该用的」。** | |
| # 两道防线:① 键里带 `Dockerfile.build` 的哈希(工具链身份,连 C 工具链一起); | |
| # ② `tests/ci/cache.sh` 在归档里另存一份 meta,**灌回前逐字比对**, | |
| # 对不上就跳过并说明,绝不硬灌。 | |
| # | |
| # ★ **怀疑缓存的时候怎么办**:把下面键里的 `v1` 改成 `v2`,整片缓存立即作废。 | |
| - name: 取构建缓存 | |
| id: buildcache | |
| uses: actions/cache/restore@v4 | |
| with: | |
| path: ~/fulcrum-cache | |
| # ★ 精确键 = 工具链 + 两把锁。锁变了就换一份新缓存, | |
| # 而 restore-keys 让它仍然能从**同一套工具链**的上一份热起来(cargo 增量补差)。 | |
| key: fulcrum-buildcache-v1-${{ runner.os }}-${{ hashFiles('docker/Dockerfile.build') }}-${{ hashFiles('Cargo.lock', 'vendor/pingora/Cargo.lock') }} | |
| restore-keys: | | |
| fulcrum-buildcache-v1-${{ runner.os }}-${{ hashFiles('docker/Dockerfile.build') }}- | |
| - name: 把缓存灌回 docker 卷 | |
| run: bash tests/ci/cache.sh restore ~/fulcrum-cache | |
| # ★ ★ ★ 就这一条。与 AGENTS.md「Current validation」第 3 条、 | |
| # 与每份交接单里写的那条命令,**逐字相同**。退出码就是判据。 | |
| # ⚠ 绝不接管道:`| tee` / `| tail` 取回的是右边那个命令的退出码(本仓库栽过)。 | |
| - name: 全量门禁(十四场景) | |
| run: bash tests/m0/docker-run.sh | |
| # ★ `always()`:门禁红了也要存 —— 依赖照样是编好的,下一次能省下同样多的时间。 | |
| # ⚠ 而**精确命中时不重存**(`cache-hit != 'true'`):那 2GB 上传是纯浪费。 | |
| # ★ ★ ★ **诊断必须落到「日志之外」的地方。** 2026-08-21 连着两轮都栽在这一步上, | |
| # 而两轮的日志 blob **都取不回来**(`gh run view --log` 一直 404 BlobNotFound)—— | |
| # 第一轮是 runner 崩了没上传,第二轮明明成功却也取不到。 | |
| # 于是「加了 df 让下一轮自己说话」这个办法**本身就没生效**: | |
| # 话是说了,只是没人听得见。 | |
| # ⇒ 诊断写进文件并**当成 artifact 传上去**,那条路与日志是分开的。 | |
| # ⚠ ⚠ **精确命中时整步跳过。** 2026-08-21 实测(命中轮 32450933715 的逐步耗时): | |
| # 这一步花了 **368 秒**压出一个 1.7GB 的归档,而下面「存构建缓存」因为 | |
| # `cache-hit == 'true'` **被跳过** —— 也就是说那 6 分钟压出来的东西**原样扔掉了**。 | |
| # ★ 一步只在「它的产物会被用到」时才该跑;判据要往前挪到这里, | |
| # 而不是等到最后一步再去决定要不要用。 | |
| # | |
| # ★ 代价说在明处:命中时缓存**不刷新**。于是随着提交累积,缓存里那份 | |
| # target 相对 HEAD 会越来越旧,cargo 每轮补的差量慢慢变大。 | |
| # 这是有意的取舍 —— 哪天两把锁之一变了,键跟着变、自然重做一份新的。 | |
| - name: 导出构建缓存 | |
| id: dumpcache | |
| if: always() && steps.buildcache.outputs.cache-hit != 'true' | |
| run: | | |
| DIAG=~/cache-diag.txt | |
| : > "$DIAG" | |
| say() { echo "$@" | tee -a "$DIAG"; } | |
| say "── 导出前的磁盘 ──" | |
| df -h / 2>&1 | tee -a "$DIAG" | |
| say "── docker 占了多少 ──" | |
| docker system df 2>&1 | tee -a "$DIAG" || true | |
| # ★ 门禁已经跑完了,镜像这时候一个都不需要留:它们本来就不跨轮缓存, | |
| # 每一轮都要重建。搬运用的 helper 会被 cache.sh 重新拉(几十 MB)。 | |
| # ⚠ 这一步不影响任何判据,纯粹是腾地方。 | |
| say "── 清掉 builder 缓存与所有镜像 ──" | |
| docker builder prune -af >/dev/null 2>&1 || true | |
| docker image prune -af >/dev/null 2>&1 || true | |
| df -h / 2>&1 | tee -a "$DIAG" | |
| # ⚠ ⚠ **判据取「命令成功了没有」,不取「文件在不在」。** | |
| # 2026-08-21 实测:一次 cancel-in-progress 把门禁掐了,导出随之失败, | |
| # **而半截归档已经落盘**(370MB,完整的是 2.2GB)—— | |
| # 按「文件在不在」判会把这份残档存进缓存键,而缓存条目不可变, | |
| # 此后每一轮都精确命中它。**「文件存在」不是「导出成功」。** | |
| say "── cache.sh save ──" | |
| if bash tests/ci/cache.sh save ~/fulcrum-cache 2>&1 | tee -a "$DIAG"; then | |
| SAVE_RC=0 | |
| else | |
| SAVE_RC=1 | |
| fi | |
| # ⚠ 管道里 `tee` 的退出码会盖掉左边那个(本仓库栽过)。取 PIPESTATUS。 | |
| SAVE_RC=${PIPESTATUS[0]:-$SAVE_RC} | |
| say "cache.sh save 退出码:$SAVE_RC" | |
| ls -la ~/fulcrum-cache 2>&1 | tee -a "$DIAG" || true | |
| if [ "$SAVE_RC" = "0" ] && [ -f ~/fulcrum-cache/fulcrum-build-cache.tar.z ]; then | |
| echo "archived=true" >> "$GITHUB_OUTPUT" | |
| say "archived=true" | |
| else | |
| echo "archived=false" >> "$GITHUB_OUTPUT" | |
| say "★ archived=false —— 不存缓存(宁可下一轮冷跑,也不占住那个键)" | |
| fi | |
| say "── 导出后的磁盘 ──" | |
| df -h / 2>&1 | tee -a "$DIAG" | |
| # 同一份诊断也塞进 job summary,两条路都留着。 | |
| { echo '```text'; cat "$DIAG"; echo '```'; } >> "$GITHUB_STEP_SUMMARY" || true | |
| # ★ 与日志分开的那条路:artifact 一定取得回来(`gh run download`)。 | |
| - name: 传诊断 | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: cache-diag | |
| path: ~/cache-diag.txt | |
| if-no-files-found: warn | |
| retention-days: 7 | |
| - name: 存构建缓存 | |
| if: always() && steps.dumpcache.outputs.archived == 'true' && steps.buildcache.outputs.cache-hit != 'true' | |
| uses: actions/cache/save@v4 | |
| with: | |
| path: ~/fulcrum-cache | |
| key: fulcrum-buildcache-v1-${{ runner.os }}-${{ hashFiles('docker/Dockerfile.build') }}-${{ hashFiles('Cargo.lock', 'vendor/pingora/Cargo.lock') }} |