Use sccache in CI (#48895)

Conrad Irwin created

Try sccache in CI to paper over cargo's abyssmal caching

Release Notes:

- N/A

Change summary

.github/workflows/release.yml                        |  58 ++++++
.github/workflows/release_nightly.yml                |  22 ++
.github/workflows/run_agent_evals.yml                |   9 +
.github/workflows/run_cron_unit_evals.yml            |   9 +
.github/workflows/run_tests.yml                      |  76 +++++++++
.github/workflows/run_unit_evals.yml                 |   9 +
script/setup-sccache                                 | 116 +++++++++++++
script/setup-sccache.ps1                             | 117 ++++++++++++++
tooling/xtask/src/tasks/workflows/run_agent_evals.rs |   6 
tooling/xtask/src/tasks/workflows/run_tests.rs       |  10 +
tooling/xtask/src/tasks/workflows/steps.rs           |  20 ++
tooling/xtask/src/tasks/workflows/vars.rs            |   3 
12 files changed, 454 insertions(+), 1 deletion(-)

Detailed changes

.github/workflows/release.yml 🔗

@@ -26,6 +26,13 @@ jobs:
       with:
         cache: rust
         path: ~/.rustup
+    - name: steps::setup_sccache
+      run: ./script/setup-sccache
+      env:
+        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
+        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
+        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
+        SCCACHE_BUCKET: sccache-zed
     - name: steps::setup_node
       uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
       with:
@@ -36,6 +43,8 @@ jobs:
       run: ./script/clear-target-dir-if-larger-than 300
     - name: steps::cargo_nextest
       run: cargo nextest run --workspace --no-fail-fast
+    - name: steps::show_sccache_stats
+      run: sccache --show-stats || true
     - name: steps::cleanup_cargo_config
       if: always()
       run: |
@@ -64,6 +73,13 @@ jobs:
       run: ./script/install-mold
     - name: steps::download_wasi_sdk
       run: ./script/download-wasi-sdk
+    - name: steps::setup_sccache
+      run: ./script/setup-sccache
+      env:
+        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
+        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
+        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
+        SCCACHE_BUCKET: sccache-zed
     - name: steps::setup_node
       uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
       with:
@@ -74,6 +90,8 @@ jobs:
       run: ./script/clear-target-dir-if-larger-than 250
     - name: steps::cargo_nextest
       run: cargo nextest run --workspace --no-fail-fast
+    - name: steps::show_sccache_stats
+      run: sccache --show-stats || true
     - name: steps::cleanup_cargo_config
       if: always()
       run: |
@@ -100,6 +118,14 @@ jobs:
         New-Item -ItemType Directory -Path "./../.cargo" -Force
         Copy-Item -Path "./.cargo/ci-config.toml" -Destination "./../.cargo/config.toml"
       shell: pwsh
+    - name: steps::setup_sccache
+      run: ./script/setup-sccache.ps1
+      shell: pwsh
+      env:
+        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
+        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
+        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
+        SCCACHE_BUCKET: sccache-zed
     - name: steps::setup_node
       uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
       with:
@@ -110,6 +136,9 @@ jobs:
     - name: steps::cargo_nextest
       run: cargo nextest run --workspace --no-fail-fast
       shell: pwsh
+    - name: steps::show_sccache_stats
+      run: sccache --show-stats; exit 0
+      shell: pwsh
     - name: steps::cleanup_cargo_config
       if: always()
       run: |
@@ -133,8 +162,17 @@ jobs:
       with:
         cache: rust
         path: ~/.rustup
+    - name: steps::setup_sccache
+      run: ./script/setup-sccache
+      env:
+        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
+        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
+        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
+        SCCACHE_BUCKET: sccache-zed
     - name: steps::clippy
       run: ./script/clippy
+    - name: steps::show_sccache_stats
+      run: sccache --show-stats || true
     timeout-minutes: 60
   clippy_linux:
     if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
@@ -159,8 +197,17 @@ jobs:
       run: ./script/install-mold
     - name: steps::download_wasi_sdk
       run: ./script/download-wasi-sdk
+    - name: steps::setup_sccache
+      run: ./script/setup-sccache
+      env:
+        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
+        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
+        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
+        SCCACHE_BUCKET: sccache-zed
     - name: steps::clippy
       run: ./script/clippy
+    - name: steps::show_sccache_stats
+      run: sccache --show-stats || true
     timeout-minutes: 60
   clippy_windows:
     if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
@@ -175,9 +222,20 @@ jobs:
         New-Item -ItemType Directory -Path "./../.cargo" -Force
         Copy-Item -Path "./.cargo/ci-config.toml" -Destination "./../.cargo/config.toml"
       shell: pwsh
+    - name: steps::setup_sccache
+      run: ./script/setup-sccache.ps1
+      shell: pwsh
+      env:
+        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
+        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
+        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
+        SCCACHE_BUCKET: sccache-zed
     - name: steps::clippy
       run: ./script/clippy.ps1
       shell: pwsh
+    - name: steps::show_sccache_stats
+      run: sccache --show-stats; exit 0
+      shell: pwsh
     timeout-minutes: 60
   check_scripts:
     if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')

.github/workflows/release_nightly.yml 🔗

@@ -38,6 +38,14 @@ jobs:
         New-Item -ItemType Directory -Path "./../.cargo" -Force
         Copy-Item -Path "./.cargo/ci-config.toml" -Destination "./../.cargo/config.toml"
       shell: pwsh
+    - name: steps::setup_sccache
+      run: ./script/setup-sccache.ps1
+      shell: pwsh
+      env:
+        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
+        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
+        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
+        SCCACHE_BUCKET: sccache-zed
     - name: steps::setup_node
       uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
       with:
@@ -48,6 +56,9 @@ jobs:
     - name: steps::cargo_nextest
       run: cargo nextest run --workspace --no-fail-fast
       shell: pwsh
+    - name: steps::show_sccache_stats
+      run: sccache --show-stats; exit 0
+      shell: pwsh
     - name: steps::cleanup_cargo_config
       if: always()
       run: |
@@ -67,9 +78,20 @@ jobs:
         New-Item -ItemType Directory -Path "./../.cargo" -Force
         Copy-Item -Path "./.cargo/ci-config.toml" -Destination "./../.cargo/config.toml"
       shell: pwsh
+    - name: steps::setup_sccache
+      run: ./script/setup-sccache.ps1
+      shell: pwsh
+      env:
+        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
+        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
+        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
+        SCCACHE_BUCKET: sccache-zed
     - name: steps::clippy
       run: ./script/clippy.ps1
       shell: pwsh
+    - name: steps::show_sccache_stats
+      run: sccache --show-stats; exit 0
+      shell: pwsh
     timeout-minutes: 60
   bundle_linux_aarch64:
     needs:

.github/workflows/run_agent_evals.yml 🔗

@@ -42,6 +42,13 @@ jobs:
       run: |
         mkdir -p ./../.cargo
         cp ./.cargo/ci-config.toml ./../.cargo/config.toml
+    - name: steps::setup_sccache
+      run: ./script/setup-sccache
+      env:
+        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
+        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
+        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
+        SCCACHE_BUCKET: sccache-zed
     - name: cargo build --package=eval
       run: cargo build --package=eval
     - name: run_agent_evals::agent_evals::run_eval
@@ -51,6 +58,8 @@ jobs:
         OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
         GOOGLE_AI_API_KEY: ${{ secrets.GOOGLE_AI_API_KEY }}
         GOOGLE_CLOUD_PROJECT: ${{ secrets.GOOGLE_CLOUD_PROJECT }}
+    - name: steps::show_sccache_stats
+      run: sccache --show-stats || true
     - name: steps::cleanup_cargo_config
       if: always()
       run: |

.github/workflows/run_cron_unit_evals.yml 🔗

@@ -39,6 +39,13 @@ jobs:
       run: ./script/install-mold
     - name: steps::download_wasi_sdk
       run: ./script/download-wasi-sdk
+    - name: steps::setup_sccache
+      run: ./script/setup-sccache
+      env:
+        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
+        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
+        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
+        SCCACHE_BUCKET: sccache-zed
     - name: steps::cargo_install_nextest
       uses: taiki-e/install-action@nextest
     - name: steps::clear_target_dir_if_large
@@ -51,6 +58,8 @@ jobs:
         GOOGLE_AI_API_KEY: ${{ secrets.GOOGLE_AI_API_KEY }}
         GOOGLE_CLOUD_PROJECT: ${{ secrets.GOOGLE_CLOUD_PROJECT }}
         ZED_AGENT_MODEL: ${{ matrix.model }}
+    - name: steps::show_sccache_stats
+      run: sccache --show-stats || true
     - name: steps::cleanup_cargo_config
       if: always()
       run: |

.github/workflows/run_tests.yml 🔗

@@ -154,9 +154,20 @@ jobs:
         New-Item -ItemType Directory -Path "./../.cargo" -Force
         Copy-Item -Path "./.cargo/ci-config.toml" -Destination "./../.cargo/config.toml"
       shell: pwsh
+    - name: steps::setup_sccache
+      run: ./script/setup-sccache.ps1
+      shell: pwsh
+      env:
+        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
+        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
+        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
+        SCCACHE_BUCKET: sccache-zed
     - name: steps::clippy
       run: ./script/clippy.ps1
       shell: pwsh
+    - name: steps::show_sccache_stats
+      run: sccache --show-stats; exit 0
+      shell: pwsh
     timeout-minutes: 60
   clippy_linux:
     needs:
@@ -183,8 +194,17 @@ jobs:
       run: ./script/install-mold
     - name: steps::download_wasi_sdk
       run: ./script/download-wasi-sdk
+    - name: steps::setup_sccache
+      run: ./script/setup-sccache
+      env:
+        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
+        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
+        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
+        SCCACHE_BUCKET: sccache-zed
     - name: steps::clippy
       run: ./script/clippy
+    - name: steps::show_sccache_stats
+      run: sccache --show-stats || true
     timeout-minutes: 60
   clippy_mac:
     needs:
@@ -205,8 +225,17 @@ jobs:
       with:
         cache: rust
         path: ~/.rustup
+    - name: steps::setup_sccache
+      run: ./script/setup-sccache
+      env:
+        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
+        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
+        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
+        SCCACHE_BUCKET: sccache-zed
     - name: steps::clippy
       run: ./script/clippy
+    - name: steps::show_sccache_stats
+      run: sccache --show-stats || true
     timeout-minutes: 60
   run_tests_windows:
     needs:
@@ -223,6 +252,14 @@ jobs:
         New-Item -ItemType Directory -Path "./../.cargo" -Force
         Copy-Item -Path "./.cargo/ci-config.toml" -Destination "./../.cargo/config.toml"
       shell: pwsh
+    - name: steps::setup_sccache
+      run: ./script/setup-sccache.ps1
+      shell: pwsh
+      env:
+        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
+        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
+        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
+        SCCACHE_BUCKET: sccache-zed
     - name: steps::setup_node
       uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
       with:
@@ -233,6 +270,9 @@ jobs:
     - name: steps::cargo_nextest
       run: cargo nextest run --workspace --no-fail-fast${{ needs.orchestrate.outputs.changed_packages && format(' -E "{0}"', needs.orchestrate.outputs.changed_packages) || '' }}
       shell: pwsh
+    - name: steps::show_sccache_stats
+      run: sccache --show-stats; exit 0
+      shell: pwsh
     - name: steps::cleanup_cargo_config
       if: always()
       run: |
@@ -264,6 +304,13 @@ jobs:
       run: ./script/install-mold
     - name: steps::download_wasi_sdk
       run: ./script/download-wasi-sdk
+    - name: steps::setup_sccache
+      run: ./script/setup-sccache
+      env:
+        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
+        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
+        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
+        SCCACHE_BUCKET: sccache-zed
     - name: steps::setup_node
       uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
       with:
@@ -274,6 +321,8 @@ jobs:
       run: ./script/clear-target-dir-if-larger-than 250
     - name: steps::cargo_nextest
       run: cargo nextest run --workspace --no-fail-fast${{ needs.orchestrate.outputs.changed_packages && format(' -E "{0}"', needs.orchestrate.outputs.changed_packages) || '' }}
+    - name: steps::show_sccache_stats
+      run: sccache --show-stats || true
     - name: steps::cleanup_cargo_config
       if: always()
       run: |
@@ -306,6 +355,13 @@ jobs:
       with:
         cache: rust
         path: ~/.rustup
+    - name: steps::setup_sccache
+      run: ./script/setup-sccache
+      env:
+        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
+        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
+        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
+        SCCACHE_BUCKET: sccache-zed
     - name: steps::setup_node
       uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
       with:
@@ -316,6 +372,8 @@ jobs:
       run: ./script/clear-target-dir-if-larger-than 300
     - name: steps::cargo_nextest
       run: cargo nextest run --workspace --no-fail-fast${{ needs.orchestrate.outputs.changed_packages && format(' -E "{0}"', needs.orchestrate.outputs.changed_packages) || '' }}
+    - name: steps::show_sccache_stats
+      run: sccache --show-stats || true
     - name: steps::cleanup_cargo_config
       if: always()
       run: |
@@ -346,10 +404,19 @@ jobs:
       run: |
         mkdir -p ./../.cargo
         cp ./.cargo/ci-config.toml ./../.cargo/config.toml
+    - name: steps::setup_sccache
+      run: ./script/setup-sccache
+      env:
+        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
+        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
+        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
+        SCCACHE_BUCKET: sccache-zed
     - id: run_doctests
       name: run_tests::doctests::run_doctests
       run: |
         cargo test --workspace --doc --no-fail-fast
+    - name: steps::show_sccache_stats
+      run: sccache --show-stats || true
     - name: steps::cleanup_cargo_config
       if: always()
       run: |
@@ -380,10 +447,19 @@ jobs:
       run: ./script/install-mold
     - name: steps::download_wasi_sdk
       run: ./script/download-wasi-sdk
+    - name: steps::setup_sccache
+      run: ./script/setup-sccache
+      env:
+        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
+        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
+        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
+        SCCACHE_BUCKET: sccache-zed
     - name: cargo build -p collab
       run: cargo build -p collab
     - name: cargo build --workspace --bins --examples
       run: cargo build --workspace --bins --examples
+    - name: steps::show_sccache_stats
+      run: sccache --show-stats || true
     - name: steps::cleanup_cargo_config
       if: always()
       run: |

.github/workflows/run_unit_evals.yml 🔗

@@ -42,6 +42,13 @@ jobs:
       run: ./script/install-mold
     - name: steps::download_wasi_sdk
       run: ./script/download-wasi-sdk
+    - name: steps::setup_sccache
+      run: ./script/setup-sccache
+      env:
+        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
+        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
+        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
+        SCCACHE_BUCKET: sccache-zed
     - name: steps::cargo_install_nextest
       uses: taiki-e/install-action@nextest
     - name: steps::clear_target_dir_if_large
@@ -54,6 +61,8 @@ jobs:
         GOOGLE_AI_API_KEY: ${{ secrets.GOOGLE_AI_API_KEY }}
         GOOGLE_CLOUD_PROJECT: ${{ secrets.GOOGLE_CLOUD_PROJECT }}
         UNIT_EVAL_COMMIT: ${{ inputs.commit_sha }}
+    - name: steps::show_sccache_stats
+      run: sccache --show-stats || true
     - name: steps::cleanup_cargo_config
       if: always()
       run: |

script/setup-sccache 🔗

@@ -0,0 +1,116 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+SCCACHE_VERSION="v0.10.0"
+SCCACHE_DIR="./target/sccache"
+
+install_sccache() {
+    mkdir -p "$SCCACHE_DIR"
+
+    if [[ -x "${SCCACHE_DIR}/sccache" ]]; then
+        echo "sccache already cached: $("${SCCACHE_DIR}/sccache" --version)"
+    else
+        echo "Installing sccache ${SCCACHE_VERSION} from GitHub releases..."
+
+        local os arch archive basename
+        os="$(uname -s)"
+        arch="$(uname -m)"
+
+        case "${os}-${arch}" in
+            Darwin-arm64)
+                archive="sccache-${SCCACHE_VERSION}-aarch64-apple-darwin.tar.gz"
+                ;;
+            Darwin-x86_64)
+                archive="sccache-${SCCACHE_VERSION}-x86_64-apple-darwin.tar.gz"
+                ;;
+            Linux-x86_64)
+                archive="sccache-${SCCACHE_VERSION}-x86_64-unknown-linux-musl.tar.gz"
+                ;;
+            Linux-aarch64)
+                archive="sccache-${SCCACHE_VERSION}-aarch64-unknown-linux-musl.tar.gz"
+                ;;
+            *)
+                echo "Unsupported platform: ${os}-${arch}"
+                exit 1
+                ;;
+        esac
+
+        basename="${archive%.tar.gz}"
+        curl -fsSL "https://github.com/mozilla/sccache/releases/download/${SCCACHE_VERSION}/${archive}" | tar xz
+        mv "${basename}/sccache" "${SCCACHE_DIR}/"
+        rm -rf "${basename}"
+        echo "Installed sccache: $("${SCCACHE_DIR}/sccache" --version)"
+    fi
+
+    if [[ -n "${GITHUB_PATH:-}" ]]; then
+        echo "$(pwd)/${SCCACHE_DIR}" >> "$GITHUB_PATH"
+    fi
+    export PATH="$(pwd)/${SCCACHE_DIR}:${PATH}"
+}
+
+configure_sccache() {
+    if [[ -z "${R2_ACCOUNT_ID:-}" ]]; then
+        echo "R2_ACCOUNT_ID not set, skipping sccache configuration"
+        return
+    fi
+
+    echo "Configuring sccache with Cloudflare R2..."
+
+    local bucket="${SCCACHE_BUCKET:-sccache-zed}"
+    local key_prefix="${SCCACHE_KEY_PREFIX:-sccache/}"
+    local base_dir="${GITHUB_WORKSPACE:-$(pwd)}"
+
+    # Set in current process
+    export SCCACHE_ENDPOINT="https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com"
+    export SCCACHE_BUCKET="${bucket}"
+    export SCCACHE_REGION="auto"
+    export SCCACHE_S3_KEY_PREFIX="${key_prefix}"
+    export SCCACHE_BASEDIR="${base_dir}"
+    export AWS_ACCESS_KEY_ID="${R2_ACCESS_KEY_ID}"
+    export AWS_SECRET_ACCESS_KEY="${R2_SECRET_ACCESS_KEY}"
+    export RUSTC_WRAPPER="sccache"
+
+    # Also write to GITHUB_ENV for subsequent steps
+    if [[ -n "${GITHUB_ENV:-}" ]]; then
+        {
+            echo "SCCACHE_ENDPOINT=${SCCACHE_ENDPOINT}"
+            echo "SCCACHE_BUCKET=${SCCACHE_BUCKET}"
+            echo "SCCACHE_REGION=${SCCACHE_REGION}"
+            echo "SCCACHE_S3_KEY_PREFIX=${SCCACHE_S3_KEY_PREFIX}"
+            echo "SCCACHE_BASEDIR=${SCCACHE_BASEDIR}"
+            echo "AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}"
+            echo "AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}"
+            echo "RUSTC_WRAPPER=${RUSTC_WRAPPER}"
+        } >> "$GITHUB_ENV"
+    fi
+
+    echo "✓ sccache configured with Cloudflare R2 (bucket: ${bucket})"
+}
+
+show_config() {
+    echo "=== sccache configuration ==="
+    echo "sccache version: $(sccache --version)"
+    echo "RUSTC_WRAPPER: ${RUSTC_WRAPPER:-<not set>}"
+    echo "SCCACHE_BUCKET: ${SCCACHE_BUCKET:-<not set>}"
+    echo "SCCACHE_ENDPOINT: ${SCCACHE_ENDPOINT:-<not set>}"
+    echo "SCCACHE_REGION: ${SCCACHE_REGION:-<not set>}"
+    echo "SCCACHE_S3_KEY_PREFIX: ${SCCACHE_S3_KEY_PREFIX:-<not set>}"
+    echo "SCCACHE_BASEDIR: ${SCCACHE_BASEDIR:-<not set>}"
+    if [[ -n "${AWS_ACCESS_KEY_ID:-}" ]]; then
+        echo "AWS_ACCESS_KEY_ID: <set, length=${#AWS_ACCESS_KEY_ID}>"
+    else
+        echo "AWS_ACCESS_KEY_ID: <not set>"
+    fi
+    if [[ -n "${AWS_SECRET_ACCESS_KEY:-}" ]]; then
+        echo "AWS_SECRET_ACCESS_KEY: <set>"
+    else
+        echo "AWS_SECRET_ACCESS_KEY: <not set>"
+    fi
+    echo "=== sccache stats ==="
+    sccache --show-stats || true
+}
+
+install_sccache
+configure_sccache
+show_config

