cargo doc --open 生成的依赖文档如何缓存?

解读

面试官问“如何缓存”,并不是想知道“文档存在哪个目录”,而是想确认候选人是否理解 Cargo 的构建缓存机制rustdoc 的增量生成策略 以及 国内网络环境下如何加速依赖文档的拉取
在国内 CI/CD、离线机房、多人协作场景下,文档重复生成耗时、 crates.io 索引拉取失败、docs.rs 无法访问都是高频痛点。能把“缓存”拆成 索引缓存 + 构建产物缓存 + 网络层缓存 三层来回答,才能体现工程化思维。

知识点

  1. 构建产物缓存路径
    Cargo 把文档输出到 target/doc,与 target/debug / target/release 并列;同一工作空间内所有成员共享该目录,增量生成粒度为 crate-level,mtime 不变即跳过。
  2. CARGO_TARGET_DIR 重定向
    通过环境变量 CARGO_TARGET_DIR 可把 target 目录指向 公司 NAS、Docker Volume 或 GitLab Cache 路径,实现跨构建复用。
  3. rustdoc 缓存键
    rustdoc 的缓存键 = (crate 源码哈希 + rustc 版本 + RUSTDOCFLAGS)。升级 nightly 或改动 #[doc(hidden)] 等属性会触发全量重建
  4. 依赖文档复用
    依赖文档默认随本地 crate 一起生成;若把 target/doc 整体缓存,docs.rs 无法访问时也不会重新去拉取 html,直接复用本地副本。
  5. 国内镜像加速
    $HOME/.cargo/config.toml 里配置 source.crates-io.replace-with = 'tuna'ustc',可把索引与原始文档源一并镜像,避免首次生成时因网络超时回退到源码渲染
  6. CI 缓存策略
    GitHub Actions / Gitee Go 中把 ~/.cargo/registry/cache~/.cargo/git/dbtarget/doc 三目录同时缓存,key 模板推荐 cargo-doc-${{ runner.os }}-${{ rustc_hash }}-${{ hashFiles('Cargo.lock') }},既保证工具链升级后重建,又能在锁文件不变时命中缓存。
  7. 只生成依赖文档
    使用 cargo doc --no-deps -p <pkg> 可排除本地 crate,CI 里先跑一遍 cargo doc 缓存依赖文档,再跑单元测试,节省整体流水线时间。

答案

cargo doc 的缓存分三层:

  1. 构建产物层:文档统一落在 target/doc,rustdoc 以 crate 为粒度做增量;通过 CARGO_TARGET_DIR 可把该目录挂载到共享存储,实现多人或 CI 复用。
  2. 依赖源码层~/.cargo/registry/cache 保存已下载的 .crate 压缩包,~/.cargo/git/db 保存 git 依赖;国内用户配置 清华或中科大镜像 后,首次拉取速度可从分钟级降到秒级。
  3. 网络层:若依赖启用了 #[doc(html_root_url = "https://docs.rs/...")],浏览器会跳转到 docs.rs;在离线机房可把 target/doc 整体作为静态站点,用 nginx 提供 /crate-name 路由,完全摆脱外网。

最佳实践:

  • 在 CI 里把 target/doc~/.cargo/registry/cache 与 rustc 版本、Cargo.lock 哈希一起作为缓存键;
  • 升级工具链后主动 bust cache,防止新旧 rustdoc 渲染差异导致隐式失效;
  • 对于大型单仓库,先执行 cargo doc --workspace --no-deps 生成依赖文档并上传至 内网 S3/Artifactory,后续测试与部署阶段直接拉取,节省 30%~50% 流水线时间

拓展思考

  1. 分布式缓存:如果团队规模上千人,可把 target/doc 做成 只读 FUSE 挂载点,结合 sccache 的 rustdoc 支持,实现 跨机器共享已渲染 HTML
  2. 版本化文档:利用 cargo-metadata 拿到精确版本号,把 target/doc 重命名为 target/doc/<rustc>-<timestamp>,通过 符号链接切换并行保留 nightly 与 stable 两套文档,方便回溯。
  3. 安全加固target/doc 里可能含有 #[doc(hidden)] 的私有 API 说明,在内网发布前可用 rustdoc --cap-lints forbid 重新扫描,防止敏感接口意外暴露