原作者:刘家财。本文依据原文核对整理;原文发布于 2022-10-29,更新于 2022-12-18。缓存键的设计思路仍有参考价值,动作版本、服务额度和权限规则则以 2026-10-05 核对的官方说明作补充。
缓存可以缩短持续集成中反复下载依赖、编译依赖的时间。真正需要设计的是:什么时候可以直接使用旧缓存,什么时候应该借用旧缓存的一部分,以及什么时候需要保存一个新缓存。
对 Rust 项目来说,Cargo.lock 的变化是一个重要信号:锁文件变化意味着依赖解析结果可能变了。操作系统和 Rust 工具链也会影响编译结果,所以不能只用一个固定名称缓存整个 target。
cache 动作的三个核心参数
key 是缓存标识;path 是需要保存和恢复的路径;restore-keys 是主键没有命中时,依次尝试的候选前缀。可以先把它理解成一个带有作用域和版本信息的键值存储,再考虑 GitHub 的分支访问规则。

原文示例:将经常变化的部分放在后面
下面保留原文工作流,用来解释键的结构。它是2022 年历史示例,不是可直接部署的当前推荐配置:checkout@master 会随分支变化,cache@v3 需要检查实际修订和运行器兼容性,整目录 ~/.cargo 还可能包含凭据。
jobs:
test:
timeout-minutes: 20
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Cache Crates
uses: actions/cache@v3
with:
path: |
./target
~/.cargo
key: debug-${{ runner.os }}-${{ hashFiles('rust-toolchain.toml') }}-${{ hashFiles('Cargo.lock') }}
restore-keys: |
debug-${{ runner.os }}-${{ hashFiles('rust-toolchain.toml') }}-
debug-${{ runner.os }}-
- run: cargo test
主键由四部分组成。开头的 debug 把调试构建与 release 构建分开;runner.os 区分操作系统;rust-toolchain.toml 的哈希区分工具链配置;Cargo.lock 的哈希区分依赖锁定结果。原文把最容易变化的锁文件放在最后,为前缀恢复留下空间。
精确命中 key 时,恢复对应缓存;同一个键下的缓存不是每次都被覆盖重写。没有精确命中时,系统按顺序查找 restore-keys,先尝试更具体的前缀,再尝试较宽的前缀。匹配到旧缓存后,仍然要运行构建或测试,让 Cargo 判断哪些产物可复用、哪些需要重建。
三个变化场景
第一种情况,只修改了 Cargo.lock。主键改变,但第一个恢复前缀仍包含同一系统和同一工具链,因此可以复用先前依赖中的有效部分。比如十个依赖只升级了一个,没有必要主动丢弃另外九个的全部下载缓存。
第二种情况,工具链配置也改变了。第一个前缀不再匹配,原文的第二个前缀会退回同一操作系统的 debug 缓存。这能提高命中机会,但恢复的内容更旧,编译产物未必可复用;收益要靠实际项目测量,不能把“匹配上了”当成“全部有效”。
第三种情况,切换成 release 构建。只要相应工作流把键前缀换成 release,就不会匹配以 debug 开头的缓存。这是一种主动隔离策略,可以减少不同配置挤在同一缓存里造成的膨胀。Cargo 自身也会区分构建配置,但缓存归档仍需要按项目成本取舍。
当主键没有精确命中,且任务成功完成、当前运行具有保存权限时,cache 动作可以用新的完整键保存缓存。原文“执行结束后就更新”的说法在这里作了收紧:失败任务和没有写权限的运行不能假定一定保存成功。
修订配置时,先缩小缓存路径
编辑修订:不建议照抄整个 ~/.cargo。该目录除了下载的依赖,还可能有 credentials.toml、私有源配置或其他不应进入共享缓存的文件。下面是可用于替换原文 with 部分的较窄配置片段;只展示缓存参数,动作本身应使用已审查、与运行器兼容的正式发行版本,并固定到其完整提交 SHA。
with:
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
target
key: debug-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('rust-toolchain.toml', 'rust-toolchain') }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
debug-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('rust-toolchain.toml', 'rust-toolchain') }}-
与原文相比,这个片段移除了整个 Cargo 主目录,加入 CPU 架构,兼顾两种工具链文件名和工作区内的锁文件,并去掉“跨工具链”的宽恢复前缀。这样会减少某些命中,换来更明确的兼容边界。它仍不适用于所有项目:如果设置了 CARGO_HOME 或 CARGO_TARGET_DIR,要改成真实路径;如果 feature、目标三元组、RUSTFLAGS、链接器或系统库决定了产物差异,也应将这些条件纳入键,或拆分下载缓存与编译产物缓存。
尤其要注意,哈希工具链文件只锁定“文件内容”。如果文件写的是会持续移动的 stable,同一文件哈希不代表同一个编译器。需要可复现构建时,应在工具链文件中固定实际版本,或把取得的编译器版本作为键的一部分。缺少文件时也不能期待 hashFiles 自动补出版本信息。
测试步骤可按项目要求使用 cargo test --locked,防止 CI 静默改写依赖锁定结果。缓存精确命中也不能成为跳过测试的理由:源代码、外部服务和测试输入仍可能变化。
版本、作用域与容量
原文使用的动作版本不能代表今天的安装建议。核对时 actions/cache 官方仓库已展示 v6 用法,并记录 v5 采用 Node.js 24、要求至少 2.327.1 的 Actions Runner。旧缓存服务在 2025 年发生过迁移;固定旧 SHA 的工作流必须核对迁移兼容性,不能只看 @v3 或 @v4 这个主版本标签就判断是否可用。标签与分支都可能移动,生产工作流应审查具体发布并使用完整提交 SHA;本文不凭空填入未经核实的 SHA。
缓存还有分支和版本作用域。原文“主分支缓存能被派生分支使用,而两个平级分支不能随意互相使用”的描述有助于入门,但真实规则还涉及 pull request 的 base 分支、合并引用和低信任触发器。恢复键只能在允许访问的缓存范围内匹配,不能绕过这些边界。缓存版本还受路径和压缩方式影响,因此键的字符串相同也不保证能跨平台恢复。
关于容量,原文列出 7 天未访问淘汰、10 GB 上限和按最近访问时间淘汰。当前 GitHub 缓存参考文档仍说明超过 7 天未访问的缓存会被移除,但 10 GB 是每仓库的默认额度,可由有权限的管理员调整;使用额外存储可能收费。不要把 2022 年的“只有 10G”继续写成固定总上限。
缓存不是可信边界
GitHub 官方明确提醒,能读取缓存的运行可以提取缓存内容;缓存内容也不是经过签名验证的可信制品。不要把 token、登录凭据、证书私钥或带秘密的配置放入缓存,不要把来自不可信分支的缓存直接带进拥有发布权限的任务。target 中包含之后可能被执行的程序,缓存投毒会影响 CI 的可信度。
当前示例没有发现直接把 PR 标题等不可信文本插入 shell 的命令注入,也没有可见硬编码秘密,但整个 ~/.cargo 的宽路径确实构成泄漏风险。cargo test 本身会运行项目、依赖构建脚本和测试代码,不应在带生产凭据的高权限运行器上执行不可信 PR。本次仅做静态审查,没有启动 GitHub Actions、安装依赖或运行 Cargo;也没有声称修订片段已经通过测试。
原文延伸阅读包括 Caching dependencies to speed up workflows、GitHub Pages 的 Actions 工作流说明,以及关于 CI 生成文档与 gh-pages 历史体积的社区讨论。版权归刘家财,未从原文页面确认独立文章许可证,不把软件仓库的 MIT 许可证套用到作者文章上。配图为未完纪原创。












暂无评论内容