From b9235ca43d6e7dd1cd484eb5a69daeb3eae671db Mon Sep 17 00:00:00 2001 From: dhruv8sh Date: Thu, 8 Oct 2026 04:51:41 +0530 Subject: [PATCH 1/3] Give each Rust CI job its own cache and save caches only from main All Rust jobs shared one rust-cache key, so the fastest job saved it and every slower job restored a cache built for a different target, logged "Cache up-to-date", and rebuilt its dependencies on every run. Use a per-job shared key, restrict saves to main so PR-scoped caches stop evicting main's, and uninstall the runner image's preinstalled stable toolchain so image rollouts no longer rotate the cache key. Integration tests restore the axum job's cache, which builds the same artifacts. Closes #1258 Signed-off-by: dhruv8sh --- .../setup-integration-test-env/action.yml | 12 +++- .github/workflows/format.yml | 10 +++- .github/workflows/test.yml | 58 +++++++++++++++++-- 3 files changed, 73 insertions(+), 7 deletions(-) diff --git a/.github/actions/setup-integration-test-env/action.yml b/.github/actions/setup-integration-test-env/action.yml index 841d8d5bd..948487bbb 100644 --- a/.github/actions/setup-integration-test-env/action.yml +++ b/.github/actions/setup-integration-test-env/action.yml @@ -51,12 +51,22 @@ runs: # `.tool-versions` is the single source of truth for the Viceroy pin. run: echo "viceroy-version=$(grep '^viceroy ' .tool-versions | awk '{print $2}')" >> "$GITHUB_OUTPUT" + - name: Remove runner-image Rust toolchain + # rust-cache hashes every installed toolchain into its key, so the image's + # preinstalled stable would rotate the key on each runner-image rollout. + shell: bash + run: if command -v rustup >/dev/null; then rustup toolchain uninstall stable; fi + - name: Set up Rust toolchain uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: ${{ steps.rust-version.outputs.rust-version }} target: wasm32-wasip1 - cache-shared-key: cargo-${{ runner.os }} + # Restore the axum job's main cache: it builds the same axum debug and + # Fastly release WASM artifacts. Integration tests run on PRs only, and + # PR caches are scoped to their ref and only evict main's shared caches. + cache-shared-key: cargo-axum-${{ runner.os }} + cache-save-if: ${{ github.ref == 'refs/heads/main' }} - name: Cache Viceroy binary if: ${{ inputs.install-viceroy == 'true' }} diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 93bc99229..ac16aae62 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -50,13 +50,21 @@ jobs: run: echo "rust-version=$(grep '^rust ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT shell: bash + - name: Remove runner-image Rust toolchain + # rust-cache hashes every installed toolchain into its key, so the image's + # preinstalled stable would rotate the key on each runner-image rollout. + shell: bash + run: if command -v rustup >/dev/null; then rustup toolchain uninstall stable; fi + - name: Set up rust toolchain uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: ${{ steps.rust-version.outputs.rust-version }} target: wasm32-wasip1,wasm32-unknown-unknown components: "clippy, rustfmt" - cache-shared-key: cargo-${{ runner.os }} + cache-shared-key: cargo-clippy-${{ runner.os }} + # PR caches are scoped to their ref and only evict main's shared caches. + cache-save-if: ${{ github.ref == 'refs/heads/main' }} - name: Run cargo fmt uses: actions-rust-lang/rustfmt@v1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d06a1b8ff..126dfd3a1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,12 +33,20 @@ jobs: run: echo "node-version=$(grep '^nodejs ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT shell: bash + - name: Remove runner-image Rust toolchain + # rust-cache hashes every installed toolchain into its key, so the image's + # preinstalled stable would rotate the key on each runner-image rollout. + shell: bash + run: if command -v rustup >/dev/null; then rustup toolchain uninstall stable; fi + - name: Set up Rust toolchain uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: ${{ steps.rust-version.outputs.rust-version }} target: wasm32-wasip1 - cache-shared-key: cargo-${{ runner.os }} + cache-shared-key: cargo-fastly-${{ runner.os }} + # PR caches are scoped to their ref and only evict main's shared caches. + cache-save-if: ${{ github.ref == 'refs/heads/main' }} - name: Cache Viceroy binary id: cache-viceroy @@ -82,6 +90,12 @@ jobs: run: echo "rust-version=$(grep '^rust ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT shell: bash + - name: Remove runner-image Rust toolchain + # rust-cache hashes every installed toolchain into its key, so the image's + # preinstalled stable would rotate the key on each runner-image rollout. + shell: bash + run: if command -v rustup >/dev/null; then rustup toolchain uninstall stable; fi + - name: Set up Rust toolchain uses: actions-rust-lang/setup-rust-toolchain@v1 with: @@ -89,7 +103,9 @@ jobs: # wasm32-wasip1 is required by the "Verify Fastly WASM release # build" step below; the axum build and tests are native. target: wasm32-wasip1 - cache-shared-key: cargo-${{ runner.os }} + cache-shared-key: cargo-axum-${{ runner.os }} + # PR caches are scoped to their ref and only evict main's shared caches. + cache-save-if: ${{ github.ref == 'refs/heads/main' }} - name: Read pinned Node.js version for CLI fixtures id: node-version @@ -133,12 +149,20 @@ jobs: run: echo "rust-version=$(grep '^rust ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT shell: bash + - name: Remove runner-image Rust toolchain + # rust-cache hashes every installed toolchain into its key, so the image's + # preinstalled stable would rotate the key on each runner-image rollout. + shell: bash + run: if command -v rustup >/dev/null; then rustup toolchain uninstall stable; fi + - name: Set up Rust toolchain (native + wasm32-unknown-unknown) uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: ${{ steps.rust-version.outputs.rust-version }} target: wasm32-unknown-unknown - cache-shared-key: cargo-${{ runner.os }} + cache-shared-key: cargo-cloudflare-${{ runner.os }} + # PR caches are scoped to their ref and only evict main's shared caches. + cache-save-if: ${{ github.ref == 'refs/heads/main' }} - name: Check Cloudflare adapter (native host) run: cargo check -p trusted-server-adapter-cloudflare @@ -160,12 +184,20 @@ jobs: run: echo "rust-version=$(grep '^rust ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT shell: bash + - name: Remove runner-image Rust toolchain + # rust-cache hashes every installed toolchain into its key, so the image's + # preinstalled stable would rotate the key on each runner-image rollout. + shell: bash + run: if command -v rustup >/dev/null; then rustup toolchain uninstall stable; fi + - name: Set up Rust toolchain (native + wasm32-wasip1) uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: ${{ steps.rust-version.outputs.rust-version }} target: wasm32-wasip1 - cache-shared-key: cargo-${{ runner.os }} + cache-shared-key: cargo-spin-${{ runner.os }} + # PR caches are scoped to their ref and only evict main's shared caches. + cache-save-if: ${{ github.ref == 'refs/heads/main' }} - name: Check Spin adapter (native host) run: cargo check -p trusted-server-adapter-spin @@ -199,12 +231,20 @@ jobs: run: echo "rust-version=$(grep '^rust ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT shell: bash + - name: Remove runner-image Rust toolchain + # rust-cache hashes every installed toolchain into its key, so the image's + # preinstalled stable would rotate the key on each runner-image rollout. + shell: bash + run: if command -v rustup >/dev/null; then rustup toolchain uninstall stable; fi + - name: Set up Rust toolchain uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: ${{ steps.rust-version.outputs.rust-version }} components: clippy, rustfmt - cache-shared-key: cargo-${{ runner.os }} + cache-shared-key: cargo-parity-${{ runner.os }} + # PR caches are scoped to their ref and only evict main's shared caches. + cache-save-if: ${{ github.ref == 'refs/heads/main' }} - name: Format check (parity test crate) run: cargo fmt --manifest-path crates/trusted-server-integration-tests/Cargo.toml -- --check @@ -230,12 +270,20 @@ jobs: run: echo "rust-version=$(grep '^rust ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT shell: bash + - name: Remove runner-image Rust toolchain + # rust-cache hashes every installed toolchain into its key, so the image's + # preinstalled stable would rotate the key on each runner-image rollout. + shell: bash + run: if command -v rustup >/dev/null; then rustup toolchain uninstall stable; fi + - name: Set up Rust toolchain uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: ${{ steps.rust-version.outputs.rust-version }} components: "clippy, rustfmt" cache-shared-key: cargo-cli-${{ runner.os }} + # PR caches are scoped to their ref and only evict main's shared caches. + cache-save-if: ${{ github.ref == 'refs/heads/main' }} - name: Read pinned Node.js version for CLI fixtures id: node-version From bcec6ad7700d4078c42edc7febb4a3cb6e435ce0 Mon Sep 17 00:00:00 2001 From: dhruv8sh Date: Mon, 12 Oct 2026 12:56:40 +0530 Subject: [PATCH 2/3] Move pinned Rust setup into a shared composite action Make the integration cache restore-only so a workflow_dispatch run on main cannot claim the axum key, and unset the rustup default after removing stable so rustup cannot silently reinstall it. Signed-off-by: dhruv8sh --- .../setup-integration-test-env/action.yml | 23 +--- .github/actions/setup-rust/action.yml | 45 ++++++++ .github/workflows/format.yml | 18 +-- .github/workflows/test.yml | 108 ++---------------- 4 files changed, 65 insertions(+), 129 deletions(-) create mode 100644 .github/actions/setup-rust/action.yml diff --git a/.github/actions/setup-integration-test-env/action.yml b/.github/actions/setup-integration-test-env/action.yml index 948487bbb..ee71d7b70 100644 --- a/.github/actions/setup-integration-test-env/action.yml +++ b/.github/actions/setup-integration-test-env/action.yml @@ -34,11 +34,6 @@ outputs: runs: using: composite steps: - - name: Retrieve Rust version - id: rust-version - shell: bash - run: echo "rust-version=$(grep '^rust ' .tool-versions | awk '{print $2}')" >> "$GITHUB_OUTPUT" - - name: Retrieve Node.js version id: node-version shell: bash @@ -51,22 +46,16 @@ runs: # `.tool-versions` is the single source of truth for the Viceroy pin. run: echo "viceroy-version=$(grep '^viceroy ' .tool-versions | awk '{print $2}')" >> "$GITHUB_OUTPUT" - - name: Remove runner-image Rust toolchain - # rust-cache hashes every installed toolchain into its key, so the image's - # preinstalled stable would rotate the key on each runner-image rollout. - shell: bash - run: if command -v rustup >/dev/null; then rustup toolchain uninstall stable; fi - - name: Set up Rust toolchain - uses: actions-rust-lang/setup-rust-toolchain@v1 + uses: ./.github/actions/setup-rust with: - toolchain: ${{ steps.rust-version.outputs.rust-version }} target: wasm32-wasip1 # Restore the axum job's main cache: it builds the same axum debug and - # Fastly release WASM artifacts. Integration tests run on PRs only, and - # PR caches are scoped to their ref and only evict main's shared caches. - cache-shared-key: cargo-axum-${{ runner.os }} - cache-save-if: ${{ github.ref == 'refs/heads/main' }} + # Fastly release WASM artifacts. Restore only: test-axum owns this key, + # and rust-cache never replaces an exact-match key, so a partial cache + # saved by a workflow_dispatch run on main would stick. + cache-key: cargo-axum + save-cache: "false" - name: Cache Viceroy binary if: ${{ inputs.install-viceroy == 'true' }} diff --git a/.github/actions/setup-rust/action.yml b/.github/actions/setup-rust/action.yml new file mode 100644 index 000000000..60c12f482 --- /dev/null +++ b/.github/actions/setup-rust/action.yml @@ -0,0 +1,45 @@ +name: Set up pinned Rust +description: Install the .tool-versions Rust toolchain with a per-job cache that only main saves. + +inputs: + cache-key: + description: Per-job rust-cache key prefix; the runner OS is appended. + required: true + target: + description: Comma-separated extra targets. + required: false + default: "" + components: + description: Comma-separated extra components. + required: false + default: "" + save-cache: + description: Set to "false" for restore-only callers. + required: false + default: "true" + +runs: + using: composite + steps: + - name: Retrieve Rust version + id: rust-version + shell: bash + run: echo "rust-version=$(grep '^rust ' .tool-versions | awk '{print $2}')" >> "$GITHUB_OUTPUT" + + - name: Remove runner-image Rust toolchain + # rust-cache hashes every installed toolchain into its key, so the image's + # preinstalled stable would rotate the key on each runner-image rollout. + # Unsetting the default stops rustup from silently reinstalling stable + # when cargo runs outside the checkout. + shell: bash + run: if command -v rustup >/dev/null; then rustup toolchain uninstall stable && rustup default none; fi + + - name: Set up Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: ${{ steps.rust-version.outputs.rust-version }} + target: ${{ inputs.target }} + components: ${{ inputs.components }} + cache-shared-key: ${{ inputs.cache-key }}-${{ runner.os }} + # PR caches are scoped to their ref and only evict main's shared caches. + cache-save-if: ${{ inputs.save-cache == 'true' && github.ref == 'refs/heads/main' }} diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index ac16aae62..eaacbf0c5 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -45,26 +45,12 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Retrieve Rust version - id: rust-version - run: echo "rust-version=$(grep '^rust ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT - shell: bash - - - name: Remove runner-image Rust toolchain - # rust-cache hashes every installed toolchain into its key, so the image's - # preinstalled stable would rotate the key on each runner-image rollout. - shell: bash - run: if command -v rustup >/dev/null; then rustup toolchain uninstall stable; fi - - name: Set up rust toolchain - uses: actions-rust-lang/setup-rust-toolchain@v1 + uses: ./.github/actions/setup-rust with: - toolchain: ${{ steps.rust-version.outputs.rust-version }} target: wasm32-wasip1,wasm32-unknown-unknown components: "clippy, rustfmt" - cache-shared-key: cargo-clippy-${{ runner.os }} - # PR caches are scoped to their ref and only evict main's shared caches. - cache-save-if: ${{ github.ref == 'refs/heads/main' }} + cache-key: cargo-clippy - name: Run cargo fmt uses: actions-rust-lang/rustfmt@v1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 126dfd3a1..0284e1e29 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,11 +16,6 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Retrieve Rust version - id: rust-version - run: echo "rust-version=$(grep '^rust ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT - shell: bash - - name: Retrieve Viceroy version id: viceroy-version # `.tool-versions` is the single source of truth so this workflow and @@ -33,20 +28,11 @@ jobs: run: echo "node-version=$(grep '^nodejs ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT shell: bash - - name: Remove runner-image Rust toolchain - # rust-cache hashes every installed toolchain into its key, so the image's - # preinstalled stable would rotate the key on each runner-image rollout. - shell: bash - run: if command -v rustup >/dev/null; then rustup toolchain uninstall stable; fi - - name: Set up Rust toolchain - uses: actions-rust-lang/setup-rust-toolchain@v1 + uses: ./.github/actions/setup-rust with: - toolchain: ${{ steps.rust-version.outputs.rust-version }} target: wasm32-wasip1 - cache-shared-key: cargo-fastly-${{ runner.os }} - # PR caches are scoped to their ref and only evict main's shared caches. - cache-save-if: ${{ github.ref == 'refs/heads/main' }} + cache-key: cargo-fastly - name: Cache Viceroy binary id: cache-viceroy @@ -85,27 +71,13 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Retrieve Rust version - id: rust-version - run: echo "rust-version=$(grep '^rust ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT - shell: bash - - - name: Remove runner-image Rust toolchain - # rust-cache hashes every installed toolchain into its key, so the image's - # preinstalled stable would rotate the key on each runner-image rollout. - shell: bash - run: if command -v rustup >/dev/null; then rustup toolchain uninstall stable; fi - - name: Set up Rust toolchain - uses: actions-rust-lang/setup-rust-toolchain@v1 + uses: ./.github/actions/setup-rust with: - toolchain: ${{ steps.rust-version.outputs.rust-version }} # wasm32-wasip1 is required by the "Verify Fastly WASM release # build" step below; the axum build and tests are native. target: wasm32-wasip1 - cache-shared-key: cargo-axum-${{ runner.os }} - # PR caches are scoped to their ref and only evict main's shared caches. - cache-save-if: ${{ github.ref == 'refs/heads/main' }} + cache-key: cargo-axum - name: Read pinned Node.js version for CLI fixtures id: node-version @@ -144,25 +116,11 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Retrieve Rust version - id: rust-version - run: echo "rust-version=$(grep '^rust ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT - shell: bash - - - name: Remove runner-image Rust toolchain - # rust-cache hashes every installed toolchain into its key, so the image's - # preinstalled stable would rotate the key on each runner-image rollout. - shell: bash - run: if command -v rustup >/dev/null; then rustup toolchain uninstall stable; fi - - name: Set up Rust toolchain (native + wasm32-unknown-unknown) - uses: actions-rust-lang/setup-rust-toolchain@v1 + uses: ./.github/actions/setup-rust with: - toolchain: ${{ steps.rust-version.outputs.rust-version }} target: wasm32-unknown-unknown - cache-shared-key: cargo-cloudflare-${{ runner.os }} - # PR caches are scoped to their ref and only evict main's shared caches. - cache-save-if: ${{ github.ref == 'refs/heads/main' }} + cache-key: cargo-cloudflare - name: Check Cloudflare adapter (native host) run: cargo check -p trusted-server-adapter-cloudflare @@ -179,25 +137,11 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Retrieve Rust version - id: rust-version - run: echo "rust-version=$(grep '^rust ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT - shell: bash - - - name: Remove runner-image Rust toolchain - # rust-cache hashes every installed toolchain into its key, so the image's - # preinstalled stable would rotate the key on each runner-image rollout. - shell: bash - run: if command -v rustup >/dev/null; then rustup toolchain uninstall stable; fi - - name: Set up Rust toolchain (native + wasm32-wasip1) - uses: actions-rust-lang/setup-rust-toolchain@v1 + uses: ./.github/actions/setup-rust with: - toolchain: ${{ steps.rust-version.outputs.rust-version }} target: wasm32-wasip1 - cache-shared-key: cargo-spin-${{ runner.os }} - # PR caches are scoped to their ref and only evict main's shared caches. - cache-save-if: ${{ github.ref == 'refs/heads/main' }} + cache-key: cargo-spin - name: Check Spin adapter (native host) run: cargo check -p trusted-server-adapter-spin @@ -226,25 +170,11 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Retrieve Rust version - id: rust-version - run: echo "rust-version=$(grep '^rust ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT - shell: bash - - - name: Remove runner-image Rust toolchain - # rust-cache hashes every installed toolchain into its key, so the image's - # preinstalled stable would rotate the key on each runner-image rollout. - shell: bash - run: if command -v rustup >/dev/null; then rustup toolchain uninstall stable; fi - - name: Set up Rust toolchain - uses: actions-rust-lang/setup-rust-toolchain@v1 + uses: ./.github/actions/setup-rust with: - toolchain: ${{ steps.rust-version.outputs.rust-version }} components: clippy, rustfmt - cache-shared-key: cargo-parity-${{ runner.os }} - # PR caches are scoped to their ref and only evict main's shared caches. - cache-save-if: ${{ github.ref == 'refs/heads/main' }} + cache-key: cargo-parity - name: Format check (parity test crate) run: cargo fmt --manifest-path crates/trusted-server-integration-tests/Cargo.toml -- --check @@ -265,25 +195,11 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Retrieve Rust version - id: rust-version - run: echo "rust-version=$(grep '^rust ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT - shell: bash - - - name: Remove runner-image Rust toolchain - # rust-cache hashes every installed toolchain into its key, so the image's - # preinstalled stable would rotate the key on each runner-image rollout. - shell: bash - run: if command -v rustup >/dev/null; then rustup toolchain uninstall stable; fi - - name: Set up Rust toolchain - uses: actions-rust-lang/setup-rust-toolchain@v1 + uses: ./.github/actions/setup-rust with: - toolchain: ${{ steps.rust-version.outputs.rust-version }} components: "clippy, rustfmt" - cache-shared-key: cargo-cli-${{ runner.os }} - # PR caches are scoped to their ref and only evict main's shared caches. - cache-save-if: ${{ github.ref == 'refs/heads/main' }} + cache-key: cargo-cli - name: Read pinned Node.js version for CLI fixtures id: node-version From bc07274ac42bc9b75d30d369f19034ac6b53aaaa Mon Sep 17 00:00:00 2001 From: dhruv8sh Date: Thu, 15 Oct 2026 14:40:13 +0530 Subject: [PATCH 3/3] Require Rust jobs to use the shared setup-rust action Add a step to the clippy job that fails when any workflow or action calls setup-rust-toolchain or rust-cache directly, so new Rust jobs cannot skip the stable removal and main-only cache save gate. Signed-off-by: dhruv8sh --- .github/workflows/format.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index d67815d67..341099293 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -45,6 +45,18 @@ jobs: steps: - uses: actions/checkout@v4 + # Rust jobs must set up the toolchain through ./.github/actions/setup-rust, + # which removes the image's stable and only saves caches from main. A + # direct setup-rust-toolchain or rust-cache step skips both silently. + - name: Require the shared Rust setup action + shell: bash + run: | + if grep -rnE 'uses:[[:space:]]*(actions-rust-lang/setup-rust-toolchain|Swatinem/rust-cache)@' .github \ + | grep -v '^\.github/actions/setup-rust/action\.yml:'; then + echo "::error::Set up Rust with ./.github/actions/setup-rust, not setup-rust-toolchain or rust-cache directly." + exit 1 + fi + - name: Set up rust toolchain uses: ./.github/actions/setup-rust with: