Merge branch 'feat/cachedim-rust-cache-key-includes-image-d'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
This commit is contained in:
commit
5a182e80de
3 files changed
+216
-64
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
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
name: Release / build(可复用)
|
||||
|
||||
# Release 流水线的**构建侧**(M145 拆分:`release.yml` 与 `packaging/README.md` 都顶到 400 行红线,下一处改动必越线)。
|
||||
# M145 时本文件 = 拆分前 `.github/workflows/release.yml` 的 `build` job **逐字搬家**,改动只有四个矩阵键换成同名入参:
|
||||
# `matrix.slot` → `inputs.slot`、`matrix.os` → `inputs.os`、`matrix.build_dir` → `inputs.build_dir`、
|
||||
# `matrix.rustflags` → `inputs.rustflags`。
|
||||
# M145 时本文件 = 拆分前 `.github/workflows/release.yml` 的 `build` job **逐字搬家**,改动只有四个矩阵键换成同名入参
|
||||
# (`matrix.{slot,os,build_dir,rustflags}` → `inputs.{slot,os,build_dir,rustflags}`)。
|
||||
# **M164 改了两步**(只这两步):① `Windows ARM64 工具链体检` → `工具链取证`(不再 exit 1,判定权交回编译器;
|
||||
# 理由见该步上方的长注释);② `cargo build` 步给 arm64 槽位显式备齐 target / `LIB`+`INCLUDE` / arm64 `link.exe`。
|
||||
# **M184 加了一步**:`linux-x64` 的 xvfb 运行时冒烟(真渲一帧 + `[theme]` / `[backend] final` 日志断言 + 像素判定)——
|
||||
# 这一档进了 `PUBLISH_SLOTS`,「发出去的包真能跑」从此由 CI 每轮证明;判据只在 `linux-x64` 槽位上跑(arm64 没有
|
||||
# 对应的运行时证据,不给它加一条没验证过的门禁)。同一轮把 arm64 的失败收尾注解按槽位分支。
|
||||
# 进了 `PUBLISH_SLOTS` 就得由 CI 每轮证明「发出去的包真能跑」;只在 `linux-x64` 跑(arm64 没有运行时证据,不造没验证过的门禁)。
|
||||
# **M270 给 `Swatinem/rust-cache` 加了 `key`(= 镜像标签)与 `save-if`(tag run 不写)**:机制与取舍见 `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 +69,20 @@ 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` ⇒ 曾逐字节相同,`ubuntu-latest`(24.04) 编的 `target/`
|
||||
# 被恢复到 `ubuntu-22.04`(2.35)。致命处:cargo 的 fingerprint(`.rlib` 的 `-C metadata`)**不含 glibc** ⇒
|
||||
# 判 fresh 不重编,24.04 编的 `aws-lc-sys` `bcm.o`(引 `__isoc23_sscanf`)在 2.35 上链接 ⇒ undefined。
|
||||
# 故 key 只取 `inputs.os`:linux-x64(22.04) 与 main 的 `ubuntu-latest` 从此不同 key(要断的就是这条),而
|
||||
# windows-x64(windows-latest) 与 main 的 `ci/build` **故意同 key** —— 同镜像,借热缓存安全(M270 前 Windows
|
||||
# 一直走这条且产物是绿的);slot 是冗余维度,每个槽位的 `inputs.os` 本来就互不相同。
|
||||
# `save-if`:tag run 只能读 main / 同名 tag 的缓存 ⇒ 本侧写出的 target 缓存永远轮不到复用,只贡献冷编 +
|
||||
# 死缓存 + 挤掉 main 的热缓存。故 tag 不写;留 `ref_type != 'tag'` 这个门是给 main 上 dispatch 预热用的。
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: ${{ inputs.os }}
|
||||
save-if: ${{ github.ref_type != 'tag' }}
|
||||
|
||||
# (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 +141,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 +162,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=""
|
||||
@@ -212,13 +219,12 @@ jobs:
|
||||
ls -l "$BUILD_DIR"/unlucky-launcher-iced* "$BUILD_DIR"/ull* || true
|
||||
fi
|
||||
|
||||
# (M190)版本资源门禁:**读产物字节**,不信构建日志。`build.rs` 里写了 `CompanyName` 不等于
|
||||
# 它进了 PE——资源在链接期被丢掉、或被后处理覆盖,都只有读回来才看得见。判据(字段值、四段式
|
||||
# 版本号、FIXEDFILEINFO 的 dwFileVersion)全在 `packaging/check_pe_version.py`,本机可跑。
|
||||
# 只在 Windows 槽位跑:Linux/macOS 产物没有 PE 头,那边 `build.rs` 整段不执行(见
|
||||
# `packaging/pe_version.rs` 文件头)。**工具缺失也 fail**:找不到 python 就等于门禁失效。
|
||||
# 步骤体本身在 `packaging/ci-pe-version-gate.sh`:读回 + 同源取版本号 + 失败逐条转 `::error::`
|
||||
# 注解(M264 从本文件拆出,给 400 行硬门禁腾地方;判据没动,仍由检查器 exit 1)。
|
||||
# (M190)版本资源门禁:**读产物字节**,不信构建日志。`build.rs` 里写了 `CompanyName` 不等于它进了 PE——
|
||||
# 资源在链接期被丢掉、或被后处理覆盖,都只有读回来才看得见。判据(字段值、四段式版本号、FIXEDFILEINFO
|
||||
# 的 dwFileVersion)全在 `packaging/check_pe_version.py`,本机可跑。只在 Windows 槽位跑:Linux/macOS 产物
|
||||
# 没有 PE 头,那边 `build.rs` 整段不执行(见 `packaging/pe_version.rs` 文件头)。**工具缺失也 fail**。
|
||||
# 步骤体本身在 `packaging/ci-pe-version-gate.sh`:读回 + 同源取版本号 + 失败逐条转 `::error::` 注解
|
||||
# (M264 从本文件拆出,给 400 行硬门禁腾地方;判据没动,仍由检查器 exit 1)。
|
||||
- name: PE 版本资源读回(CompanyName / ProductName / 版权 / 版本号真的在产物里)
|
||||
if: runner.os == 'Windows'
|
||||
shell: bash
|
||||
@@ -285,11 +291,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
|
||||
@@ -376,9 +380,8 @@ jobs:
|
||||
retention-days: 14
|
||||
|
||||
# (M142/M164)arm64 若红在别的步骤(cargo / 链接 / 打包 / 导入表门禁),也要有一条注解说清「红在哪」和
|
||||
# 「不挡发布」——只剩一句 `Process completed with exit code 1.` 的教训见上面那条体检步骤。
|
||||
# M164 起本槽位的红**只可能来自真实构建链路**(体检已降级为取证、永不 exit 1),所以这条注解同时也是
|
||||
# 「arm64 到底能不能编出来」的答案所在:红了就把上面的编译器/链接器原文当根因,别再回去改体检。
|
||||
# 「不挡发布」——只剩一句 `Process completed with exit code 1.` 的教训见上面那条体检步骤。M164 起本槽位的红
|
||||
# **只可能来自真实构建链路**(体检永不 exit 1):红了就把上面的编译器/链接器原文当根因,别再回去改体检。
|
||||
# M184:Linux arm64 进来(它现在是 `mode: ci`,tag 推送默认就编),措辞按槽位分支 —— 两个 arm64 都**不在**发布名单里,
|
||||
# 但理由不同:windows-arm64 等「能不能出 PE」的答案,linux-arm64 等「真机跑不跑得起来」的答案。
|
||||
- name: arm64 失败收尾注解(红不挡发布)
|
||||
|
||||
@@ -63,7 +63,8 @@ python packaging/make_artifacts.py --slot windows-arm64 \
|
||||
`CARGO_TARGET_AARCH64_PC_WINDOWS_MSVC_LINKER`(Hostarm64 原生优先、Hostx64 次之;`-find` 的斜杠/反斜杠两种形态都试)。
|
||||
顺序不能反:只覆盖链接器而不给 `LIB`,撞上的就是 `LNK1181: cannot open input file 'kernel32.lib'`。
|
||||
**`cmd //c` 不能直接收「带引号的 vcvarsall 路径」**:MSYS 会把 `"` 改写成 `\"`,cmd 的 `/c` 剥离规则接不住,
|
||||
结果 `LIB`/`INCLUDE` 全空(M164 复审实测;`cmd //c` 只调**不含引号**的路径是好的)。故本步往 `$RUNNER_TEMP`
|
||||
结果 `LIB`/`INCLUDE` 全空(M164 复审实测;`cmd //c` 只调**不含引号**的路径是好的)。当时的报错原文是
|
||||
`'\"C:\...\vcvarsall.bat\"' 不是内部或外部命令`(将来搜到这句、想知道来龙去脉,就看这一段)。故本步往 `$RUNNER_TEMP`
|
||||
写一个 CRLF 的 wrapper `.bat`(`call "<vcvarsall>" arm64` + `set`),`cmd //c` 只收相对文件名——引号留在
|
||||
`.bat` 内部由 cmd 自己解析,路径带空格也不受影响。
|
||||
- **编译前体检已降级为「只取证」**(M164):M141/M142 的版本用 `find` 猜 `um/arm64/kernel32.lib` 在不在、猜缺就
|
||||
@@ -137,3 +138,138 @@ 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 **逐字节相同**。这串表达式里**没有任何一项**能反映出镜像。
|
||||
`RUSTFLAGS` 也救不了:它只写在 `cargo build` 那一步的 `env:` 里,cache 步根本看不到它。
|
||||
|
||||
**为什么 cargo 自己不发现**:freshness 判据是 fingerprint,记的是编译器版本、features、profile、依赖 metadata ——
|
||||
而 `-C metadata` 里**没有 glibc 版本**。于是恢复来的 `.rlib` 被判 fresh、不重编,问题一路拖到最后的链接。
|
||||
|
||||
**修法**(两个文件里 7 处 `Swatinem/rust-cache@v2`;全仓共 8 处,见本节末尾):
|
||||
|
||||
- **发布侧**:`key: ${{ inputs.os }}`(只用镜像标签,**不带 slot**)+ `save-if: ${{ github.ref_type != 'tag' }}`
|
||||
(tag run 只读不写)。两条各有单独小节。
|
||||
- **CI 侧**:三个矩阵 job 用 `key: ${{ matrix.os }}`(自动跟 `runs-on`);三个单 OS job 用它自己的镜像标签字面量。
|
||||
后者的 `key:` 与 `runs-on:` 是**两份手写文本**:改 `runs-on` 时必须一起改 `key:`,否则镜像维度会静默失准(不报错,
|
||||
只是继续跨镜像共用)。
|
||||
- `key` 输入落在**前缀段**(`v0-rust-<key>-<job>-…`,在 `restoreKey` 定格之前),所以主 key 与兜底 `restoreKey`
|
||||
同时带上镜像维度,**两条恢复路径一起分开**。
|
||||
|
||||
#### 发布侧 key 为什么只取 `inputs.os`(不带 slot)
|
||||
|
||||
两个方向同时成立,去掉 slot 才对:
|
||||
|
||||
- **必须断的**:`linux-x64` @ `ubuntu-22.04` vs `ci/build` @ `ubuntu-latest` ⇒ 不同 key。这就是 M269 那条路径。
|
||||
- **故意保留的**:`windows-x64` @ `windows-latest` vs `ci/build` @ `windows-latest` ⇒ **同 key**。同镜像、同 rustc、
|
||||
同一份锁 ⇒ 借 main 上的热缓存是安全的:M270 之前 Windows 走的正是这条路,而 `windows-x64` 一直在 `PUBLISH_SLOTS`
|
||||
里、产物是绿的。slot 本来就是冗余维度(每个槽位的 `inputs.os` 互不相同)。
|
||||
- 代价与取舍:加了 slot 的那一版会把上面这一对也分开,两者的实质差异只是 `RUSTFLAGS=-C target-feature=+crt-static`。
|
||||
合并回同一把 key 后 cargo 按 fingerprint 区分(`RUSTFLAGS` 是否被计入本仓没有实测;但这是 M270 前线上一直成立的
|
||||
状态),最坏情况是多编一遍,不会错编。
|
||||
|
||||
#### 发布侧为什么「只读不写」(tag run 的缓存作用域)
|
||||
|
||||
- 官方文档(`docs.github.com` → dependency-caching → "Restrictions for accessing a cache"):一次 run 只能恢复
|
||||
**当前分支**或**默认分支(main)**创建的缓存;不同 tag 名之间互不可读(`release-a` 写的缓存,`release-b` 读不到)。
|
||||
- 推论一:发布侧的 key 是 `ubuntu-22.04` / `ubuntu-22.04-arm` / `windows-11-arm` / `macos-*`,而 **main 上一个 job 都
|
||||
不跑这些标签**(`ci.yml` 只有 `ubuntu-latest` / `windows-latest` / `macos-latest`)⇒ 这四个槽位**每次发布都是冷编**,
|
||||
连 registry / git 源码也拿不到(rust-cache 是「一把 key 一个 entry」,所有路径捆在一起,没有分路径缓存)。
|
||||
唯一还能借到热缓存的是 `windows-x64`(见上一小节)。
|
||||
- 推论二:发布侧写出去的缓存只能被**同名 tag 的下一次 run** 读到,实际就是一次性死缓存;它还占 per-repo 的 10 GB
|
||||
配额(超出按最后访问时间 LRU 淘汰、7 天未访问过期),会把 main 上真正在用的 CI 热缓存挤掉。**所以 tag run 不写。**
|
||||
- 为什么不是写死 `save-if: false`:把「写」留给唯一有效的场景 —— **在 main 上预热**。`gh workflow run release.yml`
|
||||
(在 main 的末梢上跑)那次 run 的缓存作用域是 **main**,之后从 main 末梢切出来的 tag run 才读得到它(锁哈希与
|
||||
rustc 版本一致才命中)。真要彻底禁写就把这一行改成 `false`,那时预热也要临时改文件才做得到。
|
||||
|
||||
#### 演算(核心判据;r1 换了 key,r0 那张表作废)
|
||||
|
||||
把上面的拼接写成脚本,对 15 个站点(ci 9:clippy/test/build 各 2 个 OS + 3 个单 OS job;发布 6)× 2 个
|
||||
`Cargo.lock` 状态两两判定:
|
||||
|
||||
| 关系 | 改前(7 处都没有 `key:`) | 改后(`key: inputs.os` + `save-if`) |
|
||||
| --- | --- | --- |
|
||||
| ① `rel/windows-x64` ↔ `ci/build`(**同镜像**) | 主 key 相同 | 主 key **仍相同**(故意保留 / 安全) |
|
||||
| ① `rel/linux-x64` ↔ `ci/build`(**跨镜像**) | 主 key 相同 ⇒ **M269 的 bug** | **不相同**(必须断) |
|
||||
| ② **跨镜像** `restoreKey` 前缀命中 | **2 组**(`ci/build@ubuntu-latest` ↔ `rel/linux-x64` 两个方向) | **0 组** |
|
||||
|
||||
拼出来(env / 锁哈希用占位值,取值不影响结论):
|
||||
|
||||
```
|
||||
改后 ci/build@ubuntu-latest cacheKey = v0-rust-ubuntu-latest-build-Linux-x64-3f9a1c07-b2d6e815
|
||||
改后 rel/linux-x64@22.04 cacheKey = v0-rust-ubuntu-22.04-build-Linux-x64-3f9a1c07-b2d6e815
|
||||
↑ 前缀 v0-rust-ubuntu- 之后立刻分叉(`l` vs `2`);两边 restoreKey 都更长 ⇒ 前缀命中不可能
|
||||
改后 ci/build@windows-latest cacheKey = v0-rust-windows-latest-build-Windows_NT-x64-3f9a1c07-b2d6e815
|
||||
改后 rel/windows-x64 cacheKey = v0-rust-windows-latest-build-Windows_NT-x64-3f9a1c07-b2d6e815
|
||||
↑ 逐字节相同(同镜像,这是要保留的复用);锁哈希不同时退化成路径②,仍是同镜像,安全
|
||||
```
|
||||
|
||||
脚本按「**跨镜像**」这一条断言(15 个站点两两 + 两个锁状态):改后任意两个 **runner 标签不同**的站点之间,主 key
|
||||
相同 0 对、`restoreKey` 前缀命中 0 组。仍存在的相同/前缀关系全部落在**同一个镜像标签内部** —— 也就是
|
||||
`rel/windows-x64` ↔ `ci/build` 那一对(同 `windows-latest`,正是要保留的复用)以及同站点换锁哈希那种(本来就该
|
||||
复用依赖)。判据的输出是一次性的脚本,跑完即删;上面这几行就是它的结论。
|
||||
|
||||
#### `cache-targets` 到底关不关:**不关**
|
||||
|
||||
- **CI 侧**:关掉就只缓存 registry / git 源码,每个 job 都要从头编整棵依赖树(M269 干净容器实测同一条命令 18m43s
|
||||
量级),日更矩阵每次提交都付这笔钱,收益为 0 —— 加镜像维度之后 `target/` 的恢复永远来自**同一镜像标签**,
|
||||
M269 那条路径已经断了。
|
||||
- **发布侧**:关掉确实能连 target 缓存一起省,但它**把 Windows 借 main 热缓存这条路一起掐掉**(target 不进缓存就
|
||||
没得借),而 `windows-x64` 是发布侧唯一还能借到的一档。发布侧的死缓存问题由 `save-if` 解决,比关 targets 精准:
|
||||
**该写的一次都不落,该借的一次都不少。**
|
||||
- 被同一把 key 一起管住的还有 `cache-bin`:`~/.cargo/bin` 里是 `dtolnay/rust-toolchain` 从 `static.rust-lang.org`
|
||||
装下来的**官方预编译** rustup 代理(不是在这台 runner 上编的),与 `target/` 同域、同一个 key,不引入额外风险。
|
||||
|
||||
#### 抓得住什么 / 抓不住什么
|
||||
|
||||
- 抓得住**跨镜像复用**:不同 runner 标签之间(`ubuntu-22.04` ↔ `ubuntu-latest`)不再共享 `target/`,两条恢复路径
|
||||
都断,判据就是上面那张表。
|
||||
- **抓不住「同一个镜像标签、但底层镜像悄悄升级」**:`ubuntu-latest` 从 24.04 升到 26.04、或 22.04 打一次 glibc 补丁时,
|
||||
标签不变 ⇒ key 不变 ⇒ 旧缓存照样能恢复。这一档**没有自动化机制**,靠手动 bump:改 `prefix-key`(`v0-rust` →
|
||||
`v1-rust`)或改对应 `key:` 的字面量,一次性作废旧缓存。**别把这修法当成「以后不会再有这类问题」。**
|
||||
- 顺带:`rust-toolchain.toml` 是 `stable`,rustc 一升版 env hash 就变 ⇒ **编译器升级自动冷启动**,这一档本来就不在
|
||||
风险里。
|
||||
|
||||
#### 代价与一次性影响
|
||||
|
||||
- CI 侧:缓存条数从「每 job 一条」变成「每 job × 每镜像一条」;三个单 OS job 各多一次冷编。
|
||||
- 发布侧:只有 `windows-x64` 还能借 main 的热缓存;`linux-x64` / `linux-arm64` / `windows-arm64` / `macos-*`
|
||||
**每次发布都冷编** —— 这是断开 M269 那条路径的必然代价(22.04 在 main 侧没有任何产出者)。想省就按上面
|
||||
「在 main 上预热」那条做。
|
||||
- 合入后所有旧 key 的缓存一次性失配,靠 LRU 自然淘汰;第一次 CI run 是冷缓存,别把它当成回归。
|
||||
- 全仓第 8 处 `rust-cache` 在 `.github/workflows/dev-guards.yml:128`(job id `clippy`,只跑 `ubuntu-latest`),
|
||||
**本次刻意不动**:它唯一的 runner 就是 `ubuntu-latest`,job id 也不与发布侧 `build` 重名,今天不可能跨镜像相撞。
|
||||
副作用是它的 key 与 `ci/clippy` 从此不再逐字节相同(改前是相同的)⇒ 合入后 dev-guards 那次 clippy 会冷编一次,
|
||||
一次性、无害。
|
||||
|
||||
**没验的部分**:真实 Actions 上的缓存命中 / 未命中本地验不了 —— 本机只能证明 key 字符串(上面的演算)、YAML 仍被真
|
||||
解析器接受(`python packaging/check_workflows_yaml.py`)、以及结构正确。「tag run 只能读 main / 同名 tag」这条来自
|
||||
官方文档而不是观测;下一次 tag 正好顺手验三件事:`Cache restored from key:` 是否变成 `No cache found.`(linux-x64
|
||||
应当是)、`Compiling aws-lc-sys` 是否重编、日志里还有没有 `__isoc23_*`。
|
||||
Reference in new issue
Block a user