script/setup-sccache.ps1 🔗

@@ -0,0 +1,117 @@
+#Requires -Version 5.1
+$ErrorActionPreference = "Stop"
+
+$SCCACHE_VERSION = "v0.10.0"
+$SCCACHE_DIR = "./target/sccache"
+
+function Install-Sccache {
+    New-Item -ItemType Directory -Path $SCCACHE_DIR -Force | Out-Null
+
+    $sccachePath = Join-Path $SCCACHE_DIR "sccache.exe"
+
+    if (Test-Path $sccachePath) {
+        Write-Host "sccache already cached: $(& $sccachePath --version)"
+    }
+    else {
+        Write-Host "Installing sccache ${SCCACHE_VERSION} from GitHub releases..."
+
+        $arch = if ([Environment]::Is64BitOperatingSystem) { "x86_64" } else { "i686" }
+        $archive = "sccache-${SCCACHE_VERSION}-${arch}-pc-windows-msvc.zip"
+        $basename = "sccache-${SCCACHE_VERSION}-${arch}-pc-windows-msvc"
+        $url = "https://github.com/mozilla/sccache/releases/download/${SCCACHE_VERSION}/${archive}"
+
+        $tempDir = Join-Path $env:TEMP "sccache-install"
+        New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
+
+        try {
+            $archivePath = Join-Path $tempDir $archive
+            Invoke-WebRequest -Uri $url -OutFile $archivePath
+            Expand-Archive -Path $archivePath -DestinationPath $tempDir
+
+            $extractedPath = Join-Path $tempDir $basename "sccache.exe"
+            Move-Item -Path $extractedPath -Destination $sccachePath -Force
+
+            Write-Host "Installed sccache: $(& $sccachePath --version)"
+        }
+        finally {
+            Remove-Item -Recurse -Force $tempDir -ErrorAction SilentlyContinue
+        }
+    }
+
+    $absolutePath = (Resolve-Path $SCCACHE_DIR).Path
+    if ($env:GITHUB_PATH) {
+        $absolutePath | Out-File -FilePath $env:GITHUB_PATH -Append -Encoding utf8
+    }
+    $env:PATH = "$absolutePath;$env:PATH"
+}
+
+function Configure-Sccache {
+    if (-not $env:R2_ACCOUNT_ID) {
+        Write-Host "R2_ACCOUNT_ID not set, skipping sccache configuration"
+        return
+    }
+
+    Write-Host "Configuring sccache with Cloudflare R2..."
+
+    $bucket = if ($env:SCCACHE_BUCKET) { $env:SCCACHE_BUCKET } else { "sccache-zed" }
+    $keyPrefix = if ($env:SCCACHE_KEY_PREFIX) { $env:SCCACHE_KEY_PREFIX } else { "sccache/" }
+    $baseDir = if ($env:GITHUB_WORKSPACE) { $env:GITHUB_WORKSPACE } else { (Get-Location).Path }
+
+    # Set in current process
+    $env:SCCACHE_ENDPOINT = "https://$($env:R2_ACCOUNT_ID).r2.cloudflarestorage.com"
+    $env:SCCACHE_BUCKET = $bucket
+    $env:SCCACHE_REGION = "auto"
+    $env:SCCACHE_S3_KEY_PREFIX = $keyPrefix
+    $env:SCCACHE_BASEDIR = $baseDir
+    $env:AWS_ACCESS_KEY_ID = $env:R2_ACCESS_KEY_ID
+    $env:AWS_SECRET_ACCESS_KEY = $env:R2_SECRET_ACCESS_KEY
+    $env:RUSTC_WRAPPER = "sccache"
+
+    # Also write to GITHUB_ENV for subsequent steps
+    if ($env:GITHUB_ENV) {
+        @(
+            "SCCACHE_ENDPOINT=$($env:SCCACHE_ENDPOINT)"
+            "SCCACHE_BUCKET=$($env:SCCACHE_BUCKET)"
+            "SCCACHE_REGION=$($env:SCCACHE_REGION)"
+            "SCCACHE_S3_KEY_PREFIX=$($env:SCCACHE_S3_KEY_PREFIX)"
+            "SCCACHE_BASEDIR=$($env:SCCACHE_BASEDIR)"
+            "AWS_ACCESS_KEY_ID=$($env:AWS_ACCESS_KEY_ID)"
+            "AWS_SECRET_ACCESS_KEY=$($env:AWS_SECRET_ACCESS_KEY)"
+            "RUSTC_WRAPPER=$($env:RUSTC_WRAPPER)"
+        ) | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
+    }
+
+    Write-Host "✓ sccache configured with Cloudflare R2 (bucket: $bucket)"
+}
+
+function Show-Config {
+    Write-Host "=== sccache configuration ==="
+    Write-Host "sccache version: $(sccache --version)"
+    Write-Host "RUSTC_WRAPPER: $($env:RUSTC_WRAPPER ?? '<not set>')"
+    Write-Host "SCCACHE_BUCKET: $($env:SCCACHE_BUCKET ?? '<not set>')"
+    Write-Host "SCCACHE_ENDPOINT: $($env:SCCACHE_ENDPOINT ?? '<not set>')"
+    Write-Host "SCCACHE_REGION: $($env:SCCACHE_REGION ?? '<not set>')"
+    Write-Host "SCCACHE_S3_KEY_PREFIX: $($env:SCCACHE_S3_KEY_PREFIX ?? '<not set>')"
+    Write-Host "SCCACHE_BASEDIR: $($env:SCCACHE_BASEDIR ?? '<not set>')"
+
+    if ($env:AWS_ACCESS_KEY_ID) {
+        Write-Host "AWS_ACCESS_KEY_ID: <set>"
+    }
+    else {
+        Write-Host "AWS_ACCESS_KEY_ID: <not set>"
+    }
+
+    if ($env:AWS_SECRET_ACCESS_KEY) {
+        Write-Host "AWS_SECRET_ACCESS_KEY: <set>"
+    }
+    else {
+        Write-Host "AWS_SECRET_ACCESS_KEY: <not set>"
+    }
+
+    Write-Host "=== sccache stats ==="
+    sccache --show-stats
+}
+
+Install-Sccache
+Configure-Sccache
+Show-Config

