zccache
一款针对 C/C++ 和 Rust 的极速编译器缓存
受 sccache 启发,但针对本地优先使用进行了优化, 并采用激进的元数据缓存和文件系统监听。
快速安装
curl -LsSf https://github.com/zackees/zccache/releases/latest/download/install.sh | sh
powershell -ExecutionPolicy Bypass -c "irm https://github.com/zackees/zccache/releases/latest/download/install.ps1 | iex"
验证:
zccache --version
性能
基准图像由最近一次计划运行生成,并取代了手动维护的文本统计。完整结果、渲染后的 HTML 以及机器可读的 JSON 发布在
benchmark-stats 分支
以及 zackees.github.io/zccache。使用 ./perf.sh 在本地运行
相同的测试套件。
为什么 zccache 在热命中时快得多?
差异源于架构,而非更优的缓存:
| sccache | zccache | |
|---|---|---|
| IPC 模型 | 每次调用一个子进程(fork + exec + connect) | 持久守护进程,每次编译一条 IPC 消息 |
| 缓存查找 | 客户端对输入进行哈希,发送给服务器,服务器检查磁盘 | 守护进程将输入保存在内存中(文件监视器 + 元数据缓存) |
| 命中时 | 服务器从磁盘读取工件,通过 IPC 发送回 | 守护进程将缓存文件硬链接到输出路径(1 次系统调用) |
| 多文件 | 编译每个文件(不支持多文件缓存) | 并行进行每文件缓存查找,仅未命中项进入编译器 |
| 每次命中成本 | ~170ms(进程生成 + 哈希 + 磁盘 I/O + IPC) | ~1ms(内存查找 + 硬链接) |
使差异产生的架构增强:
- 文件系统监视器 — 一个后台
notify监视器实时跟踪文件变更,因此守护进程在你调用编译之前就已经知道输入是否已更改。命中时无需冗余的 stat/hash 操作。 - 内存元数据缓存 — 文件大小、mtime 和内容哈希存储在一个无锁
DashMap中。缓存键计算是内存查找,而非磁盘 I/O。 - 单往返 IPC — 每次编译是通过 Unix 套接字(或 Windows 上的命名管道)发送的一条带长度前缀的守护进程消息。无子进程生成,无重复握手。当前使用的线路协议是 v15 bincode;v16 prost 线路协议已准备就绪,位于由 zackees/running-process#234 跟踪的迁移之后。
- 硬链接交付 — 缓存命中通过硬链接将缓存产物链接到输出路径来提供服务——只需一次系统调用,而非读取 + 写入文件内容。
- 多文件快速路径 — 当构建系统在单次调用中传递 N 个源文件时,zccache 并行检查所有 N 个文件与缓存的匹配情况,立即提供命中结果,并将普通未命中编译到私有的每源输出集中。
与 sccache 的功能对比
完整矩阵位于 docs/FEATURE-MATRIX.md,由 docs/feature-matrix.yaml 生成 — 这些表格是自动渲染的,请勿手动编辑。
| 功能 | zccache | sccache |
|---|---|---|
| 链接缓存(产物 + 同级文件) | 是 | 否 |
| Emscripten (emcc / em++) | 是 | 否 |
| 多文件编译快速路径 | 是 | 否 |
| clang-tidy(静态分析结果缓存) | 是 | 否 |
| include-what-you-use (IWYU) | 是 | 否 |
| 持久化守护进程,亚毫秒级 IPC | 是 | 部分 |
| 命中时安全硬链接交付 | 是 | 否 |
| ZCCACHE_PATH_REMAP=auto 跨工作树共享 | 是 | 否 |
| GitHub Actions Cache(原生 API 客户端) | 是 | 是 |
| 每次命中成本 | 是 | 部分 |
完整功能对比(zccache 与 sccache)
请参阅 docs/FEATURE-MATRIX.md 查看包含注释和证据列的长格式视图。
缓存范围
| 功能 | zccache | sccache |
|---|---|---|
| C/C++ 对象缓存 | 是 | 是 |
| 链接缓存(产物 + 同级文件) | 是 | 否 |
| Rust rustc 缓存(--emit=metadata, --emit=link, extern crate 哈希) | 是 | 是 |
| Emscripten (emcc / em++) | 是 | 否 |
| wasm-ld 链接 | 是 | 否 |
| CUDA / nvcc | 否 | 是 |
| MSVC cl.exe / link.exe | 部分 | 是 |
| 多文件编译快速路径 | 是 | 否 |
| 响应文件 (.rsp) 展开 | 是 | 部分 |
工具覆盖
| 功能 | zccache | sccache |
|---|---|---|
| clang-tidy(静态分析结果缓存) | 是 | 否 |
| include-what-you-use (IWYU) | 是 | 否 |
| rustfmt | 是 | 否 |
| clippy | 是 | 否 |
| cargo check / cargo build | 是 | 是 |
构建系统集成
| 功能 | zccache | sccache |
|---|---|---|
| Ninja (通过 CC / CXX 启动器) | 是 | 是 |
| CMake (CMAKE_C_COMPILER_LAUNCHER) | 是 | 是 |
| Meson (native file) | 是 | 是 |
| Make | 是 | 是 |
| RUSTC_WRAPPER | 是 | 是 |
| setuptools-rust / maturin / scikit-build-core | 是 | 是 |
架构
| 功能 | zccache | sccache |
|---|---|---|
| 持久化守护进程,亚毫秒级 IPC | 是 | 部分 |
| 单往返 IPC (长度前缀 bincode) | 是 | 否 |
| 命中时安全硬链接交付 | 是 | 否 |
| Reflink 交付 (ReFS, btrfs/XFS, APFS) | 是 | 否 |
| 内存元数据缓存 (DashMap) | 是 | 否 |
| 文件系统监视器 (基于 notify) | 是 | 否 |
| 内容寻址工件存储 | 是 | 是 |
| 协议版本控制 (线格式升级策略) | 是 | 部分 |
| 编译日志 (JSONL 诊断) | 是 | 否 |
| 会话统计 / 每次构建命中率 | 是 | 部分 |
| 崩溃转储器 (CLI + 守护进程) | 是 | 否 |
Worktree / 多检出
| 功能 | zccache | sccache |
|---|---|---|
| ZCCACHE_PATH_REMAP=auto 跨 worktree 共享 | 是 | 否 |
| C/C++ -ffile-prefix-map 注入 | 是 | 否 |
| Rust --remap-path-prefix 注入 | 是 | 否 |
| 严格路径验证 | 是 | 否 |
存储后端
| 功能 | zccache | sccache |
|---|---|---|
| 本地文件系统 | 是 | 是 |
| S3 | 否 | 是 |
| Google Cloud Storage | 否 | 是 |
| Redis | 否 | 是 |
| Memcached | 否 | 是 |
| Azure Blob | 否 | 是 |
| GitHub Actions Cache (原生 API 客户端) | 是 | 是 |
| 分布式调度器 / 构建农场 | 否 | 是 |
平台 / 打包
| 功能 | zccache | sccache |
|---|---|---|
| Linux x86_64 | 是 | 是 |
| Linux aarch64 | 是 | 是 |
| macOS x86_64 | 是 | 是 |
| macOS arm64 (Apple Silicon) | 是 | 是 |
| Windows x86_64 | 是 | 是 |
| Windows arm64 | 是 | 部分支持 |
| Windows Defender 排除辅助工具 | 是 | 否 |
| PyPI wheels | 是 | 否 |
| crates.io 发布 | 是 | 是 |
| GitHub Action (composite) | 是 | 是 |
| 目标快照缓存 + zccache 预热回填 | 是 | 否 |
性能表现
| 功能 | zccache | sccache |
|---|---|---|
| 每次命中成本 | 是 | 部分支持 |
| 命中时保留 mtime | 是 | 部分支持 |
| 编译器子进程优先级 (CPU 95% 时自动节流) | 是 | 否 |
可靠性
| 功能 | zccache | sccache |
|---|---|---|
| 硬链接缓存污染防护与检测 | 是 | 部分支持 |
更广泛的工具覆盖范围 — zccache 支持其他编译器缓存不支持的模式:
| 模式 | 描述 |
|---|---|
| 多文件编译 | clang++ -c a.cpp b.cpp c.cpp — 按文件缓存,支持并行查找 |
| 响应文件 | 嵌套的 .rsp 文件,包含数百个标志 — 完全展开并缓存 |
| clang-tidy | 静态分析结果被缓存并重放 |
| include-what-you-use | IWYU 输出按翻译单元缓存 |
| Emscripten (emcc/em++) | WebAssembly 编译端到端缓存 |
| wasm-ld | WebAssembly 链接被缓存 |
| rustfmt | 递归格式化始终运行;显式非递归格式化可以使用内容标记 |
| clippy | Lint 结果被缓存 |
| Rust 检查与构建 | cargo check 和 cargo build,支持外部 crate 内容哈希 |
链接时副作用
链接器驱动程序有时会产生除指定的 -o 输出之外的更多文件。Windows 和
MinGW 工具链可能会在可执行文件旁边部署运行时 DLL,MSVC 链接可能会
生成 PDB 文件,而 Emscripten 链接可能会创建 .wasm、.map 或 .js
伴随文件。如果缓存命中仅恢复了主二进制文件,即使缓存的二进制文件本身是正确的,
这些同级文件也可能缺失。
对于链接调用,zccache 在运行真实链接器之前对输出目录进行快照,记录由成功链接创建或修改的兄弟文件,并将它们与主要链接产物一起存储。后续的缓存命中会将完整的产物集恢复到输出目录,因此诸如 clang-tool-chain 运行时部署等工具无需针对每个构建系统的链接后钩子即可正常工作。
Windows:Defender 排除项
Windows Defender 的实时扫描器会在写入返回之前检查每个新写入的文件。zccache 在每次冷构建中会写入数百个 .rmeta / .rlib / .o 文件,而在未排除的缓存目录中,每个文件都要支付 Defender 的往返开销——多分钟的延迟是常态,且对 zccache 自身的遥测不可见(守护进程看到其写入正常完成;但在 write() 和字节到达磁盘之间,墙钟时间急剧膨胀)。
修复方法是一次性的排除。zccache 附带了一个辅助工具,因此您无需手动编写 PowerShell:
# Show whether the cache root is excluded. Read-only — no elevation needed.
zccache defender-exclusions check
# Add the exclusion. Requires an elevated PowerShell or Administrator cmd.
zccache defender-exclusions add
# Undo.
zccache defender-exclusions remove
在 Windows 上,如果缓存目录尚未被排除,守护进程在启动时会在 stderr 中打印一行警告。使用 ZCCACHE_QUIET=1 将其静默。
defender-exclusions 子命令在所有平台上均可用——非 Windows
主机将打印 Defender exclusion is Windows-only. 并以 0 退出,以便跨平台
脚本可以无条件地调用它。
安装
curl -LsSf https://github.com/zackees/zccache/releases/latest/download/install.sh | sh
powershell -ExecutionPolicy Bypass -c "irm https://github.com/zackees/zccache/releases/latest/download/install.ps1 | iex"
这将直接从 GitHub Releases 安装独立的 原生 Rust 二进制文件(zccache、zccache-daemon
和 zccache-fp)。
默认安装位置:
- Linux/macOS 用户安装:
~/.local/bin - Linux/macOS 全局安装:
/usr/local/bin - Windows 用户安装:
%USERPROFILE%\.local\bin - Windows 全局安装:
%ProgramFiles%\zccache\bin
全局安装示例:
curl -LsSf https://github.com/zackees/zccache/releases/latest/download/install.sh | sudo sh -s -- --global
powershell -ExecutionPolicy Bypass -c "$env:ZCCACHE_INSTALL_MODE='global'; irm https://github.com/zackees/zccache/releases/latest/download/install.ps1 | iex"
每个 GitHub 版本还发布了独立的按平台划分的归档:
- Linux:
zccache-vX.Y.Z-x86_64-unknown-linux-musl.tar.gz,zccache-vX.Y.Z-aarch64-unknown-linux-musl.tar.gz - macOS:
zccache-vX.Y.Z-x86_64-apple-darwin.tar.gz,zccache-vX.Y.Z-aarch64-apple-darwin.tar.gz - Windows:
zccache-vX.Y.Z-x86_64-pc-windows-msvc.zip,zccache-vX.Y.Z-aarch64-pc-windows-msvc.zip
如果您更喜欢 pip install zccache,PyPI 仍然可用;这些 wheel 也会将原生二进制文件直接安装到您的 PATH 中。可用的预构建 wheel 如下:
| 平台 | 架构 |
|---|---|
| Linux | x86_64, aarch64 |
| macOS | x86_64, Apple Silicon |
| Windows | x86_64 |
验证安装:
zccache --version
Rust crates 也发布在 crates.io 上。主要的可安装/运行时 crates 是:
zccache-clizccache-daemonzccache-corezccache-hashzccache-protocolzccache-fscachezccache-artifact
将其作为 sccache 的直接替代方案使用——只需替换 zccache:
集成摘要
RUSTC_WRAPPER=zccache cargo build
export CC="zccache clang"
export CXX="zccache clang++"
- Rust:设置
RUSTC_WRAPPER=zccache或向.cargo/config.toml添加rustc-wrapper = "zccache"。 - Bash:在您的 shell 或 CI 环境中一次性导出
RUSTC_WRAPPER、CC和CXX。 - Python:在调用
cargo或clang时,通过subprocess环境变量传递RUSTC_WRAPPER、CC和CXX。 - 首先检查的命令:
zccache --version、zccache start、zccache status。
Rust zccache 集成
使用 zccache 作为 Cargo 的编译器包装器:
# one-off invocation
RUSTC_WRAPPER=zccache cargo build
RUSTC_WRAPPER=zccache cargo check
# optional: start the daemon explicitly
zccache start
添加到 .cargo/config.toml 以自动使用:
[build]
rustc-wrapper = "zccache"
推荐的项目本地配置:
[build]
rustc-wrapper = "zccache"
[env]
ZCCACHE_CACHE_DIR = { value = "/tmp/.zccache", force = false }
支持 --emit=metadata(cargo check)、--emit=dep-info,metadata,link(cargo build)、
extern crate 内容哈希,以及可缓存的 crate 类型,例如 lib、rlib
和 staticlib。Proc-macro 和二进制 crate 会直接传递而不进行缓存,
这与通常的 sccache 行为一致。
有用的 Rust 工作流命令:
# inspect status
zccache status
# clear local cache
zccache clear
# validate wrapper is active
RUSTC_WRAPPER=zccache cargo clean
RUSTC_WRAPPER=zccache cargo check
zccache status
缓存根目录覆盖
设置 ZCCACHE_CACHE_DIR 以将每个 zccache 缓存和状态路径隔离在特定根目录下:
export ZCCACHE_CACHE_DIR="$HOME/.soldr/cache/zccache"
zccache start
zccache status
当设置且非空时,该覆盖值将用于 artifacts/、tmp/、
depgraph/、index.bin、crashes/、logs/、守护进程锁文件、下载
守护进程状态以及默认守护进程端点。因此,不同的缓存根目录
将使用不同的守护进程实例,除非显式设置了 ZCCACHE_ENDPOINT。
相对覆盖路径将相对于当前工作目录进行规范化。
缓存大小与保留策略
守护进程负责其确切的有效缓存根目录的保留策略;无需系统 调度器。缓存工件默认占文件系统容量的 5%, 限制在 40-200 GiB 之间,并在需要时缩减以保留恢复所需的空闲空间。 对于容量不足以达到 30 GiB 恢复目标的文件系统,最多保留 卷容量的一半,以确保缓存保留有用的非零预算。 最多选择一个覆盖值:
# Fixed artifact-store budget in bytes
export ZCCACHE_CACHE_SIZE_BYTES=42949672960
# Or a percentage of filesystem capacity (1 through 100)
export ZCCACHE_CACHE_SIZE_PERCENT=5
当预算达到 85% 时,超过四天的条目将按 LRU 顺序逐出至 70%。当达到 100% 或空闲空间不足时,无论条目年龄如何,均应用 LRU 逐出,直到使用率达到 80% 且恢复空闲空间。每日全量扫描会过期超过 30 天的条目,包括在守护进程保持存活但无编译流量的期间。预算涵盖已分配的文件产物以及待写入的数据;小型的根本地索引、日志和元数据不在其范围内。
守护进程命名空间覆盖
设置 ZCCACHE_DAEMON_NAMESPACE 以针对同一用户账户或缓存根目录运行第二个 zccache 守护进程身份,而不共享默认的套接字、命名管道、锁文件或生命周期日志:
export ZCCACHE_DAEMON_NAMESPACE=soldr-dev
zccache start
zccache status --json
未设置/默认命名空间保留历史端点和路径名称。
非空命名空间值会被清理为路径安全的 ASCII 组件。对于
soldr 开发,请使用 ZCCACHE_DAEMON_NAMESPACE=dev 或更具体的值,
例如 soldr-dev。zccache-daemon-dev 并非单独发布的二进制文件;它
由文档中记录的命名空间模式表示,因此 zccache CLI、包装器
模式和直接守护进程入口点都解析为相同的守护进程身份。
所有由守护进程拥有的可变状态(索引、元数据快照、依赖图、
编译器/系统包含哈希、日志、下载标记和运行时
伴随文件)都在 <cache>/daemon-state/<namespace> 下遵循该稳定命名空间。
ZCCACHE_DAEMON_STATE_DIR 覆盖允许嵌入的代理直接提供
已派生的私有目录;遗留的无命名空间布局仍然
有效,且带命名空间的守护进程会从缺失中重建,而不是依赖于
共享快照的成功迁移。
守护进程线路迁移
在 v16 prost 架构和
调度器基础落地期间,活动的守护进程线路仍保持为 v15 bincode。ZCCACHE_DAEMON_WIRE=prost 保留给
未来的 prost 默认值,而 ZCCACHE_DAEMON_WIRE=bincode 是文档中记录的
回退拼写,用于在迁移期间保持 v15 行为。
工作树缓存共享
不可变的暂存输出
受支持的编译器和归档输出默认使用不可变的暂存通道。zccache 将工具重定向到一个私有目录,将完整的输出集作为一个代次发布,然后才将请求的路径实体化。这可以防止部分缓存条目,并使发布、救援和交付在会话 phase_profile.staged 遥测中可观察。Rust 输出路径在发布前被重映射到一个稳定的逻辑标记;依赖文件和捕获的流被重新水合到每个调用者请求的目标位置,因此私有暂存根目录的差异不会造成虚假的发布冲突。多源编译器发布还要求一个原生的每文件变更序列,以便 A 到 B 再到 A 的输入重写不能伪装为未更改的快照。Windows 使用文件 USN。Unix ctime 只是一个时间戳,并且在粗略时钟滴答内可能会重复,因此 Unix 执行暂存的编译器结果,但在可用的真实变更计数器之前保守地抑制其多源缓存发布。
ZCCACHE_STAGED_ARTIFACTS=off 是回滚终止开关。诊断值
rust 和 c-cpp 将暂存限制在一个编译器家族内;exec 启用精确的
通用执行;并且 all 还将声明的链接器输出纳入暂存。这些兼容性值
在 1.13.x 发布系列中保持受支持,并且在 1.14 之前不会被考虑移除。当暂存
被禁用时,遗留的 v1 和 pack 条目仍然可读。
私有编译器输出通常位于 {cache_root}/staging 之下。设置
ZCCACHE_STAGING_DIR 以选择更短的基路径(例如,用于 Windows
链接器);zccache 仅在该位置创建并清理其 zccache-staging 子目录。
持久化工件保留在配置的缓存中,且暂存发布保持
启用状态。嵌入式主机可通过
ZccacheService::start_with_options 设置 ZccacheStartOptions::staging_root。
安全的文件系统实体化
缓存命中交付由能力驱动。zccache 仅探测一次实际的源和目标 卷对并缓存结果:优先使用 reflink(真正的写时复制),否则 zccache 使用已注册的只读硬链接, 配合写前复制和监视器辅助验证,最后在跨卷或受限文件系统上执行普通复制。 每一层的正确性均相同; 仅磁盘共享方式有所不同。
设置 ZCCACHE_DISABLE_REFLINK=1 以诊断或绕过块克隆。只读
硬链接强制执行默认开启;仅将 ZCCACHE_COW_READONLY=0 作为
兼容性逃生舱设置。reflink 和
硬链接的缓存文件 mtime 会被保留,且永远不会打上当前时间戳。在 Windows 上,将
缓存和构建目标都放置在 ReFS Dev Drive 上可提供最强的真正 COW
层级;日常使用请优先选择基于真实分区的 Dev Drive 而非 VHDX。
zccache 可以在编译等效时,在兄弟 Git worktree 之间共享缓存条目。这针对的是多智能体工作流,其中同一仓库的多个检出在不同的绝对路径下构建相同的 Rust crate。守护进程会为每个编译请求检测其所在的 Git 根目录,将该根目录作为基准,对项目本地的源文件、依赖项、当前工作目录以及安全路径参数进行规范化处理,并将缓存命中写回当前 worktree 请求的输出路径。
对于 C/C++ 项目,请启用编译器路径重映射,以便 __FILE__、调试/源路径以及兼容的链接搜索路径等对路径敏感的输出,可以在等效的 Git 根目录之间共享缓存条目:
export ZCCACHE_PATH_REMAP=auto
在自动模式下,zccache 会检测所在的 Git 根目录,并为 GCC/Clang 系列的编译
未命中内部添加 root/cwd -ffile-prefix-map=...=. 参数。原始构建文件无需自行注入这些标志。
对于链接请求,zccache 会规范化已知的本地工作区输入/搜索路径
以用于缓存标识,同时保留物理输出和面向运行时的路径。
当自动 Git 根目录检测不可靠时,
或当包装器/测试需要显式定义规范化根目录时,请设置 ZCCACHE_WORKTREE_ROOT:
export ZCCACHE_WORKTREE_ROOT="$PWD"
RUSTC_WRAPPER=zccache cargo build
对于 Rust 项目,请使用相同的 path-remap 指令:
export ZCCACHE_PATH_REMAP=auto
RUSTC_WRAPPER=zccache cargo build
在自动模式下,zccache 会在 macOS、
Linux 和 Windows 上自动发现 Git worktree 根目录,
并在需要时添加一个覆盖根目录的 rustc --remap-path-prefix=...=.。
仅将 ZCCACHE_WORKTREE_ROOT 设置为高级覆盖选项,用于
非 Git 检出或自动根目录检测不可靠的特殊构建布局。
ZCCACHE_PATH_REMAP=auto 指示 zccache 在能够证明其安全时应用编译器特定的路径重映射,
例如 C/C++ -ffile-prefix-map、Rust
--remap-path-prefix 以及平台等效项。其目标是使生成的
源路径、调试路径和宏路径在等效 worktree 之间保持稳定,
而无需每个构建生成器都正确指定这些标志。构建工具用于依赖跟踪的物理
路径,例如 Ninja depfiles,
必须对当前检出保持可用。
该覆盖项应指向等效 worktree 共享的逻辑项目根目录。该根目录下的路径 可能针对缓存标识进行规范化。 该根目录之外的路径保持绝对路径,除非 zccache 对 它们有特定的安全规则,因此工具链文件、sysroots、检出之外的生成文件 以及其他外部输入不会意外地变为共享。
Worktree 共享是有意保守的。如果 zccache 无法证明某个 编译是根目录等效的,它将回退到现有的特定路径缓存键 或记录一次未命中。诊断信息和会话日志区分正常的同根 命中与 worktree 等效命中,并报告保守原因,例如:
git_root_unavailable- 没有 Git 根目录且没有显式的ZCCACHE_WORKTREE_ROOT。path_outside_root- 输入路径位于检测到的/覆盖的根目录之外。path_sensitive_arg- 诸如--remap-path-prefix、调试路径标志, 或带有未知绝对路径的选项可能会影响输出的结果。content_hash_mismatch- 根相对路径匹配但文件内容不同。toolchain_mismatch- 编译器或相关工具链的输入不同。unsupported_language- 该调用不受感知工作树的 规范化规则覆盖。
支持的工作树等效路径包括 Rust rustc 编译,包括
通过 --extern 使用的依赖项工件,以及通过现有
depgraph 上下文/工件键进行的 C/C++ 编译。请求级快速路径仅在验证当前工作树记录的输入哈希后
才提供跨根命中;否则 zccache 回退到正常的 depgraph 检查或特定路径的
未命中。
子代理 / 并行工作树配方
典型的多代理工作流为每个 git worktree 运行一个子代理,所有
工作树都从同一仓库检出到同级路径下。如果没有重映射,每个
工作树都有不同的绝对编译输入,因此即使源代码内容相同,每个代理也会支付完整的编译
成本。通过在编排器级别导出一次 ZCCACHE_PATH_REMAP=auto,
每个子代理在每个工作树中的编译共享同一个逻辑缓存。
-
创建工作树。任何
git worktree add产生的(或同级的git clone)都可以 — zccache 会自动检测每个包含的 Git 根目录:git worktree add ../agent-a -b agent-a main git worktree add ../agent-b -b agent-b main git worktree add ../agent-c -b agent-c main -
在启动代理之前,一次性导出 remap 指令。每个 子进程都会继承它;无需针对每个 worktree 进行配置:
export ZCCACHE_PATH_REMAP=auto
然后以与单个 checkout 相同的方式将 zccache 集成到构建中。对于 Rust:
export RUSTC_WRAPPER=zccache
对于 C/C++,请使用你的构建系统已支持的启动器模式
(Make 和 Ninja 会自动选择 CC/CXX):
# Make / Ninja / plain shell
export CC="zccache clang"
export CXX="zccache clang++"
# CMake — set once, applies to every target
set(CMAKE_C_COMPILER_LAUNCHER zccache)
set(CMAKE_CXX_COMPILER_LAUNCHER zccache)
对于 Emscripten,替换为 emcc / em++:
export CC="zccache emcc"
export CXX="zccache em++"
ZCCACHE_PATH_REMAP=auto 导出是解锁跨工作树共享的关键,适用于代理编译的任何语言;包装器的选择
只是常规的单检出配置。
-
在各自的工作树中并行启动子代理。第一个编译单元的代理 会填充缓存;其他代理即使绝对路径不同,也能获得等效于工作树的命中:
(cd ../agent-a && agent-runner ...) & (cd ../agent-b && agent-runner ...) & (cd ../agent-c && agent-runner ...) & wait -
验证其是否正常工作。
zccache status将 worktree 等效命中与同根命中分开报告,并且如果请求回退,每个会话的日志都会包含门控原因(path_outside_root、content_hash_mismatch、toolchain_mismatch等——参见上述列表)。
以下是一些值得了解的事项:
- 一个守护进程,一个缓存。默认情况下,所有工作树共享同一个 zccache 守护进程和
工件存储——请勿为每个工作树设置
ZCCACHE_CACHE_DIR,否则 会破坏共享机制。 - 自动检测需要 Git 检出。守护进程会遍历编译
当前工作目录的祖先目录以查找
.git(文件或目录),因此普通的git clone和git worktree add检出均可正常工作,但原始源代码树 (tarball 解压、归档负载、没有.git的自定义构建布局) 则不行。对于这些情况,请将ZCCACHE_WORKTREE_ROOT="$PWD"(或任何绝对 路径)设置为您希望用于规范化缓存键的逻辑项目根目录。 如果没有检测到 Git 根目录,也没有显式覆盖,ZCCACHE_PATH_REMAP=auto将不起作用,并且会话日志将报告git_root_unavailable。 - 用户提供的重映射标志具有优先权。如果您的构建已经传递了
-ffile-prefix-map=<root>=...(C/C++/Emscripten)或--remap-path-prefix=<root>=...(Rust),其中<root>是自动检测到的 工作树根目录,zccache 将原样使用您的标志,并且不会注入 重复项。检查是按标志和按路径进行的:只有与-ffile-prefix-map/--remap-path-prefix工作树根目录匹配的标志才会抑制自动注入; 相关标志如-fdebug-prefix-map、-fmacro-prefix-map、-fcoverage-prefix-map和-fprofile-prefix-map则不会。如果当前工作目录 与检测到的根目录不同,且您未提供匹配的-ffile-prefix-map=<cwd>=.,zccache 仍可能为该路径注入一个。 自动注入的重映射是放置在用户提供的重映射之前的回退重映射, 因此更窄的重叠用户重映射仍然是后生效的获胜规则。 - 相同内容保证。跨工作树命中会验证内容哈希 每个输入。如果两个工作树在某个文件上出现了分歧,第二次编译 将未命中并重新编译——缓存不会在兄弟工作树之间被污染( 在 #197 中固定的不变量)。
- 实测收益。
perf_cpp_sibling_remap_warm/perf_rustc_sibling_remap_warm基准测试(在 #238 中引入)证实,跨兄弟 工作树的暖态命中比裸编译和 sccache 快一个数量级,尽管 sccache 根本无法在兄弟根目录之间共享。 - 缓存的诊断信息遵循相同的开关。zccache 将编译器的
stdout 和 stderr 与目标文件负载一起缓存,并在每次
命中时逐字重放它们。使用
ZCCACHE_PATH_REMAP=auto时,原始编译会看到 注入的-ffile-prefix-map标志,因此缓存的诊断信息已经是 工作树中立的,并且可以干净地提供给兄弟克隆。使用ZCCACHE_PATH_REMAP=off(默认值)时,编译器会将绝对路径 输出到 stderr——例如,一个包含指向C:\Users\me\dev\fastled5\src\...的包含跟踪的警告——而这些字节正是每次 跨工作树命中重放到新构建中的内容。.obj机器代码 本身仍然由 #474/#489 修复针对非-auto情况进行清理 (针对 PCH/MSVC 的每工作树加盐;C/C++ 调试信息中的绝对路径 仅在其标志缺失时才会出现),因此污染是表面上的: 令人困惑的诊断信息,绝不会产生错误的代码生成。设置ZCCACHE_PATH_REMAP=auto以在源头清理诊断信息,或在升级到 1.11.9 之后运行zccache clear以丢弃在每工作树加盐(#474,发布于 1.11.8)之前捕获的 修复前条目 以及请求缓存作用域(#489,已在 1.11.9 中发布)已落地——这些条目 仍作为跨工作树命中提供服务,并在诊断流中以及(对于 PCH/MSVC) 工件字节本身中携带其原始克隆路径。
C/C++ 依赖策略
默认情况下,zccache 会向 GCC 和 Clang 请求 -MD 依赖清单,并跟踪每个由编译器选择的用户和系统头文件。该清单是一次性成本:后续的 C 和 C++ 请求将使用相同的直接模式依赖图,而不会重新发现包含闭包。
--fast 是一个广泛的可选预设。它目前通过使用 -MMD 从新的 C/C++ 清单中省略系统头文件;--skip-system-headers 直接选择该行为。显式的细粒度设置会覆盖预设:
zccache --fast clang++ -c src/main.cpp -o main.o
zccache --skip-system-headers clang -c src/main.c -o main.o
zccache --fast --scan-system-headers clang++ -c src/main.cpp -o main.o
配置等效项为 ZCCACHE_FAST=1 和
ZCCACHE_SCAN_SYSTEM_HEADERS=0|1。策略标志必须出现在编译器
名称之前;其后同名的参数将直接传递给编译器。
用户提供的依赖开关仍具有权威性:显式的 -MD
清单将被完整跟踪,而显式的 -MMD 清单则被信任为
编译器分类的用户头文件集。
“系统头文件”遵循编译器的标准分类,包括 C 库、C++ 标准库、SDK 以及编译器内置头文件。跳过 它们是以正确性换取速度:如果 SDK/头文件更新未更改 编译器可执行文件,则可能导致其他方面匹配的缓存对象过期。
严格路径验证
使用 --strict-paths 或 ZCCACHE_STRICT_PATHS 在编译器路径
标志以可能使 #pragma once 在 Windows 上产生混淆的方式拼写时快速失败。
zccache --strict-paths=absolute clang++ -c src/main.cpp -IC:/work/project/include
ZCCACHE_STRICT_PATHS=consistent ninja
模式:
off禁用验证。consistent允许相对路径或绝对路径,但拒绝在同一路径内或同一调用中跨受检路径标志混用/和\分隔符。absolute要求受检路径标志为不含/./或/../组件的正斜杠绝对路径。ZCCACHE_STRICT_PATHS=1映射到此 模式。
受检标志包括 -I、-isystem、-iquote、-idirafter、-include、
-include-pch、-imacros、-F、-iframework、-imsvc 以及 MSVC /I。
响应文件参数在由守护进程展开后进行检查。
编译器子进程优先级
编译器和链接器子进程默认以 ZCCACHE_COMPILE_PRIORITY=auto 运行。自动模式在总 CPU
使用率低于 95% 时保持编译器子进程为正常优先级,当机器饱和时
将其降至低优先级。在构建命令或守护进程环境中设置该变量以
覆盖它:
| 值 | 行为 |
|---|---|
auto | 默认。在 CPU 利用率低于 95% 时使用 normal,在 95-100% 利用率时使用 low。 |
low | 降低编译器优先级(Unix/macOS 上为 nice +10,Windows 上为 BELOW_NORMAL_PRIORITY_CLASS)。 |
normal | 保留继承的进程优先级以获得最大吞吐量。 |
idle | 更保守的后台模式(Unix/macOS 上为 nice +19,Windows 上为 IDLE_PRIORITY_CLASS)。 |
high | 用于实时基准测试的高优先级模式(在允许的情况下,Unix/macOS 上为 nice -5,Windows 上为 HIGH_PRIORITY_CLASS)。 |
不支持的优先级更改会软失败并记录守护进程日志消息,且不会
破坏编译。无效值会发出警告并回退到 low。
Bash 集成
对于 shell 驱动的构建,请在你的会话或 CI 步骤中导出一次该包装器:
export RUSTC_WRAPPER=zccache
export CC="zccache clang"
export CXX="zccache clang++"
zccache start
cargo build
ninja
如果你希望此设置在交互式 shell 中生效,请将其添加到 ~/.bashrc:
export RUSTC_WRAPPER=zccache
export PATH="$HOME/.local/bin:$PATH"
在 Bash 中获取每次构建的统计信息:
eval "$(zccache session-start --stats)"
cargo build
zccache session-end "$ZCCACHE_SESSION_ID"
Python 集成
Python 项目可以通过 subprocess、构建后端或扩展模块构建,在调用 Rust 或 C/C++ 工具链时使用 zccache。
import os
import subprocess
env = os.environ.copy()
env["RUSTC_WRAPPER"] = "zccache"
env["CC"] = "zccache clang"
env["CXX"] = "zccache clang++"
subprocess.run(["cargo", "build", "--release"], check=True, env=env)
这对于以下场景有用:
setuptools-rustmaturinscikit-build-core- 自定义 Python 构建/测试框架,其通过 shell 调用
cargo、clang或clang++
使用 maturin 的示例:
RUSTC_WRAPPER=zccache maturin build
使用 Python 驱动的示例 cargo check:
subprocess.run(["cargo", "check"], check=True, env=env)
GitHub Actions
zccache 提供了一个复合 GitHub Action,它用一个操作替换了 两者 mozilla-actions/sccache-action 和 Swatinem/rust-cache。
最小示例
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.94.1
- uses: zackees/zccache@main
with:
shared-key: ${{ runner.os }}
- run: cargo build --release
- run: cargo test
# REQUIRED: always clean up at end of job
- if: always()
uses: zackees/zccache/action/cleanup@main
多平台矩阵
name: CI
on: [push, pull_request]
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- { os: ubuntu-24.04, target: x86_64-unknown-linux-gnu }
- { os: ubuntu-24.04-arm, target: aarch64-unknown-linux-gnu }
- { os: macos-15, target: aarch64-apple-darwin }
- { os: macos-14, target: x86_64-apple-darwin }
- { os: windows-2025, target: x86_64-pc-windows-msvc }
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.94.1
targets: ${{ matrix.target }}
# One action replaces sccache + rust-cache
- uses: zackees/zccache@main
with:
shared-key: ${{ matrix.target }}
- run: cargo build --release --target ${{ matrix.target }}
- run: cargo test --target ${{ matrix.target }}
- if: always()
uses: zackees/zccache/action/cleanup@main
功能说明
该 action 提供两个默认缓存层,外加一个可选的目标快照层
以及 zccache warm,以实现近乎即时的后续构建:
| 层 | 内容 | 替代项 | 效果 |
|---|---|---|---|
| 编译缓存 | 通过 zccache 守护进程实现的按单元 .o/.rlib 文件 | sccache | 每次缓存命中约 1ms,而 sccache 约 170ms |
| Cargo 注册表缓存 | ~/.cargo/registry/ + ~/.cargo/git/ | Swatinem/rust-cache | 避免重新下载 crates |
| 目标快照缓存 | 排除 incremental/ 的 target/ tarball | (新增) | Cargo 同时看到目标输出和指纹 |
zccache warm | 从编译缓存回填 target/deps/ | (新增) | 在 cargo 运行前恢复缺失的工件 |
在设置阶段,该 action 安装 zccache,然后在 GitHub Actions 缓存运行时可用时,通过原生
zccache gha-cache 后端恢复缓存。
当该运行时缺失时,回退到 actions/cache。当
cache-target: true 被设置时,它还提取目标快照,运行
zccache warm 以回填缓存的 .rlib/.rmeta 文件,并将所有
时间戳设置为单一一致的值。
在清理阶段:停止守护进程并保存已启用的缓存。原生后端保存
编译缓存、cargo 注册表归档以及可选的目标
快照,无需在 workflow 中手动执行缓存步骤。当启用前缀
restore-fallback 时,该 action 仍使用 actions/cache 用于该
回退路径。目标快照在保存前会被修剪并进行大小检查。
CI 基准测试结果
在 ubuntu-24.04 上构建 zccache-core (14 个 crates) 时测量:
| 场景 | 裸编译 | sccache | zccache |
|---|---|---|---|
| 第 1 次 CI 运行(干净目标) | 5,315ms | 3,261ms | 2,194ms |
| 第 2 次 CI 运行(缓存目标) | 5,315ms | 3,261ms | ~200ms |
后续 CI 运行比 sccache 快 15 倍。 零重新编译 — cargo 将所有指纹视为新鲜并立即打印 Finished。
工作原理:
- 使用
cache-target: true首次运行:冷构建,填充 zccache 编译缓存并保存有界的目标快照。 - 使用
cache-target: true第二次运行:恢复目标快照,运行zccache warm作为回填,更新时间戳,然后cargo build在无需重新编译的情况下完成。
zccache warm 读取磁盘上的工件索引(无需守护进程)并按 Cargo.lock 进行过滤 — 仅恢复与 lockfile 中 crate 匹配的工件。这是一种速度优化,而非完整的完整性验证过程:已加热的工件被视为可信,Cargo 预期会拒绝或重新构建任何不兼容的内容。
输入
| 输入 | 默认值 | 描述 |
|---|---|---|
cache-cargo-registry | true | 缓存 cargo 注册表索引 + crate 文件 + git 依赖 |
cache-compilation | true | 通过 zccache 守护进程缓存编译单元 |
cache-target | false | 缓存 target 快照 + 运行 zccache warm;仅针对 target 快照值得占用磁盘预算的工作流启用 |
target-snapshot-mode | hot | hot 保存 Cargo 元数据以及作业期间读取或修改的 target 文件;full 保存修剪后的 target 树 |
target-snapshot-max-size | 2GiB | 当修剪后的快照超过此大小时,跳过或使 target 快照保存失败;使用 0 表示无限制 |
target-snapshot-too-large | skip | skip 过大的 target 快照或 fail 清理 |
target-prune-incremental | true | 在创建快照前移除 target/**/incremental |
target-prune-build-script-out | false | 在创建快照前移除 target/**/build/*/out |
compilation-restore-fallback | true | 允许编译缓存恢复时使用前缀回退 |
target-restore-fallback | false | 允许 target 快照恢复时使用前缀回退 |
target-dir | target | cargo target 目录的路径 |
shared-key | "" | 用于矩阵隔离的额外键(通常是目标三元组) |
zccache-version | latest | 要安装的版本 |
save-cache | true | 为 PR 构建设置 false(仅恢复,节省缓存预算) |
恢复策略
该操作现在对两个缓存层采用不同的处理方式:
- 编译缓存回退默认保持启用。这能在相邻提交之间保留快速的增量复用,同时仍允许 zccache 在
rustc实际运行时验证缓存命中。 - 目标快照回退默认禁用。在不同的源树之间复用过时的 Cargo 指纹和构建脚本输出,可能会让 PR 合并引用看起来是新鲜的,而实际上并非如此。
- 目标快照默认禁用,因为 Cargo 不会垃圾回收
target/。启用时,默认的target-snapshot-mode: hot会保存 Cargo 新鲜度元数据以及作业期间读取或修改的目标文件,而不是归档整个树。仅对目标目录已知保持有界的紧密范围作业使用target-snapshot-mode: full。 - 目标快照保存默认修剪
target/**/incremental,可选修剪target/**/build/*/out,并在修剪后的快照超过target-snapshot-max-size时跳过保存。
目标快照是 cache-target: true
工作流的遗留仅操作行为。soldr/setup-soldr 集成应使用 zccache rust-plan 进行
目标工件恢复/保存行为;参见
docs/architecture/target-cache.md 了解所有权边界。
如果您希望为开发者 CI 恢复旧的尽可能快的行为,请显式重新启用:
- uses: zackees/zccache@main
with:
cache-target: true
compilation-restore-fallback: true
target-restore-fallback: true
如果您希望获得更经过发布加固的配置,请保持目标快照处于禁用状态,并优先使用精确恢复:
- uses: zackees/zccache@main
with:
compilation-restore-fallback: false
本项目针对开发者速度进行了优化,而非完整的工件证明。zccache warm 不会在每次运行时对每个恢复的对象进行校验和,该操作也不会尝试在构建之前证明缓存完整性。如果您需要该级别的保证,请针对该工作流禁用以速度为重点的层。
输出
| 输出 | 描述 |
|---|---|
cache-hit-compilation | zccache 编译缓存是否已恢复 |
cache-hit-registry | cargo 注册表缓存是否已恢复 |
cache-hit-target | target 快照缓存是否已恢复 |
为什么分为两部分?
复合 GitHub Actions 不支持 post 步骤(自动清理)。该操作被拆分为:
zackees/zccache— setup: install zccache, restore caches through the native GHA cache backend when available, optionally warm target, start daemon, setRUSTC_WRAPPERzackees/zccache/action/cleanup— teardown: print stats, stop daemon, prune and save enabled caches through the same backend
清理操作必须使用 if: always() 调用,以确保即使在失败时缓存也能被保存。
从 sccache + rust-cache 迁移
之前(两个操作):
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
env:
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: sccache
之后(一个动作):
- uses: zackees/zccache@main
with:
shared-key: ${{ matrix.target }}
# ... build steps ...
- if: always()
uses: zackees/zccache/action/cleanup@main
无需环境变量 — 该操作会自动设置 RUSTC_WRAPPER=zccache。
C/C++ 构建系统集成(ninja、meson、cmake、make)
zccache 是一个即插即用的编译器包装器。将您的构建系统的编译器
指向 zccache <real-compiler>,其余部分将由它处理:
# meson native file
[binaries]
c = ['zccache', '/usr/bin/clang']
cpp = ['zccache', '/usr/bin/clang++']
# CMake
set(CMAKE_C_COMPILER_LAUNCHER zccache)
set(CMAKE_CXX_COMPILER_LAUNCHER zccache)
首次构建(冷缓存)以接近裸机的速度运行。后续重新构建
(ninja -t clean && ninja,或触及源文件)通过硬链接在不到一秒内提供缓存产物。
严格路径验证: 设置 ZCCACHE_STRICT_PATHS 或在编译器名称前传入
--strict-paths=<off|consistent|absolute>,以便在真正的编译器运行之前捕获未规范化的包含路径:
ZCCACHE_STRICT_PATHS=consistent ninja
zccache --strict-paths=absolute clang++ -IC:/project/src -c main.cpp
consistent 拒绝在单个路径内或同一次调用中混合使用分隔符样式的已检查路径标志。absolute 还要求 -I、-isystem、-include 和 -include-pch 等路径标志为不含 /./ 或 /../ 段的正斜杠绝对路径。违规时以非零状态退出,并显示违规标志和完整的调用者命令。
路径重映射自动模式: 计划中的 C/C++ 工作树共享使用
ZCCACHE_PATH_REMAP=auto 让 zccache 内部为 clang/gcc/emcc 构建注入并键控编译器路径重映射。
这使 Ninja、Meson、CMake 和 Make 命令保持简单,同时允许当编译器可见的源/调试路径等效时,
兄弟检出共享工件。
单往返 IPC: 在 drop-in 模式下,zccache 发送单个
CompileEphemeral 消息,该消息结合了会话创建、编译和
会话拆除——消除了每次调用中 3 次 IPC 往返中的 2 次。
会话统计: 使用 --stats 跟踪每次构建的命中率:
eval $(zccache session-start --stats --log build.log)
export ZCCACHE_SESSION_ID=...
# ... build runs ...
zccache session-stats $ZCCACHE_SESSION_ID # query mid-build
zccache session-end $ZCCACHE_SESSION_ID # final stats
从已保存的 stats JSON 文件渲染常驻引擎阶段分析器:
zccache session-end $ZCCACHE_SESSION_ID --json > last-session-stats.json
zccache engine-profile last-session-stats.json
zccache engine-profile last-session-stats.json --json
此报告汇总了来自 phase_profile 的命中/未命中阶段总计、平均值及主导阶段。
这是缓存引擎的回归视图;Tokio Console 用于
实时异步运行时症状,例如阻塞任务、长轮询和资源
争用。
持久缓存: 工件存储在 ~/.zccache/artifacts/
中,并在守护进程重启后保留。重启后无需重新预热缓存。
编译日志(诊断和部分重放): 每个编译和链接
命令都以 JSONL 格式记录到 ~/.zccache/logs/compile_journal.jsonl:
{"ts":"2026-03-17T10:30:00.123Z","outcome":"hit","compiler":"/usr/bin/clang++","args":["-c","foo.cpp","-o","foo.o"],"cwd":"/project/build","env":[["CC","clang"]],"exit_code":0,"session_id":"uuid","latency_ns":1234567}
字段:ts(ISO 8601 UTC)、outcome(hit/miss/error/link_hit/link_miss)、
compiler(完整路径)、args(完整参数列表)、cwd、env(一个狭窄的、
对密钥安全的构建诊断白名单)、exit_code、session_id(临时会话为 null)、
latency_ns(墙钟纳秒)。每行一个 JSON 对象——通过
jq 进行过滤。精确重放需要一个独立捕获的可信
环境;zccache 从不更改传递给编译器的环境。
每会话编译日志: 将 --journal <path> 传递给 session-start 以
写入一个专用 JSONL 日志,仅包含该会话的命令。
路径必须以 .jsonl 结尾:
result=$(zccache session-start --journal build.jsonl)
session_id=$(echo "$result" | jq -r .session_id)
export ZCCACHE_SESSION_ID=$session_id
# ... build runs ...
# Inspect this session's commands only (no noise from other sessions)
jq . build.jsonl
zccache session-end $session_id
会话日志使用与全局日志相同的 JSONL 架构。条目会同时写入全局日志和会话日志。当调用 session-end 时,会话文件句柄会被释放。
多文件编译(快速路径)
当构建系统将多个源文件传递给单次编译器调用时(例如 gcc -c a.cpp b.cpp c.cpp -o ...),zccache 将其视为快速路径:
- 每个源文件都会并行地与缓存进行检查。
- 缓存命中会立即响应——其
.o文件从缓存中写入。 - 剩余的缓存未命中会被批量处理到单个编译器进程中,保留编译器自身的进程复用和内存共享优势。
- 批量编译的输出会单独缓存,以供未来命中使用。
这种混合方法意味着首次构建会按文件填充缓存,而后续构建会尽可能多地从缓存中提供文件,同时仍让编译器高效地批量处理未命中。
建议: 尽可能配置构建系统,使每次编译器调用传递多个源文件。这为 zccache 提供了最佳机会来并行化缓存查找并最小化编译器启动次数。
并发
守护进程使用无锁并发数据结构(DashMap)进行工件和元数据查找,因此来自多个构建工作线程的并行编译请求永远不会在全局锁上串行化。
状态
早期开发 — 架构和脚手架阶段。
目标
- 在本地机器上速度极快(守护进程保持缓存处于热状态)
- 可移植至 Linux、macOS 和 Windows
- 在高强度并行编译下正确无误(无陈旧缓存命中)
- 部署简单(单一二进制文件)
工具兼容性
zccache 可作为以下编译器和工具的即插即用包装器:
- Clang 工具链: clang, clang-tidy, IWYU
- Emscripten / WebAssembly: emcc, wasm-ld
- Rust 工具链: rustc, rustfmt, clippy
架构
请参阅 docs/ARCHITECTURE.md 了解完整的系统设计。
关键组件
| Crate | 用途 |
|---|---|
zccache-cli | 命令行界面(zccache 二进制文件) — 包含 warm、cargo-registry、gha-cache 子命令 |
zccache-daemon | 守护进程(IPC 服务器,编排) |
zccache-core | 共享类型、错误、配置、路径工具 |
zccache-protocol | IPC 消息类型和序列化 |
zccache-ipc | 传输层(Unix 套接字 / 命名管道) |
zccache-hash | blake3 哈希和缓存键计算 |
zccache-fscache | 内存文件元数据缓存 |
zccache-artifact | 基于磁盘的工件存储,带有 bincode 索引 |
zccache-watcher | 文件监视子系统:守护进程 notify 管道以及基于 Rust 的 Python 监视器绑定 |
zccache-compiler | 编译器检测和参数解析 |
zccache-gha | GitHub Actions Cache API 客户端 |
zccache-test-support | 测试工具和测试夹具 |
构建
cargo build --workspace
测试
cargo test --workspace
文档
Watcher API
zccache 在三个不同的地方暴露了 watcher 相关的 API,具体取决于 你希望如何消费变更检测:
- CLI:
zccache fp ...用于脚本和 CI 中基于守护进程的指纹检查 - Python:
zccache.watcher用于跨平台的库风格文件监视 - Rust:
zccache-watcher用于面向守护进程的 watcher 管道原语
CLI API
CLI watcher 的入口点是 fingerprint API。它通过查询守护进程的内存中监视状态和缓存的文件指纹来回答“我应该重新运行吗?”
zccache fp --cache-file .cache/headers.json check \
--root . \
--include '**/*.cpp' \
--include '**/*.h' \
--exclude build \
--exclude .git
退出码:
0:文件已更改,运行耗时步骤1:未检测到更改,跳过该步骤
在运行成功或失败后,更新守护进程的监视状态:
zccache fp --cache-file .cache/headers.json mark-success
zccache fp --cache-file .cache/headers.json mark-failure
zccache fp --cache-file .cache/headers.json invalidate
指纹 API 最适合仅需“是/否”变更答案,而非文件事件流的 shell 脚本、CI 任务和构建步骤。
Python API
pip install zccache 除了原生二进制文件外,现在还暴露了一个可导入的 zccache 模块。Python 接口针对 CLI 已暴露的相同热路径功能:监视器事件、指纹决策、守护进程/会话控制、下载以及 Arduino .ino 转换。
from zccache.client import ZcCacheClient
from zccache.fingerprint import FingerprintCache
from zccache.ino import convert_ino
from zccache.watcher import watch_files
client = ZcCacheClient()
client.start()
fp = FingerprintCache(".cache/watch.json")
decision = fp.check(
root=".",
include=["**/*.cpp", "**/*.hpp", "**/*.ino"],
exclude=["**/.build/**", "**/fastled_js/**"],
)
if decision.should_run:
convert_ino("Blink.ino", "build/Blink.ino.cpp")
fp.mark_success()
watcher API 仍然对轮询和回调友好,而后端在 Rust 中运行文件系统扫描循环,仅在分发事件时跨越到 Python。
from zccache.watcher import watch_files
watcher = watch_files(
".",
include_folders=["src", "include"],
include_globs=["src/**/*.cpp", "include/**/*.h"],
exclude_globs=["build", "dist/**", ".git"],
debounce_seconds=0.2,
poll_interval=0.1,
)
event = watcher.poll(timeout=1.0)
if event is not None:
print(event.paths)
watcher.stop()
如需显式生命周期控制,请使用类 API:
from zccache.watcher import FileWatcher
watcher = FileWatcher(".", include_globs=["**/*.cpp"], autostart=False)
watcher.start()
event = watcher.poll(timeout=1.0)
watcher.stop()
watcher.resume()
watcher.stop()
Python 监视器功能:
include_folders用于缩小扫描根目录include_globs用于仅包含匹配的文件exclude_globs/excluded_patterns用于跳过目录或文件debounce_seconds用于合并连续的编辑操作- 可选的
notification_predicate在 Python 交付时应用 - 回调 API 以及轮询 API
- 显式的
start()、stop()、resume()以及上下文管理器支持
守护进程/会话控制也可用,且无需每次调用都启动 shell:
from zccache.client import ZcCacheClient
client = ZcCacheClient()
client.start()
session = client.session_start(cwd=".", track_stats=True)
stats = client.session_stats(session.session_id)
client.session_end(session.session_id)
并且指纹状态可以直接从 Python 中管理:
from zccache.fingerprint import FingerprintCache
fp = FingerprintCache(".cache/lint.json", cache_type="two-layer")
decision = fp.check(root=".", include=["**/*.cpp"], exclude=["**/.build/**"])
if decision.should_run:
fp.mark_success()
fastled-wasm 所使用的兼容性包装器也可用:
FileWatcherProcessDebouncedFileWatcherProcesswatch_filesFileWatcher
参见 crates/zccache-watcher/README.md 以获取 完整的 Python watcher 接口。
Rust API
对于 Rust 使用者,公开的 watcher crate 是 zccache-watcher。
它现在同时暴露了面向守护进程的 watcher 管道和库风格的
轮询 watcher API:
-
PollingWatcherConfig -
PollingWatcher -
PollWatchBatch -
PollWatchObserver -
IgnoreFilter用于基于目录名称的过滤 -
NotifyWatcher用于notify支持的操作系统监视注册 -
SettleBuffer和SettledEvent用于突发合并 -
OverflowRecovery用于溢出驱动的重新扫描调度 -
WatchEvent和WatcherConfig用于事件/配置管道
示例:
use std::time::Duration;
use zccache_watcher::{PollingWatcher, PollingWatcherConfig};
let mut config = PollingWatcherConfig::new(".");
config.include_globs = vec!["**/*.cpp".to_string()];
config.poll_interval = Duration::from_millis(50);
config.debounce = Duration::from_millis(50);
let watcher = PollingWatcher::new(config)?;
watcher.start()?;
let batch = watcher.poll_timeout(Duration::from_secs(1))?;
watcher.stop()?;
下载器 API
zccache 还在三个地方暴露了专用的下载子系统:
- CLI:主二进制文件上的
zccache download ...,以及独立的zccache-download工具 - Python:
zccache.downloader.DownloadApi - Rust:客户端 API 的
zccache-download-client和共享下载类型的zccache-download
下载器守护进程独立于编译器缓存守护进程。它旨在用于 长期存活的工件下载、确定性的缓存路径、可选的解包, 以及来自多个客户端的附加/等待/状态流程。
下载器 CLI
主 zccache 二进制文件包含一个简单的下载子命令:
zccache download \
https://example.com/toolchain.tar.zst \
--unarchive .cache/toolchain \
--sha256 0123456789abcdef \
--multipart-parts 8
该路径会阻塞,直到制品就绪,并打印解析后的缓存路径、 SHA-256 以及可选的解包目标位置。
对于守护进程生命周期控制、attach/wait/status 操作、JSON 输出以及 显式的归档格式选择,请使用独立的下载器 CLI:
zccache-download daemon start
zccache-download fetch \
https://example.com/toolchain.tar.zst \
.cache/downloads/toolchain.tar.zst \
--expanded .cache/toolchain \
--archive-format tar.zst \
--max-connections 8
zccache-download exists \
https://example.com/toolchain.tar.zst \
.cache/downloads/toolchain.tar.zst
zccache-download --json daemon status
其他独立子命令:
get用于附加到原始下载句柄wait、status和cancel用于句柄生命周期操作daemon stop用于显式关闭下载守护进程
Python 下载器 API
pip install zccache 将下载器暴露为 zccache.downloader。
from zccache.downloader import DownloadApi
api = DownloadApi()
api.start()
result = api.download(
source_url="https://example.com/toolchain.tar.zst",
destination=".cache/downloads/toolchain.tar.zst",
expanded=".cache/toolchain",
archive_format="tar.zst",
multipart_parts=8,
)
print(result.status, result.sha256, result.expanded_path)
state = api.exists(
source_url="https://example.com/toolchain.tar.zst",
destination=".cache/downloads/toolchain.tar.zst",
)
print(state.kind, state.reason)
如果你需要 attach/wait/status 语义,而不是阻塞式 fetch 调用,请使用
DownloadApi.attach(...) 并对返回的 DownloadHandle 进行操作:
from zccache.downloader import DownloadApi
api = DownloadApi()
with api.attach(
source_url="https://example.com/toolchain.tar.zst",
destination=".cache/downloads/toolchain.tar.zst",
max_connections=8,
) as handle:
status = handle.wait(timeout_ms=1_000)
print(handle.download_id, status.phase, status.downloaded_bytes)
Python 下载器接口包括:
DownloadApi.start()、stop()和daemon_status()DownloadApi.download()/fetch()用于阻塞或非阻塞获取DownloadApi.exists()用于缓存状态检查DownloadApi.attach()以及DownloadHandle.status()、wait()和cancel()
Rust 下载器 API
对于 Rust 代码,请使用 zccache-download-client 作为入口点,
并使用 zccache-download 作为共享的状态和选项类型。
use std::path::PathBuf;
use zccache_download_client::{ArchiveFormat, DownloadClient, FetchRequest, WaitMode};
let client = DownloadClient::new(None);
client.start_daemon()?;
let mut request = FetchRequest::new(
"https://example.com/toolchain.tar.zst",
PathBuf::from(".cache/downloads/toolchain.tar.zst"),
);
request.destination_path_expanded = Some(PathBuf::from(".cache/toolchain"));
request.archive_format = ArchiveFormat::TarZst;
request.multipart_parts = Some(8);
request.wait_mode = WaitMode::Block;
let result = client.fetch(request)?;
println!("{:?} {} {}", result.status, result.sha256, result.cache_path.display());
对于基于句柄的控制,请使用 DownloadClient::download(...):
use std::path::Path;
use zccache_download::DownloadOptions;
use zccache_download_client::DownloadClient;
let client = DownloadClient::new(None);
let mut handle = client.download(
"https://example.com/toolchain.tar.zst",
Path::new(".cache/downloads/toolchain.tar.zst"),
DownloadOptions {
force: false,
max_connections: Some(8),
min_segment_size: None,
},
)?;
let status = handle.wait(Some(1_000))?;
println!("{:?} {}", status.phase, status.downloaded_bytes);
Rust 下载器接口包括:
DownloadClient::start_daemon()、stop_daemon()和daemon_status()- 带有
FetchRequest的DownloadClient::fetch()和exists() - 返回
DownloadHandle的DownloadClient::download() ArchiveFormat、FetchResult、FetchState、FetchStatus和WaitModeDownloadOptions、DownloadStatus和DownloadDaemonStatus
许可证
根据 Apache License, Version 2.0 或 MIT license 任选其一进行授权。



