ci(cache): rust-cache 的 key 带上镜像维度,发布构建不再吃 24.04 编的 target/(M270)
M269 已端到端定性:`aws-lc-sys` 的 `bcm.o` 在 glibc≥2.38 的头下编出 `__isoc23_sscanf`, 被从 24.04 的缓存恢复到 22.04(glibc 2.35)去链接 ⇒ undefined。根因是 `Swatinem/rust-cache@v2` 的 key 只有 `-Linux-X64`:`ci.yml` 的 `build`(ubuntu-latest = 24.04)与 `release-build.yml` 的 `build`(ubuntu-22.04)同 job id、同 rustc、同一份 `Cargo.lock` ⇒ key 逐字节相同。 - 7 处 `Swatinem/rust-cache@v2` 全部加显式 `key:`(发布侧 `inputs.os-inputs.slot`,CI 侧矩阵 维度或该 job 的字面量镜像标签)。`key` 落在 key 的**前缀段**,故主 key 与兜底 `restoreKey` 两条恢复路径一起分开。 - 演算判据(按 config.ts 复刻拼接,12 站点 × 2 锁状态两两比对):改前主 key 相同 2 对、 restoreKey 跨站点前缀命中 4 组;改后均为 0。 - 机制注释写进两个 workflow(`-C metadata` 不含 glibc ⇒ 恢复后 cargo 判 fresh 的因果链), 完整推导、`cache-targets` 的取舍结论与「抓得住/抓不住」写进 packaging/docs/build-and-ci.md §3。 - 两个 workflow 仍 ≤400 行(397 / 399):加注释腾地方时按「判据一字不动、只并句」精简了 M164/M171/M184/M147 几处既有注释。
This commit is contained in:
1 parent
14e8cc4ef2
commit
93dc27bed8
3 files changed
+148
-48
No files matched your search
+41
-28
@@ -3,6 +3,10 @@ name: CI
|
||||
# 主干门禁:fmt / clippy / test / build 双平台矩阵。
|
||||
# 架构守门(core 零 UI、文件 ≤400 行、依赖许可)见 guards.yml。
|
||||
# 平台基线 Win10+(标准工具链产物),平台矩阵见 AGENTS.md §7。
|
||||
# (M270)每个 `Swatinem/rust-cache@v2` 都显式写 `key:` = **该 job 的 runner 镜像标签**(不是「更保险」):v2 的 key
|
||||
# 只有 `os.type()-os.arch()`(`Linux-X64`),**没有镜像标签/glibc** ⇒ 本文件 `build`(ubuntu-latest = 24.04)与
|
||||
# `release-build.yml` 的 `build`(ubuntu-22.04 = glibc 2.35)共用同一份 `target/`。因果链见 `release-build.yml`
|
||||
# 那处注释;`cache-targets` 取舍、剩余风险与 key 演算见 packaging/docs/build-and-ci.md §3「构建缓存」。
|
||||
#
|
||||
# M118(D26):根 Cargo.toml 已 `exclude = ["crates/app"]`(Slint 退役树,源码原样保留)。
|
||||
# 所以本文件里所有 `--workspace` 都**不再**覆盖 `unlucky-app`:CI 不再编 slint 1.18 + usvg 0.48
|
||||
@@ -103,6 +107,8 @@ jobs:
|
||||
with:
|
||||
components: clippy
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: ${{ matrix.os }}
|
||||
# `--workspace` 现在是 8 个 crate,不含 `unlucky-app`(M118 的 workspace exclude),
|
||||
# 所以这条命令不会拉起 slint / usvg / i-slint-*。
|
||||
- run: cargo clippy --workspace --all-targets -- -D warnings
|
||||
@@ -125,17 +131,17 @@ jobs:
|
||||
libwayland-dev libxcb1-dev
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: ${{ matrix.os }}
|
||||
# ---- 下载页选项区的四条像素守门(M127):先出图,再把路径喂给 `--lib` ----
|
||||
# 之前这四条靠开发者本机手动设 `ULL_DL_*_SHOT`(不设就 SKIP),而本文件里 grep 这些
|
||||
# 变量零命中 ⇒ **CI 里一次都没跑过**。空跑的守门长期等于没有,所以这两步是它们的第一处
|
||||
# 常驻调用点。(另有一道保险:变量缺失且 `CI` 已设置时,守门自己会失败而不是跳过。
|
||||
# 于是将来谁把下面出图那步重构掉,构建会红。)
|
||||
# 之前这四条靠开发者本机手动设 `ULL_DL_*_SHOT`(不设就 SKIP),而本文件里 grep 这些变量零命中 ⇒
|
||||
# **CI 里一次都没跑过**。空跑的守门长期等于没有,所以这两步是它们的第一处常驻调用点。(另有一道保险:
|
||||
# 变量缺失且 `CI` 已设置时,守门自己会失败而不是跳过 ⇒ 将来谁重构掉下面出图那步,构建会红。)
|
||||
#
|
||||
# **为什么只在 Windows**:断言里有几条直接量墨迹(行高 44、版本号墨迹条数、图标药丸
|
||||
# 40×40),这些数字按本仓字体栈(Segoe UI / Consolas)标定;Linux/macOS runner 上
|
||||
# 这套字体不存在,回退字体的字高与行距一变就是假红。同一条限制也写在
|
||||
# `crates/app-iced/src/pages/download/tests/options_shot/feed.rs` 的模块文档里
|
||||
# (非 Windows 喂了图会当场失败),不靠「碰巧只配了一处」。
|
||||
# **为什么只在 Windows**:断言里有几条直接量墨迹(行高 44、版本号墨迹条数、图标药丸 40×40),这些数字
|
||||
# 按本仓字体栈(Segoe UI / Consolas)标定;Linux/macOS runner 上这套字体不存在,回退字体的字高与行距
|
||||
# 一变就是假红。同一条限制也写在 `crates/app-iced/src/pages/download/tests/options_shot/feed.rs` 的
|
||||
# 模块文档里(非 Windows 喂了图会当场失败),不靠「碰巧只配了一处」。
|
||||
- name: 出四张像素守门用的图并写进 $GITHUB_ENV(仅 Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: bash
|
||||
@@ -177,22 +183,20 @@ jobs:
|
||||
name: dl-shots
|
||||
path: ${{ runner.temp }}/dl-shots/
|
||||
if-no-files-found: warn
|
||||
# 同 clippy:`--workspace` 不含 `unlucky-app`(M118),Slint 退役树不再被 `--all-targets` 拉起。
|
||||
# 也因此**不要**再写 `--exclude unlucky-app`:它已不是成员,那面旗子只会打一条
|
||||
# `excluded package(s) … not found in workspace` 的警告然后空转(实测 exit 0,不报错)。
|
||||
# 同 clippy:`--workspace` 不含 `unlucky-app`(M118)。**别再写 `--exclude unlucky-app`**:它已不是成员,
|
||||
# 只会打一条 `excluded package(s) … not found in workspace` 的警告然后空转(实测 exit 0,不报错)。
|
||||
#
|
||||
# ---- 取证(M147):下面是同一件事的两个平台版本,不是两条不同的测试 ----
|
||||
# M144 的三条红里两条已在 docker 里定位到行,第三条 `test (windows)` 在 run 里只留下
|
||||
# `Process completed with exit code 1`——而 cargo 自己的失败码是 **101**,所以那个 1 的
|
||||
# 来源不明(包装层洗码 / 进程被杀,两说都未排除)。job log 要登录才能读(匿名 403),
|
||||
# 但**注解匿名可读**(`GET /check-runs/<id>/annotations` → 200,M144 实证)。所以这三件
|
||||
# 事各自对准一种「看不见」:
|
||||
# 1) `--no-fail-fast`:cargo 默认在第一个失败的 target 就停(M144 实测 Linux 只跑了
|
||||
# 7 个 test binary 就退出),后面还有没有第二条失败完全看不到;
|
||||
# 2) 把 `error` / `failures:` / panicked 行升成 `::error::` 注解:拿不到 job log 时,
|
||||
# 注解是匿名侧唯一能取到的失败正文;
|
||||
# 3) 显式打印 cargo 的退出码:把「包装层把码洗成 1」「cargo 真失败(101)」「进程被杀
|
||||
# 只给码、零诊断」三种情形分开——这正是 M144 没定的那一半。
|
||||
# M144 的第三条红 `test (windows)` 只留下 `Process completed with exit code 1`,而 cargo 自己的失败码
|
||||
# 是 **101** ⇒ 那个 1 的来源不明(包装层洗码 / 进程被杀,两说都未排除);job log 要登录才能读
|
||||
# (匿名 403),但**注解匿名可读**(`GET /check-runs/<id>/annotations` → 200,M144 实证)。三件事
|
||||
# 各对准一种「看不见」:
|
||||
# 1) `--no-fail-fast`:cargo 默认在第一个失败的 target 就停(M144 实测 Linux 只跑 7 个 test
|
||||
# binary 就退出),后面还有没有第二条失败完全看不到;
|
||||
# 2) `error` / `failures:` / panicked 行升成 `::error::` 注解:拿不到 job log 时,注解是匿名侧
|
||||
# 唯一能取到的失败正文;
|
||||
# 3) 显式打印 cargo 的退出码:把「包装层把码洗成 1」「cargo 真失败(101)」「进程被杀只给码、零诊断」
|
||||
# 分开——这正是 M144 没定的那一半。
|
||||
- name: cargo test --workspace --all-targets(ubuntu,失败升注解)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
@@ -215,8 +219,8 @@ jobs:
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
env:
|
||||
# M171 跨卷守卫:runner 到底有没有第二块可写卷没有核实手段,所以不赌 —— 显式声明豁免。
|
||||
# 豁免是惰性的:真探到第二块卷时它不参与判断(见 install_xdev_cases 的真值表用例)。
|
||||
# M171 跨卷守卫:runner 有没有第二块可写卷没核实手段,所以不赌 ⇒ 显式豁免(惰性:真探到就不参与
|
||||
# 判断,见 install_xdev_cases 的真值表用例)。
|
||||
ULL_XDEV_TEST_WAIVE: "1"
|
||||
run: |
|
||||
# pwsh 没有 PIPESTATUS(那是 bash 语法):拿 `$LASTEXITCODE` —— Tee-Object 是 cmdlet,
|
||||
@@ -252,6 +256,10 @@ jobs:
|
||||
libwayland-dev libxcb1-dev
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
# M270:本 job 就是与发布侧**同名同 key** 的那一个(都叫 build,镜像 24.04 vs 22.04)。
|
||||
# 镜像标签进 key 前缀之后,主 key 与兜底 restoreKey 两条恢复路径都不再跨镜像命中。
|
||||
key: ${{ matrix.os }}
|
||||
# 同上:`--workspace` 不含 `unlucky-app`(M118)。被摘掉的正是 release 档
|
||||
# (lto=fat + codegen-units=1)最贵的那棵 slint + usvg 树。
|
||||
- run: cargo build --workspace --release
|
||||
@@ -294,9 +302,10 @@ jobs:
|
||||
fonts-dejavu-core fonts-liberation2
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# 用 debug 而不是 release:本 job 验的是「运行时渲染通路能通」,不是优化后二进制的行为;
|
||||
# release 的 lto="fat" + codegen-units=1 会把这一步拖到十几分钟却不带来额外证据。
|
||||
# M118:bin 从 Slint 版 `unlucky-launcher` 换成 iced 版(见上方 job 注释)。
|
||||
with:
|
||||
key: ubuntu-latest
|
||||
# 用 debug 而不是 release:「运行时渲染通路能通」与优化无关,release 的 lto="fat" + codegen-units=1
|
||||
# 只会把这一步拖到十几分钟。M118:bin 从 Slint 版 `unlucky-launcher` 换成 iced 版(见上方 job 注释)。
|
||||
- run: cargo build --bin unlucky-launcher-iced --locked
|
||||
- name: 在 Xvfb 里跑一帧并截图(退出码非 0 即失败)
|
||||
run: |
|
||||
@@ -335,6 +344,8 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: macos-latest
|
||||
- run: cargo build --bin unlucky-launcher-iced --locked
|
||||
- name: 尝试截图(macOS 没有 X 服务器,直接跑;失败即为结论)
|
||||
run: |
|
||||
@@ -378,6 +389,8 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: windows-latest
|
||||
- name: update-e2e
|
||||
run: >
|
||||
cargo test --release -p unlucky-cli --features update-test-source
|
||||
|
||||
@@ -9,6 +9,8 @@ name: Release / build(可复用)
|
||||
# **M184 加了一步**:`linux-x64` 的 xvfb 运行时冒烟(真渲一帧 + `[theme]` / `[backend] final` 日志断言 + 像素判定)——
|
||||
# 这一档进了 `PUBLISH_SLOTS`,「发出去的包真能跑」从此由 CI 每轮证明;判据只在 `linux-x64` 槽位上跑(arm64 没有
|
||||
# 对应的运行时证据,不给它加一条没验证过的门禁)。同一轮把 arm64 的失败收尾注解按槽位分支。
|
||||
# **M270 给 `Swatinem/rust-cache` 加了显式 `key`(镜像 + 槽位)**:缓存会跨镜像串产物,机制、收益/剩余风险与 key 演算见
|
||||
# `packaging/docs/build-and-ci.md` §3「构建缓存」。
|
||||
# 矩阵**仍由调用方** `release.yml` 的 `plan` job 生成并在外层展开(矩阵不能带 `if`,`strategy` 可读 `needs`),
|
||||
# 本文件只跑**单个槽位**,不持有槽位表 —— 改槽位只改 `release.yml` 里那一处。
|
||||
# `env` 必须在被调方重复声明:**被调工作流不继承调用方的 workflow 级 `env`**(CARGO_TERM_COLOR / RUST_BACKTRACE
|
||||
@@ -70,7 +72,15 @@ jobs:
|
||||
libgl1-mesa-dev libegl1-mesa-dev libwayland-dev libxcb1-dev
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
# (M270)`key:` 必须带**镜像维度**,不是「加个 key 更保险」:v2 的 key = `v0-rust-` + 本行 key + job +
|
||||
# `-Linux-X64`(os.type/os.arch)+ env 哈希 + 锁哈希 —— 里面**没有镜像标签/glibc**;本 job 与 `ci.yml` 的
|
||||
# `build` 同 job id、rustc 同版、同一份 `Cargo.lock` ⇒ 两边 key 逐字节相同,`ubuntu-latest`(24.04) 编的
|
||||
# `target/` 会被恢复到 `ubuntu-22.04`(glibc 2.35)。致命处:cargo 的 fingerprint(`.rlib` 的 `-C metadata`)
|
||||
# **不含 glibc** ⇒ 恢复后判 fresh 不重编,24.04 编的 `aws-lc-sys` `bcm.o`(引 `__isoc23_sscanf`)在 2.35 上
|
||||
# 链接 ⇒ undefined。`key:` 落**前缀段**,主 key 与兜底 `restoreKey`(前缀去锁哈希)一起分开,演算见 docs §3。
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: ${{ inputs.os }}-${{ inputs.slot }}
|
||||
|
||||
# (M141/M142 → **M164 判定权移交**)windows-arm64 跑在**原生** `windows-11-arm` 上。本步**只取证,不判定**:
|
||||
# M141/M142 的版本用 `find` 猜 SDK 的 `um/arm64/kernel32.lib` 在不在,猜缺就 `exit 1` —— 结果 v0.2.1~v0.2.3
|
||||
@@ -129,15 +139,12 @@ jobs:
|
||||
# 官方产物自带微软登录所需的配置,装完即可登录;运行期同名环境变量仍优先于它,可覆盖。
|
||||
# 值来自仓库 Secret(不进 git 文件);Secret 没配时这里是空串,client_id.rs 把空白当「没配」,
|
||||
# 于是非官方构建不烘 id。想自己烘一份就从源码构建:`ULL_MSA_CLIENT_ID=<id> cargo build`。
|
||||
# (M164)arm64 槽位在编译前把工具链**显式**备齐,不再依赖「runner 反正自带」这种默契:
|
||||
# ① `rustup target add aarch64-pc-windows-msvc`:宿主机本来就是 aarch64-pc-windows-msvc,这句在原生
|
||||
# runner 上是幂等 no-op;它把「缺 std」这类失败挡在编译前(缺 target 时 rustc 只会说找不到 crate std);
|
||||
# ② MSVC 的 `LIB` / `INCLUDE` 用微软自己的 `vcvarsall.bat arm64` 取(**不手搓路径**:少一个目录就是
|
||||
# LNK1181),本步 `export` 一次、同时写进 `$GITHUB_ENV` 供下游 dumpbin / 打包步;
|
||||
# ③ arm64 的 `link.exe` 用 vswhere 定位后显式导出 `CARGO_TARGET_AARCH64_PC_WINDOWS_MSVC_LINKER`
|
||||
# (Hostarm64 原生优先、Hostx64 次之)。**②必须先于③**:只覆盖链接器而不给 LIB,rustc 会在
|
||||
# `link.exe` 里撞 `LNK1181: cannot open input file 'kernel32.lib'`——那正是我们要定位的错误,不是要制造的错误。
|
||||
# ①②③ 全是「定位不到就打印原因继续」,不用启发式决定红绿:真缺工具链时让链接器自己报出来。
|
||||
# (M164)arm64 槽位在编译前把工具链**显式**备齐(不再靠「runner 反正自带」):① `rustup target add
|
||||
# aarch64-pc-windows-msvc`(原生 runner 上幂等 no-op,把「缺 std」挡在编译前);② `LIB`/`INCLUDE` 用微软
|
||||
# 自己的 `vcvarsall.bat arm64` 取(**不手搓路径**,少一个目录就是 LNK1181)并写进 `$GITHUB_ENV`;
|
||||
# ③ `link.exe` 由 vswhere 定位后导出 `CARGO_TARGET_AARCH64_PC_WINDOWS_MSVC_LINKER`(Hostarm64 优先)。
|
||||
# **②必须先于③**:只给链接器不给 LIB 撞的就是 `LNK1181: cannot open input file 'kernel32.lib'`。
|
||||
# 三道都是「定位不到就打印原因继续」——真缺工具链时让链接器自己报错。
|
||||
- name: cargo build --release --locked
|
||||
shell: bash
|
||||
env:
|
||||
@@ -153,12 +160,10 @@ jobs:
|
||||
rustup target list --installed
|
||||
vswhere="C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe"
|
||||
if [ -x "$vswhere" ]; then
|
||||
# ② MSVC 的 LIB/INCLUDE。**别把 vcvarsall 的路径直接塞给 `cmd //c "..."`**:MSYS 会把参数里的
|
||||
# `"` 改写成 `\"` 再交给 cmd.exe,而 cmd 的 /c 引号剥离接不住这一层,只会报
|
||||
# `'\"C:\...\vcvarsall.bat\"' 不是内部或外部命令` —— 结果 LIB/INCLUDE 全空、只剩 `set` 的空输出
|
||||
# (M164 复审实测)。故本步改为往 `$RUNNER_TEMP` 写一个 **CRLF** 的 wrapper .bat,引号留在
|
||||
# .bat 自己的 `call "..."` 里(由 cmd 解析,路径带空格也没事),`cmd //c` 只收**不含引号**的
|
||||
# 相对文件名(先 cd 进去),彻底绕开 MSYS 的引号改写。
|
||||
# ② MSVC 的 LIB/INCLUDE。**别把 vcvarsall 路径直接塞给 `cmd //c "..."`**:MSYS 会把 `"` 改写成
|
||||
# `\"`,cmd 的 /c 剥离接不住 ⇒ LIB/INCLUDE 全空、只剩 `set` 的空输出(M164 复审实测)。故往
|
||||
# `$RUNNER_TEMP` 写一个 **CRLF** 的 wrapper .bat(引号留在 .bat 的 `call "..."` 里),`cmd //c`
|
||||
# 只收不含引号的文件名(先 cd 进去),彻底绕开 MSYS 的引号改写。
|
||||
tmp=${RUNNER_TEMP:-${TEMP:-/tmp}}
|
||||
mkdir -p "$tmp"
|
||||
vcvars=""
|
||||
@@ -285,11 +290,9 @@ jobs:
|
||||
PY=$(command -v python3 || command -v python)
|
||||
"$PY" packaging/check_pe_subsystem.py --gui "${{ inputs.build_dir }}/unlucky-launcher-iced.exe" --cli "${{ inputs.build_dir }}/ull.exe"
|
||||
|
||||
# (M184)linux-x64 的**运行时门禁**:这一档已进 PUBLISH_SLOTS,所以「发出去的包真能开窗渲出一帧」得由 CI 每轮证明,
|
||||
# 不能只靠一次性的本机验收(证据与实测输出见 `packaging/docs/linux-evidence.md`)。
|
||||
# **只在 linux-x64 槽位上跑**:判据是按 x64 软渲染实测标定的;`linux-arm64` 上这条链一次都没验过(没有 arm64 的
|
||||
# xvfb 证据),写成无条件的 `runner.os == 'Linux'` 就等于给一个没验证过的槽位加一条会红的判据——正是 M171 那个
|
||||
# 「豁免行要单独落」的同类事故。宁可这条只在有证据的槽位上真跑。
|
||||
# (M184)linux-x64 的**运行时门禁**:这一档已进 PUBLISH_SLOTS,「发出去的包真能开窗渲出一帧」得每轮由 CI 证明
|
||||
# (证据见 `packaging/docs/linux-evidence.md`)。**只在 linux-x64 跑**:判据按 x64 软渲染实测标定,`linux-arm64`
|
||||
# 上一次都没验过,写成 `runner.os == 'Linux'` 就是给没验证过的槽位加会红的判据(M171 同类事故)。
|
||||
- name: Linux x64 运行时冒烟(xvfb 真渲一帧 + 主题/后端日志断言)
|
||||
if: inputs.slot == 'linux-x64'
|
||||
shell: bash
|
||||
|
||||
@@ -137,3 +137,87 @@ python packaging/make_artifacts.py --slot windows-arm64 \
|
||||
Windows 为什么用 MSVC 静态 CRT 而不是本机这套 GNU 工具链:两者都属"标准工具链"(D21),
|
||||
但发布产物要**不依赖 VC++ 可再发行包**,`+crt-static` 把 CRT 静态链进 exe,Win10+ 的 UCRT 本身是
|
||||
系统组件,于是 exe 真正单文件可拷走。GNU 交叉产物在 CI 上还要拉 MinGW 链接器,收益为零。
|
||||
|
||||
### 构建缓存 key:必须带镜像维度(`Swatinem/rust-cache`,M270)
|
||||
|
||||
**事故形态(M269 端到端复现)**:`linux-x64` 发布构建红在链接期,`libaws_lc_sys-*.rlib` 里的 `bcm.o` 引用
|
||||
`__isoc23_sscanf` / `__isoc23_strtol` —— 那是 glibc ≥ 2.38 的头把 `sscanf` / `strtol` 改写成 `__isoc23_*` 之后才有的符号,
|
||||
而链接发生在 `ubuntu-22.04`(glibc 2.35)。干净 22.04 容器里同一条命令 **exit 0**,干净 24.04 也 exit 0 ⇒ 不是「镜像版本
|
||||
差异」本身,是**缓存把 24.04 编出来的 `.o` 搬到了 22.04**。
|
||||
|
||||
**key 是怎么算出来的**(`rust-cache@v2` 的 `src/config.ts`;`add-job-id-key` 与 `add-rust-environment-hash-key` 两项默认都是 true):
|
||||
|
||||
```
|
||||
key = prefix-key「v0-rust」
|
||||
+ 「-<key 输入>」 ← 本仓 M270 才第一次用(下文「修法」)
|
||||
+ 「-<GITHUB_JOB>」 ← 是 job **id**,不是 job 的 `name:`
|
||||
+ 「-<os.type()>-<os.arch()>」 ← Linux-X64 / Windows_NT-X64 / Darwin-ARM64,**没有镜像标签**
|
||||
+ 「-<env hash 前 8 位>」 ← rustc -vV(release + host + commit-hash)+ CARGO*/CC/CFLAGS/CXX/CMAKE/RUST* 的 sha1
|
||||
restoreKey = 到这里为止的一整串 ← 兜底恢复 key(**不含**锁哈希)
|
||||
key += 「-<manifest/lock hash 前 8 位>」 ← 各 Cargo.toml(版本号被抹平)+ Cargo.lock + .cargo/config.toml
|
||||
cacheKey = key
|
||||
```
|
||||
|
||||
两条恢复路径(`src/restore.ts` 就是一句 `restoreCache(paths, cacheKey, [restoreKey])`):
|
||||
|
||||
- ① 主 key **逐字节相同** → `full match = true`,**不清理** `target/`,直接吃别人的产物;
|
||||
- ② 主 key 未命中、`restoreKey` **前缀匹配**到某条已有缓存 → `full match = false`,先 `cleanTargetDir`(只清 profile 层),
|
||||
依赖的 `.o` / `.rlib` 留着算 fresh —— M269 那条「依赖 fresh + 旧系统链接」正是走这条。
|
||||
|
||||
**为什么 CI 与发布两侧会撞**:`ci.yml` 的 job id 是 `build`(跑 `ubuntu-latest` = 24.04),`release-build.yml` 的 job id
|
||||
也是 `build`(跑 `ubuntu-22.04`);`rust-toolchain.toml` 是 `channel = "stable"`(同一天两边解析到同一版 rustc),
|
||||
`Cargo.lock` 也是同一份 ⇒ env hash 与锁哈希全相同 ⇒ 两个 key **逐字节相同**。这串表达式里**没有任何一项**能反映出镜像。
|
||||
|
||||
**为什么 cargo 自己不发现**:freshness 判据是 fingerprint,记的是编译器版本、features、profile、依赖 metadata ——
|
||||
而 `-C metadata` 里**没有 glibc 版本**。于是恢复来的 `.rlib` 被判 fresh、不重编,问题一路拖到最后的链接。
|
||||
|
||||
**修法**:两个文件里 7 处 `Swatinem/rust-cache@v2` 全部加显式 `key:` —— 发布侧 `${{ inputs.os }}-${{ inputs.slot }}`
|
||||
(如 `ubuntu-22.04-linux-x64`),CI 侧用 `${{ matrix.os }}`(矩阵维度)或该 job 的字面量标签。`key` 输入落在**前缀段**
|
||||
(`v0-rust-<key>-<job>-…`),所以主 key 与兜底 `restoreKey` 同时带上镜像维度,**两条恢复路径一起断**。
|
||||
|
||||
**演算(这次的核心判据)**:把上面那段拼接写成脚本,对 12 个站点 × 2 个 `Cargo.lock` 状态逐对做前缀判定,改前 / 改后:
|
||||
|
||||
| 对照 | 改前(7 处都没有 `key:`) | 改后 |
|
||||
| --- | --- | --- |
|
||||
| 路径①:主 key 逐字节相同 | **2 对**:`ci/build` == `rel/linux-x64` | 0 对 |
|
||||
| 路径②:`restoreKey` 前缀命中**别的站点** | **4 组**(`v0-rust-build-Linux-X64-<env>` 命中 `…-<另一个锁哈希>`) | 0 组 |
|
||||
|
||||
拼出来长这样(env / 锁哈希用占位值,取不同的值不影响结论):
|
||||
|
||||
```
|
||||
改前 ci/build restoreKey = v0-rust-build-Linux-X64-3f9a1c07
|
||||
cacheKey = v0-rust-build-Linux-X64-3f9a1c07-b2d6e815
|
||||
改前 rel/linux-x64 restoreKey = v0-rust-build-Linux-X64-3f9a1c07 ← 与上行完全相同 ⇒ 路径①当场命中
|
||||
cacheKey = v0-rust-build-Linux-X64-3f9a1c07-b2d6e815
|
||||
改后 ci/build restoreKey = v0-rust-ubuntu-latest-build-Linux-X64-3f9a1c07
|
||||
cacheKey = v0-rust-ubuntu-latest-build-Linux-X64-3f9a1c07-b2d6e815
|
||||
改后 rel/linux-x64 restoreKey = v0-rust-ubuntu-22.04-linux-x64-build-Linux-X64-3f9a1c07
|
||||
cacheKey = v0-rust-ubuntu-22.04-linux-x64-build-Linux-X64-3f9a1c07-b2d6e815
|
||||
```
|
||||
|
||||
`v0-rust-ubuntu-latest-…` 与 `v0-rust-ubuntu-22.04-…` 在第一个 `-` 之后就分叉,任何一方的 `restoreKey` 都不是另一方
|
||||
`cacheKey` 的前缀(脚本对 12 个站点两两验证,主 key 相同 0 对、前缀命中 0 组)。前缀判定是**字面量**的 —— 只要
|
||||
`key:` 里不出现 `-<jobid>-<os类型>-<arch>-<env哈希>` 这种形状的文本(runner 标签不会是),两条 key 就不可能重新互为
|
||||
前缀;将来谁把自由文本塞进 `key:`,这一条要自己重新确认。
|
||||
|
||||
**`cache-targets` 要不要一起关掉:不关。** 关掉就只缓存 registry / git 源码,跨镜像确实彻底无风险,但每个 job 都要
|
||||
**从头编译整棵依赖树**(M269 干净容器实测同一条命令 18m43s 量级),日更的 CI 矩阵和每次发版都得付这笔钱。加了镜像维度
|
||||
之后,`target/` 的恢复永远来自**同一个镜像标签**下的缓存,M269 那条路径已经被切断;剩下被同一把 key 一起管住的还有
|
||||
`cache-bin`(`~/.cargo/bin` 里的 rustup shim 也是宿主编译的二进制),它与 `target/` 同域,没有引入额外风险。
|
||||
|
||||
**抓得住什么 / 抓不住什么**:
|
||||
|
||||
- 抓得住**跨镜像复用**:不同 runner 标签之间(`ubuntu-22.04` ↔ `ubuntu-latest`)不再共享 `target/`,两条恢复路径都断,
|
||||
判据就是上面那张演算表。
|
||||
- **抓不住「同一个镜像标签、但底层镜像悄悄升级」**:`ubuntu-latest` 从 24.04 升到 26.04 时分母还是同一个标签,key 不变,
|
||||
旧缓存照样能恢复;22.04 打一次 glibc 补丁同理。这一档**没有自动化机制**,靠手动 bump:改 `prefix-key`
|
||||
(`v0-rust` → `v1-rust`)或改本处 `key:` 的字面量,一次性作废旧缓存。**别把这修法当成「以后不会再有这类问题」。**
|
||||
- 顺带:`rust-toolchain.toml` 是 `stable`,rustc 一升版 env hash 就变 ⇒ **编译器升级自动冷启动**,这一档本来就不在风险里。
|
||||
|
||||
**代价与一次性影响**:缓存条数从「每 job 一条」变成「每 job × 每镜像一条」,GitHub 的 per-repo 上限是 10 GB(LRU 淘汰 +
|
||||
7 天未访问过期),命中率靠 `restoreKey` 的锁哈希前缀兜着。**这次改动本身会让现有缓存一次性失效**(旧 key 再也匹配不上,
|
||||
交给 LRU 自然淘汰),合入后第一轮是冷缓存,别把它当成回归。
|
||||
|
||||
**没验的部分**:真实 Actions 上的缓存命中 / 未命中本地验不了 —— 本机只能证明 key 字符串确实分开了(上面的演算)以及
|
||||
workflow 仍被真 YAML 解析器接受(`python packaging/check_workflows_yaml.py`,M190 的门禁)。「命中后日志里不再出现
|
||||
`__isoc23_*`」要等下一次 tag 的 `linux-x64` 构建日志说话。
|
||||
Reference in new issue
Block a user