From 5f22a40d2e62b5feeb3634860f14b641bb5ce72c Mon Sep 17 00:00:00 2001 From: Stefanie Jane Date: Fri, 2 Oct 2026 13:07:19 -0700 Subject: [PATCH 1/2] docs(cache): name the statistic that shows sccache 0.17 writing to R2 The docs told readers to look for a Cache writes row, which sccache 0.17 does not print. The first main run against R2 showed what a writing run actually reports: Average cache write 0.463 s with Cache write errors at 0 across 4 misses, where the read-only pull request run had 4 write errors for the same 4 misses. Co-Authored-By: Claude Opus 5.5 --- docs/development/SERVO_BUILD_CACHING.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/development/SERVO_BUILD_CACHING.md b/docs/development/SERVO_BUILD_CACHING.md index ec59fef1a..a9566beeb 100644 --- a/docs/development/SERVO_BUILD_CACHING.md +++ b/docs/development/SERVO_BUILD_CACHING.md @@ -201,8 +201,9 @@ Fork pull requests receive no secrets and compile with the local disk cache. The `Compiler cache:` line in the configure step's log and the `Cache location` row of the job summary's statistics show which backend a job used. The statistics are authoritative: a mid-job fallback, or a write check -that downgrades sccache to read-only, leaves the configure line stale, and the -first `main` run should show `Cache writes` above zero. A read-only run counts +that downgrades sccache to read-only, leaves the configure line stale. +sccache 0.17 has no write counter, so a writing run shows a nonzero +`Average cache write` with `Cache write errors` at zero. A read-only run counts every write it skips as a `Cache write errors` entry, so in pull requests and tags that row matching `Cache misses` is expected, not a fault. From e01cafda3adcbd6e866b91106f961bcf56f685a5 Mon Sep 17 00:00:00 2001 From: Stefanie Jane Date: Fri, 2 Oct 2026 13:13:40 -0700 Subject: [PATCH 2/2] ci(cache): write R2 from main lanes that opt out of Actions saves The first main run against R2 showed E2E Build / CPU Smoke compiling 267 objects in READ_ONLY mode. The R2 write mode followed the same cacheWriter decision as the Actions cache, which honors save-if, and two lanes, python-generated and e2e-build-cpu, set save-if: "false" to keep duplicate target directories out of the Actions cache budget. R2 has no such budget, so those lanes recompiled on every run without ever filling the shared cache. R2 now writes from every trusted default-branch run regardless of save-if; pull requests and tags still read only. Those jobs already receive the read-write key on main through the sccache-writer environment, so no new run can write. Co-Authored-By: Claude Opus 5.5 --- .github/actions/rust-build-cache/configure.mjs | 5 ++++- .github/actions/rust-build-cache/configure.test.mjs | 10 ++++++++++ docs/development/SERVO_BUILD_CACHING.md | 8 +++++--- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/.github/actions/rust-build-cache/configure.mjs b/.github/actions/rust-build-cache/configure.mjs index 15984cb82..dd88047ad 100644 --- a/.github/actions/rust-build-cache/configure.mjs +++ b/.github/actions/rust-build-cache/configure.mjs @@ -138,7 +138,10 @@ export function configure(env = process.env, compilerVersion, probe = probeRemot }); const cargoHome = env.CARGO_HOME || path.join(homedir(), '.cargo'); const write = cacheWriter(env.CACHE_SAVE_IF || 'auto', env.GITHUB_REF, env.CACHE_DEFAULT_BRANCH, env.GITHUB_EVENT_NAME); - const remote = remoteCompilerCache(env, write); + // save-if opts a lane out of the Actions cache budget; R2 has no such budget, + // so every trusted default-branch run writes the shared compiler cache. + const trusted = cacheWriter('auto', env.GITHUB_REF, env.CACHE_DEFAULT_BRANCH, env.GITHUB_EVENT_NAME); + const remote = remoteCompilerCache(env, trusted); let remoteValues = {}; let compilerCacheLocation = `local disk (${remote.reason})`; if (remote.values) { diff --git a/.github/actions/rust-build-cache/configure.test.mjs b/.github/actions/rust-build-cache/configure.test.mjs index 245ca9b3b..3cebe7dfa 100644 --- a/.github/actions/rust-build-cache/configure.test.mjs +++ b/.github/actions/rust-build-cache/configure.test.mjs @@ -198,6 +198,16 @@ test('configuration enables R2 only after the probe succeeds and exports nothing 'rustc test fixture', () => ({ ok: true, detail: 'HTTP 404' })); assert.equal(pull.SCCACHE_S3_RW_MODE, 'READ_ONLY'); + // An Actions-cache opt-out lane on main still writes the shared compiler cache. + const optedOut = configure({ ...env, CACHE_SAVE_IF: 'false', GITHUB_ENV: path.join(temp, 'env-opt-out') }, + 'rustc test fixture', () => ({ ok: true, detail: 'HTTP 404' })); + assert.equal(optedOut.HYPERCOLOR_CACHE_WRITE, 'false'); + assert.equal(optedOut.SCCACHE_S3_RW_MODE, 'READ_WRITE'); + const optedOutPull = configure({ ...env, CACHE_SAVE_IF: 'false', GITHUB_REF: 'refs/pull/7/merge', + GITHUB_EVENT_NAME: 'pull_request', GITHUB_ENV: path.join(temp, 'env-opt-out-pr') }, + 'rustc test fixture', () => ({ ok: true, detail: 'HTTP 404' })); + assert.equal(optedOutPull.SCCACHE_S3_RW_MODE, 'READ_ONLY'); + const down = configure({ ...env, GITHUB_ENV: path.join(temp, 'env-down') }, 'rustc test fixture', () => ({ ok: false, detail: 'HTTP 403' })); assert.equal(down.SCCACHE_BUCKET, undefined); assert.equal(down.AWS_SECRET_ACCESS_KEY, undefined); diff --git a/docs/development/SERVO_BUILD_CACHING.md b/docs/development/SERVO_BUILD_CACHING.md index a9566beeb..4d0833a1e 100644 --- a/docs/development/SERVO_BUILD_CACHING.md +++ b/docs/development/SERVO_BUILD_CACHING.md @@ -186,9 +186,11 @@ Each of the thirteen jobs that use the cache action passes the bucket, the `SCCACHE_R2_SECRET_ACCESS_KEY` secrets as the action's `r2-*` inputs. Jobs that never compile see none of them. The action then: -- writes (`SCCACHE_S3_RW_MODE=READ_WRITE`) only where the Actions cache writes, - and reads everywhere else, so pull requests and tags reuse what `main` - compiled without adding entries; +- writes (`SCCACHE_S3_RW_MODE=READ_WRITE`) from every push or dispatch on + `main`, including lanes that set `save-if: "false"` (that switch protects + the Actions cache budget, which R2 does not share), and reads everywhere + else, so pull requests and tags reuse what `main` compiled without adding + entries; - keys entries under `sccache/-`, so each runner platform keeps its own namespace; - sends a signed HEAD request before enabling R2. A refused or unreachable