tooling/xtask/src/tasks/workflows/run_agent_evals.rs 🔗

@@ -74,8 +74,10 @@ fn agent_evals() -> NamedJob {
             .add_step(steps::cache_rust_dependencies_namespace())
             .map(steps::install_linux_dependencies)
             .add_step(setup_cargo_config(Platform::Linux))
+            .add_step(steps::setup_sccache(Platform::Linux))
             .add_step(steps::script("cargo build --package=eval"))
             .add_step(add_api_keys(run_eval()))
+            .add_step(steps::show_sccache_stats(Platform::Linux))
             .add_step(steps::cleanup_cargo_config(Platform::Linux)),
     )
 }
@@ -138,9 +140,11 @@ fn cron_unit_evals_job() -> Job {
         .add_step(steps::setup_cargo_config(Platform::Linux))
         .add_step(steps::cache_rust_dependencies_namespace())
         .map(steps::install_linux_dependencies)
+        .add_step(steps::setup_sccache(Platform::Linux))
         .add_step(steps::cargo_install_nextest())
         .add_step(steps::clear_target_dir_if_large(Platform::Linux))
         .add_step(script_step)
+        .add_step(steps::show_sccache_stats(Platform::Linux))
         .add_step(steps::cleanup_cargo_config(Platform::Linux))
 }
 
