Skip to content
How it works

Cache key

The cache key is a blake3 hash that uniquely identifies one compiled artifact. Two rustc invocations that would produce identical output must produce the same key. Two invocations that differ in any input that affects the output must produce different keys.

Getting this right is the core correctness challenge in any build cache. A false positive (same key, different output) corrupts the build. A false negative (different keys, identical output) wastes time.

What's in the key

The principle is simple: anything that changes the compiled output goes into the key; anything that's just machine-local noise is normalized out. That's what lets one machine restore what another built.

What goes in (change any of these and the crate re-keys):

  • Toolchain identity — the rustc --version --verbose banner (commit hash + host triple), plus the linker's --version for outputs that actually link.
  • What you're compiling — crate name, types, and edition; and every source file the crate reads. kache finds them with a --emit=dep-info pre-pass, so modules, include!() targets, and build-script output are all covered, and hashes each by content.
  • How you're compiling it — target triple, feature/cfg flags, codegen (-C) options, RUSTFLAGS, and the active --emit set (so a cargo check entry is never served to a cargo build).
  • What it depends on — each --extern rlib/rmeta, hashed by content, so a dependency change propagates to everything downstream.
  • Compile-time environment — the env!() / option_env!() values a build bakes in.

What's deliberately left out (machine-local, doesn't affect output): the incremental-compilation directory, the linker path (its identity is captured via --version instead), and absolute build/checkout paths — those are normalized to stable sentinels so the key is the same regardless of where you built. See Cross-machine portability.

The authoritative, always-current list for any specific build is what kache actually logs — kache why-miss <crate> and KACHE_LOG=trace print every component it hashed (see Debugging cache misses). Prefer that over a hand-kept enumeration, which drifts as the keying logic evolves.

Tuning the key

A few opt-in knobs, all no-ops by default:

  • KACHE_BASE_DIR — declare a path prefix to strip from the key (collapsed to <BASE_DIR>). Use it for checkout or container-mount paths the automatic sentinels don't catch, so out-of-tree builds still share hits.
  • KACHE_KEY_SALT / cache.key_salt — fold an opaque string into every key to force a cold cache when the toolchain changes in a way no --version reveals (a libc/linker bump, a Nix rebuild). See Configuration.
  • kache.toml extra inputs — declare files the compiler reads but never reports (sqlx's .sqlx/, migrations/, include!'d data) so editing them re-keys the crate. See Configuration.
  • path_only_env_vars — extra env vars whose value is only a path locator, so kache normalizes their path out of the key for cross-machine hits. See Configuration.

Cross-machine portability

The key is designed to be the same on any machine that has the same:

  • Rust toolchain version
  • Target triple
  • Source code
  • Feature flags and cfg values
  • RUSTFLAGS (after path normalization)

This is what makes the remote cache useful in CI: a developer's laptop and a fresh CI runner with the same toolchain produce identical keys, so CI can restore what the developer already built.

Debugging cached binaries

The same --remap-path-prefix rules that make artifacts portable also rewrite the paths baked into debug info. So the DWARF (or PDB on Windows) in a cached binary refers to your sources through stable, machine-independent paths instead of the local path it was compiled at. Those paths are chosen so profilers and debuggers can resolve them:

Real locationPath baked into debug info
the repo / workspace checkout/proc/self/cwd (Linux) or /kache/workspace
rust std / core sources/rustc/<commit-hash>/... (the upstream form)
registry + git deps (~/.cargo)/kache/.cargo/registry/src/...
~/.rustup toolchain files/kache/rustup
KACHE_BASE_DIR, $HOME, tempdir, $CARGO_TARGET_DIR/kache/base-dir, /kache/home, /kache/tmp, /kache/target

The paths are absolute, so a debugger or profiler never composes them into nonsense, and machine-independent, so one cached binary is valid in every checkout.

samply / the Firefox Profiler resolve most of this with no configuration. std sources (/rustc/<hash>/...) are fetched from github.com/rust-lang/rust, and dependency sources (/kache/.cargo/registry/src/...) from crates.io, exactly as they are for a plain cargo build. That works on every OS, because it depends on the path shape, not on /proc.

