Skip to content

A Rust build cache in GitHub Actions

Updated

A Rust job in GitHub Actions starts with nothing compiled, so every run compiles every crate again. With hushrun's shared store, a job downloads the crates your main branch's CI already compiled instead, and so do teammates' laptops and coding agents' sandboxes, while a pull request can reuse that code but never change it.

If you only need one repository's CI warm, and no laptops or sandboxes, a plain cache may be enough: see Without hushrun.

Before you begin

  • hushrun 0.15.0 or later, on every machine and in CI.
  • The repository on GitHub Actions, connected with npx hushrun init, which also gives its jobs id-token: write.
  • An admin or owner of your organization on hushrun, for the steps in the dashboard.
  • One Rust toolchain for laptops and CI, pinned in rust-toolchain.toml. A machine reuses only code built for its own system and compiler version, so a Mac gets nothing from Linux CI.

Run your cargo commands through hushrun

Only hushrun run NAME -- COMMAND uses the shared store. npx hushrun init wraps the cargo lines it finds in your workflows, or you write them yourself:

.github/workflows/ci.yml
jobs:
  test:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@v5
      - uses: hushrun/hushrun/action/[email protected]
      - run: hushrun run test -- cargo test --locked
      - run: hushrun run build -- cargo build --locked

The job needs id-token: write: it asks the shared store as itself, with the job's ID token, and without one it shares nothing. The backend comes from the hushrun.yaml that init writes; without one, set HUSHRUN_BACKEND in the job's env (Set up CI).

The same line also skips the whole check when these exact files already passed it (How it works). Clippy, rustdoc and coverage builds can go through hushrun too, but their compiles aren't reused.

Take out other compile caches

In the jobs that run through hushrun:

  • Remove RUSTC_WRAPPER, and sccache's setup step if it sets it. Where it's set, hushrun's build reuse turns itself off for that run, and says so in one line.
  • Stop caching target/ between jobs. A restored folder proves nothing about how its files were built, so hushrun shares only what the job compiled itself.
  • Caching cargo's registry, ~/.cargo, is fine.

Then push to main, so its CI runs hushrun once.

Confirm the repository and trust main

In the dashboard's Shared store page, https://app.hushrun.com/<org>/-/build:

  1. Under Repositories, open the repository, which shows as seen once its CI has run hushrun, and choose Confirm….
  2. Under Trusted branches, choose main and List…. Its CI now writes the code every other machine reuses. List only branches whose pushes you trust, and protect them on GitHub.

The full rules, and what each choice trusts: Share compiled code.

Turn sharing on, then reuse

  1. Under Sharing, choose Turn on…. Sharing starts by watching: machines compile everything, upload what they build and compare it with the store, and reuse nothing.
  2. Push to main and open a pull request or two. The Usage panel shows the comparisons, trusted CI's first.
  3. Once trusted CI has compared crates and none differed, choose Start reusing….

From then on, a job's last lines say what came from the store. This line is an example of the output, not a measurement:

hushrun: build: 226 of 360 crates reused (226 from the shared store), 134 built, saving about 2m 3s

Which jobs reuse what

JobReusesWrites
A push to main, once listedmain's trusted codemain's trusted code
A pull requestThe trusted code, then what earlier runs of the same pull request builtCode for that pull request alone
A tag, or a job with an environment: keyThe trusted codeNothing
A pull request from a forkNothingNothing

A job on a runner that isn't one of GitHub's own writes trusted code only after an admin trusts self-hosted runners (why). The whole table, laptops and sandboxes included: Whose code a machine reuses.

Laptops and sandboxes

  • A teammate's laptop reuses the trusted code after hushrun build on, once per machine. It's off until then.
  • A coding agent's sandbox with HUSHRUN_AGENT reuses it with nothing more, as long as it can reach the store's host: see Warm sandboxes for coding agents.

Reuse runs one way: CI's code reaches laptops and sandboxes, and a laptop's never reaches CI.

Without hushrun

A plain cache is the better fit when:

  • your CI isn't GitHub Actions: hushrun's shared store doesn't take part in GitLab CI or other CI yet;
  • you build on Windows, or compile C, C++ or CUDA as well, which hushrun's compile reuse doesn't cover;
  • only CI needs to be warm, and you'd rather not involve an admin or a hosted store.

Cache cargo's folders. Swatinem/rust-cache caches ~/.cargo and the dependencies' build output in ./target, keyed on the compiler, Cargo.lock, the Cargo.toml files and the toolchain files. It doesn't cache your workspace's own crates.

.github/workflows/ci.yml
steps:
  - uses: actions/checkout@v5
  - uses: Swatinem/rust-cache@v2
  - run: cargo test --locked

Cache compiler output with sccache. mozilla-actions/sccache-action installs sccache, and SCCACHE_GHA_ENABLED keeps its cache in GitHub's. It can't cache bin, dylib, cdylib or proc-macro crates, or incremental builds (sccache's Rust notes).

.github/workflows/ci.yml
jobs:
  test:
    runs-on: ubuntu-latest
    env:
      SCCACHE_GHA_ENABLED: "true"
      RUSTC_WRAPPER: sccache
    steps:
      - uses: actions/checkout@v5
      - uses: mozilla-actions/[email protected]
      - run: cargo test --locked

Both store in GitHub's cache, which holds 10 GB per repository by default and drops what's unused for 7 days. A run restores caches from its own branch and the default branch, and a pull request also from its base branch. More: hushrun and sccache.

Limits

  • Only libraries are shared. Programs, tests, proc macros and build scripts are compiled on each machine; a machine's own store still reuses them.
  • New code compiles once, wherever it's first built.
  • hushrun run CHECK, the checks of hushrun.yaml, uses no shared code in CI. Use hushrun run NAME -- COMMAND.
  • No speed figure. hushrun hasn't published a measurement of the shared store yet. A download starts only when it should arrive within half the time the crate takes to compile; otherwise the compiler runs (Compiled code).
Markdown