@@ -153,11 +157,13 @@ fn unit_evals(commit: Option<&WorkflowInput>) -> Job {
         .add_step(steps::setup_cargo_config(Platform::Linux))
         .add_step(steps::cache_rust_dependencies_namespace())
         .map(steps::install_linux_dependencies)
+        .add_step(steps::setup_sccache(Platform::Linux))
         .add_step(steps::cargo_install_nextest())
         .add_step(steps::clear_target_dir_if_large(Platform::Linux))
         .add_step(match commit {
             Some(commit) => script_step.add_env(("UNIT_EVAL_COMMIT", commit)),
             None => script_step,
         })
+        .add_step(steps::show_sccache_stats(Platform::Linux))
         .add_step(steps::cleanup_cargo_config(Platform::Linux))
 }

tooling/xtask/src/tasks/workflows/run_tests.rs 🔗

@@ -345,8 +345,10 @@ fn check_workspace_binaries() -> NamedJob {
             .add_step(steps::setup_cargo_config(Platform::Linux))
             .add_step(steps::cache_rust_dependencies_namespace())
             .map(steps::install_linux_dependencies)
+            .add_step(steps::setup_sccache(Platform::Linux))
             .add_step(steps::script("cargo build -p collab"))
             .add_step(steps::script("cargo build --workspace --bins --examples"))
+            .add_step(steps::show_sccache_stats(Platform::Linux))
             .add_step(steps::cleanup_cargo_config(Platform::Linux)),
     )
 }