Your own workspace sources are the exception, and here the OS matters:

  • Linux: they resolve with no configuration, as long as you run the profiler from the workspace root. /proc/self/cwd is a kernel-provided symlink to the reading process's working directory, so the profiler opens the real files.
  • macOS / Windows: there is no /proc, so workspace sources use the fixed /kache/workspace prefix. gdb/lldb can map that back (below), but samply currently has no source-map setting, so workspace sources are not resolved there yet (std and dependency sources still are). Closing that gap needs an upstream samply feature; it is tracked in the #485 follow-up. If you need workspace sources in samply on macOS/Windows today, use the KACHE_RUSTC_PATH_NORMALIZE=0 opt-out below.

For gdb / lldb, map the workspace prefix back to your checkout. On Linux, launching the debugger from the workspace root usually makes this unnecessary (/proc/self/cwd already points there); elsewhere:

# in ~/.gdbinit or at the prompt
set substitute-path /kache/workspace /abs/path/to/your/checkout
# in ~/.lldbinit or at the prompt
settings set target.source-map /kache/workspace /abs/path/to/your/checkout

Add substitute-path / source-map entries for /kache/.cargo or /kache/rustup if you want to step into dependency or std sources from a local copy instead of fetching them.

If you would rather have your real local paths baked in with no mapping at all, set KACHE_RUSTC_PATH_NORMALIZE=0 (see below).

This page describes the rustc artifact cache. C/C++ object compiles (cc / c++ / clang-cl, see C/C++ caching) are keyed separately, and their -ffile-prefix-map targets follow the same scheme (/proc/self/cwd for the build directory on Linux, /kache/cc-root, /kache/sdkroot, and so on). clang-cl debug compiles (/Z7 / -Z7 / -g) are cached, but the key stays machine-local: clang-cl embeds CodeView paths directly in the .obj (no separate PDB) and ignores -ffile-prefix-map, so kache keeps those paths literal in the key rather than remapping them, giving correct local hits but no cross-machine sharing.

Coverage builds are exempt. When coverage instrumentation is active (-C instrument-coverage), kache skips path remapping entirely, so tools like cargo-llvm-cov and tarpaulin map profiling data back to real source paths with no extra configuration. Skipping the remap also changes the key, so coverage builds keep a separate cache from regular ones — and, because the artifact then bakes real machine-local paths, that key includes the build's local path identity too (same mechanism as the opt-out below), so coverage artifacts only share when the baked path identity matches — different checkouts or tool roots get different keys.

Opting out entirely. If you profile or debug locally and would rather have real source paths baked into debug info than configure a source map — e.g. so samply / the Firefox Profiler resolve sources with no setup — set KACHE_RUSTC_PATH_NORMALIZE=0. kache then injects no --remap-path-prefix flags and rustc records the real machine-local paths (this is the rustc analog of KACHE_CC_PATH_NORMALIZE=0 for C/C++). The trade-off: the key hashes remap:none plus the build's real local path identity (working directory, $CARGO_HOME/$RUSTUP_HOME/$HOME/tempdir roots) — a separate namespace from remapped builds and from other checkouts, so these real-path artifacts only share when the baked path identity matches — different checkouts or tool roots get different keys (the rustc analog of cc's path-literal keys). Accepted tokens are the same as the cc toggle — 0 / false / off / no disable it; anything else (including unset) keeps normalization on, which is the default so ordinary builds stay portable.

Debugging cache misses

If a crate keeps missing when you expect a hit, use kache why-miss <crate-name>. It compares the cache key from the last known entry against the current build and reports which input changed.

For full key-component details, run the command why-miss prints:

KACHE_LOG=trace cargo build -p <crate-name> 2>&1 | grep '\[key:<crate-name>\]'

This logs every component being hashed during key computation. As a defense-in-depth check, a KACHE_LOG=warn run also flags any residual machine-local absolute path that survives normalization (matching prefixes like /Users/, /home/, /private/tmp/, /var/folders/, C:\Users\), naming the offending key field — a fast signal that something is leaking a per-machine path into the key.

Available for:
Apple macOS logomacOSMicrosoft Windows logoWindowsLinux logoLinux
Download Kunobi