Composite action: sccache-backed Rust build cache for Forgejo CI
Find a file
Stephen Way 4101cee842
rust-ci-cache: sccache-backed Rust build cache composite action
Collapse the per-job sccache boilerplate that paragon and flux repeat in
every Rust CI job (RUSTC_WRAPPER, the SCCACHE_* / AWS_* env, the guard that
the S3 credentials are present, the sccache install, and the cache config)
into one uses: line.

The action guards the S3 credentials loudly so a missing secret never
silently downgrades sccache to a useless local-only cache on an ephemeral
runner, delegates the install to rasterstate/sccache-action@v1 rather than
reimplementing it, and writes the cache config to GITHUB_ENV so the caller's
later cargo steps are wrapped automatically.
2026-06-09 10:58:33 -07:00
action.yml rust-ci-cache: sccache-backed Rust build cache composite action 2026-06-09 10:58:33 -07:00
LICENSE rust-ci-cache: sccache-backed Rust build cache composite action 2026-06-09 10:58:33 -07:00
README.md rust-ci-cache: sccache-backed Rust build cache composite action 2026-06-09 10:58:33 -07:00

rust-ci-cache

Composite action that wires up an sccache-backed, S3-shared build cache for Rust jobs on Forgejo Actions runners. It collapses the ~25 lines of RUSTC_WRAPPER / SCCACHE_* / AWS_* env, the "guard the secrets are present" step, the sccache install, and the cache config that paragon and flux were repeating in every Rust CI job into a single uses: line. No GitHub PAT required (the install delegates to rasterstate/sccache-action, which pulls the public Mozilla release tarball directly).

Why

Our CI runs on ephemeral autoscaler VMs: every job starts on a fresh box with an empty local cache, so a local-only sccache buys nothing and every build is cold. The win comes entirely from the shared S3 backend, which means the S3 credentials are load-bearing. If they are missing, sccache does not fail; it quietly downgrades to that useless local cache and the only symptom is slow builds nobody connects to a missing secret. This action fails loudly when the credentials are absent so that never happens silently, then exports the cache config to $GITHUB_ENV so the caller's later cargo steps are wrapped automatically.

Usage

jobs:
  test:
    runs-on: [self-hosted, Linux]
    steps:
      - uses: actions/checkout@v6
      - uses: https://rasterhub.com/rasterstate/setup-rust-action@v1

      - uses: https://rasterhub.com/rasterstate/rust-ci-cache@v1
        with:
          aws-access-key-id: ${{ secrets.SCCACHE_AWS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.SCCACHE_AWS_SECRET_KEY }}

      - run: cargo build --release
      - run: cargo test

      - name: sccache stats
        if: always()
        run: sccache --show-stats

The two aws-* inputs are required. A composite action cannot read the calling workflow's secrets on its own, so you must wire them in explicitly: pass secrets.SCCACHE_AWS_KEY_ID into aws-access-key-id and secrets.SCCACHE_AWS_SECRET_KEY into aws-secret-access-key. Those are the names the rasterstate org uses for the sccache S3 bucket credentials.

After the action runs, every cargo step in the same job is transparently wrapped by sccache and reads/writes the shared S3 cache. You add nothing else except the trailing stats step (below).

Cache stats

This is a setup-style action: all of its steps run at the point you invoke it, before your build. It therefore cannot print sccache --show-stats for you, because the meaningful numbers only exist after the build has run. Add the stats yourself as the last step of the job, with if: always() so it still runs when the build fails:

- name: sccache stats
  if: always()
  run: sccache --show-stats

If you want the numbers in the job summary instead, the equivalent table-rendering snippet lives in the sccache-action README.

Inputs

Name Required Default Description
sccache-version no v0.8.2 sccache release tag, passed through to sccache-action. Pin deliberately: the S3 cache layout has changed between minor versions (notably v0.7 to v0.8), so a bump invalidates the cache until jobs re-populate it.
key-prefix no ${{ github.repository }} Value for SCCACHE_S3_KEY_PREFIX. Defaults to the calling repo so each repo gets its own namespace in the bucket. Override to pool cache across repos.
aws-access-key-id yes Written to AWS_ACCESS_KEY_ID. Wire secrets.SCCACHE_AWS_KEY_ID in. Empty is a hard error.
aws-secret-access-key yes Written to AWS_SECRET_ACCESS_KEY. Wire secrets.SCCACHE_AWS_SECRET_KEY in. Empty is a hard error.

What it exports

The action appends the following to $GITHUB_ENV, so every later step in the job inherits them:

Variable Value
RUSTC_WRAPPER sccache
SCCACHE_BUCKET fal-sscache-01
SCCACHE_ENDPOINT https://fsn1.your-objectstorage.com
SCCACHE_REGION fsn1
SCCACHE_S3_USE_SSL true
SCCACHE_S3_KEY_PREFIX the key-prefix input
AWS_ACCESS_KEY_ID the aws-access-key-id input
AWS_SECRET_ACCESS_KEY the aws-secret-access-key input

The bucket name is not a typo

SCCACHE_BUCKET is fal-sscache-01, with a double "s" in "sscache". That misspelling was baked in when the bucket was created and the bucket name is immutable on the object store, so it is correct exactly as written. Do not "fix" it to fal-sccache-01: sccache would then point at a bucket that does not exist and the shared cache would silently stop working.

Versioning

Tags follow vMAJOR rolling-pointer style so consumers can @v1 and get patch/minor fixes without re-pinning. Concrete release tags (v1.0.0, v1.0.1, ...) exist alongside for users who want full pinning. The operator cuts and moves these tags; this repo ships unversioned until then.

License

MIT. See LICENSE.