@@ -371,7 +373,9 @@ pub(crate) fn clippy(platform: Platform) -> NamedJob {
                 platform == Platform::Linux,
                 steps::install_linux_dependencies,
             )
-            .add_step(steps::clippy(platform)),
+            .add_step(steps::setup_sccache(platform))
+            .add_step(steps::clippy(platform))
+            .add_step(steps::show_sccache_stats(platform)),
     }
 }
 
@@ -417,6 +421,7 @@ fn run_platform_tests_impl(platform: Platform, filter_packages: bool) -> NamedJo
                 platform == Platform::Linux,
                 steps::install_linux_dependencies,
             )
+            .add_step(steps::setup_sccache(platform))
             .add_step(steps::setup_node())
             .when(
                 platform == Platform::Linux || platform == Platform::Mac,
@@ -431,6 +436,7 @@ fn run_platform_tests_impl(platform: Platform, filter_packages: bool) -> NamedJo
             .when(!filter_packages, |job| {
                 job.add_step(steps::cargo_nextest(platform))
             })
+            .add_step(steps::show_sccache_stats(platform))
             .add_step(steps::cleanup_cargo_config(platform)),
     }
 }
@@ -494,7 +500,9 @@ fn doctests() -> NamedJob {
             .add_step(steps::cache_rust_dependencies_namespace())
             .map(steps::install_linux_dependencies)
             .add_step(steps::setup_cargo_config(Platform::Linux))
+            .add_step(steps::setup_sccache(Platform::Linux))
             .add_step(run_doctests())
+            .add_step(steps::show_sccache_stats(Platform::Linux))
             .add_step(steps::cleanup_cargo_config(Platform::Linux)),
     )
 }

tooling/xtask/src/tasks/workflows/steps.rs 🔗

@@ -2,6 +2,8 @@ use gh_workflow::*;
 
 use crate::tasks::workflows::{runners::Platform, vars, vars::StepOutput};
 
+const SCCACHE_R2_BUCKET: &str = "sccache-zed";
+
 const BASH_SHELL: &str = "bash -euxo pipefail {0}";
 // https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idstepsshell
 pub const PWSH_SHELL: &str = "pwsh";
@@ -155,6 +157,24 @@ pub fn cache_rust_dependencies_namespace() -> Step<Use> {
         .add_with(("path", "~/.rustup"))
 }
 
+pub fn setup_sccache(platform: Platform) -> Step<Run> {
+    let step = match platform {
+        Platform::Windows => named::pwsh("./script/setup-sccache.ps1"),
+        Platform::Linux | Platform::Mac => named::bash("./script/setup-sccache"),
+    };
+    step.add_env(("R2_ACCOUNT_ID", vars::R2_ACCOUNT_ID))
+        .add_env(("R2_ACCESS_KEY_ID", vars::R2_ACCESS_KEY_ID))
+        .add_env(("R2_SECRET_ACCESS_KEY", vars::R2_SECRET_ACCESS_KEY))
+        .add_env(("SCCACHE_BUCKET", SCCACHE_R2_BUCKET))
+}
+
+pub fn show_sccache_stats(platform: Platform) -> Step<Run> {
+    match platform {
+        Platform::Windows => named::pwsh("sccache --show-stats; exit 0"),
+        Platform::Linux | Platform::Mac => named::bash("sccache --show-stats || true"),
+    }
+}
+
 pub fn cache_nix_dependencies_namespace() -> Step<Use> {
     named::uses("namespacelabs", "nscloud-cache-action", "v1").add_with(("cache", "nix"))
 }

tooling/xtask/src/tasks/workflows/vars.rs 🔗

@@ -46,6 +46,9 @@ secret!(DISCORD_WEBHOOK_RELEASE_NOTES);
 secret!(WINGET_TOKEN);
 secret!(VERCEL_TOKEN);
 secret!(SLACK_WEBHOOK_WORKFLOW_FAILURES);
+secret!(R2_ACCOUNT_ID);
+secret!(R2_ACCESS_KEY_ID);
+secret!(R2_SECRET_ACCESS_KEY);
 
 // todo(ci) make these secrets too...
 var!(AZURE_SIGNING_ACCOUNT_NAME);