Skip to content

Commit 12a49e9

Browse files
authored
chore(ci): switch sccache from local disk to memcached backend (#68)
1 parent c4a6970 commit 12a49e9

11 files changed

Lines changed: 75 additions & 24 deletions

File tree

.github/workflows/checks.yml

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,20 @@ env:
99
CARGO_TERM_COLOR: always
1010
CARGO_INCREMENTAL: "0"
1111
MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12+
SCCACHE_MEMCACHED_ENDPOINT: ${{ vars.SCCACHE_MEMCACHED_ENDPOINT }}
1213

1314
permissions:
1415
contents: read
1516
packages: read
1617

1718
jobs:
1819
rust:
19-
name: Rust
20-
runs-on: build-amd64
20+
name: Rust (${{ matrix.runner }})
21+
strategy:
22+
fail-fast: false
23+
matrix:
24+
runner: [build-amd64, build-arm64]
25+
runs-on: ${{ matrix.runner }}
2126
container:
2227
image: ghcr.io/nvidia/nv-agent-env/ci:latest
2328
credentials:
@@ -26,13 +31,17 @@ jobs:
2631
steps:
2732
- uses: actions/checkout@v4
2833

29-
- name: Cache sccache
30-
uses: actions/cache@v4
34+
- name: Install tools
35+
run: mise install
36+
37+
- name: Cache Rust target and registry
38+
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
3139
with:
32-
path: .cache/sccache
33-
key: sccache-rust-${{ runner.arch }}-${{ hashFiles('Cargo.lock') }}
34-
restore-keys: |
35-
sccache-rust-${{ runner.arch }}-
40+
# Separate caches for clippy (check-like) vs test (full build)
41+
# so they don't thrash each other's artifacts
42+
shared-key: rust-checks-${{ matrix.runner }}
43+
# Cache the sccache directory too
44+
cache-directories: .cache/sccache
3645

3746
- name: Format
3847
run: mise run rust:format:check
@@ -45,11 +54,15 @@ jobs:
4554

4655
- name: sccache stats
4756
if: always()
48-
run: sccache --show-stats
57+
run: mise x -- sccache --show-stats
4958

5059
python:
51-
name: Python
52-
runs-on: build-amd64
60+
name: Python (${{ matrix.runner }})
61+
strategy:
62+
fail-fast: false
63+
matrix:
64+
runner: [build-amd64, build-arm64]
65+
runs-on: ${{ matrix.runner }}
5366
container:
5467
image: ghcr.io/nvidia/nv-agent-env/ci:latest
5568
credentials:
@@ -58,6 +71,9 @@ jobs:
5871
steps:
5972
- uses: actions/checkout@v4
6073

74+
- name: Install tools
75+
run: mise install
76+
6177
- name: Install dependencies
6278
run: uv sync --frozen
6379

.github/workflows/docker-build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ on:
2020

2121
env:
2222
MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
23+
SCCACHE_MEMCACHED_ENDPOINT: ${{ vars.SCCACHE_MEMCACHED_ENDPOINT }}
2324

2425
permissions:
2526
contents: read

.github/workflows/publish.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ jobs:
5050
- /var/run/docker.sock:/var/run/docker.sock
5151
env:
5252
MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
53+
SCCACHE_MEMCACHED_ENDPOINT: ${{ vars.SCCACHE_MEMCACHED_ENDPOINT }}
5354
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
5455
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
5556
AWS_DEFAULT_REGION: us-west-2
@@ -93,6 +94,7 @@ jobs:
9394
- /var/run/docker.sock:/var/run/docker.sock
9495
env:
9596
MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
97+
SCCACHE_MEMCACHED_ENDPOINT: ${{ vars.SCCACHE_MEMCACHED_ENDPOINT }}
9698
NAV_PYPI_S3_BUCKET: navigator-pypi-artifacts
9799
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
98100
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

architecture/build-containers.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ A k3s image with bundled Helm charts and Kubernetes manifests for single-contain
121121

122122
Two Dockerfiles produce Python wheels for the CLI package distribution. These are not deployed as running containers.
123123

124-
- **`Dockerfile.python-wheels`** -- Builds Linux amd64/arm64 wheels using Maturin with a two-pass Rust build (dependency prebuild + final wheel build), BuildKit cache mounts for cargo registry/git/target and sccache, and `cross-build.sh` for conditional cross-toolchain installation. The final build step patches workspace version inside the container layer from `NAVIGATOR_CARGO_VERSION` (computed before Docker build), preserving cacheable dependency layers and avoiding dirty working-tree edits. Output stage is `scratch` with only the `.whl` files.
124+
- **`Dockerfile.python-wheels`** -- Builds Linux amd64/arm64 wheels using Maturin with a two-pass Rust build (dependency prebuild + final wheel build), BuildKit cache mounts for cargo registry/git/target, sccache (backed by memcached when `SCCACHE_MEMCACHED_ENDPOINT` build arg is provided), and `cross-build.sh` for conditional cross-toolchain installation. The final build step patches workspace version inside the container layer from `NAVIGATOR_CARGO_VERSION` (computed before Docker build), preserving cacheable dependency layers and avoiding dirty working-tree edits. Output stage is `scratch` with only the `.whl` files.
125125
- **`Dockerfile.python-wheels-macos`** -- Builds macOS arm64 wheels using osxcross (cross-compiling from Linux) with the same two-pass dependency caching pattern and cargo cache mounts. Version injection uses the same in-container workspace-version patch from `NAVIGATOR_CARGO_VERSION`, avoiding host-side file edits that break Docker layer caching. Uses `crazymax/osxcross:latest` as the cross-toolchain source. The `OSXCROSS_IMAGE` build arg allows using a mirrored registry image instead of Docker Hub.
126126

127127
### CI Runner Image (`navigator-ci`)
@@ -386,7 +386,7 @@ Container builds use Docker BuildKit with local cache directories:
386386
- `build/scripts/docker-build-component.sh` stores per-component caches in `.cache/buildkit/<component>`.
387387
- `build/scripts/docker-build-cluster.sh` stores the cluster image cache in `.cache/buildkit/cluster`.
388388
- `mise run python:build:multiarch` stores per-platform wheel caches in `.cache/buildkit/python-wheels/<platform>` for local builds when using a `docker-container` buildx driver.
389-
- Rust-heavy Dockerfiles use BuildKit cache mounts for cargo registry and target directories, keyed by image name and `TARGETARCH`, with `sharing=locked` to prevent concurrent cache corruption in parallel CI builds.
389+
- Rust-heavy Dockerfiles use BuildKit cache mounts for cargo registry, cargo target, and sccache local disk directories, keyed by image name and `TARGETARCH`, with `sharing=locked` to prevent concurrent cache corruption in parallel CI builds. The cargo target mount gives cargo a persistent `target/` directory for true incremental rebuilds on source-only changes. sccache uses memcached in CI (`SCCACHE_MEMCACHED_ENDPOINT`) and falls back to the local disk cache mount for local dev builds, providing a second layer of caching at the compilation unit level.
390390
- When the active buildx driver is `docker` (not `docker-container`), local cache import/export flags are skipped automatically because the docker driver cannot export local caches. In CI, cache export is also skipped.
391391
- For local single-arch builds, the scripts auto-select a builder with the native `docker` driver (matching the active Docker context) so images land directly in the Docker image store without slow tarball export.
392392

@@ -395,7 +395,7 @@ Container builds use Docker BuildKit with local cache directories:
395395
In CI pipelines:
396396

397397
- Remote BuildKit daemons (`buildkit-amd64` and `buildkit-arm64`) are used as persistent builders via `driver: remote`. Their built-in layer cache persists across builds, so no external cache (registry-backed or otherwise) is needed in CI.
398-
- Rust lint/test jobs cache `.cache/sccache/` and `target/` with keys derived from `Cargo.lock` and Rust task config files, scoped per runner architecture.
398+
- Rust lint/test jobs cache `target/` with keys derived from `Cargo.lock` and Rust task config files, scoped per runner architecture. sccache uses a shared memcached backend (`SCCACHE_MEMCACHED_ENDPOINT`) instead of local disk.
399399
- CI sets `CARGO_INCREMENTAL=0` to favor deterministic clean builds over incremental metadata churn.
400400
- Publish jobs mirror `crazymax/osxcross:latest` into `$CI_REGISTRY_IMAGE/third_party/osxcross:latest` (when missing) and set `OSXCROSS_IMAGE` so macOS wheel Docker builds consume the mirrored image instead of pulling from Docker Hub on each run.
401401
- The sandbox e2e test job tags and pushes component images to the GitLab project registry (`$CI_REGISTRY_IMAGE`) and configures cluster bootstrap to pull from that remote registry with CI credentials.

build/scripts/docker-build-component.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,16 @@ if [[ "${DOCKER_PUSH:-}" == "1" ]]; then
9393
OUTPUT_FLAG="--push"
9494
fi
9595

96+
SCCACHE_ARGS=()
97+
if [[ -n "${SCCACHE_MEMCACHED_ENDPOINT:-}" ]]; then
98+
SCCACHE_ARGS=(--build-arg "SCCACHE_MEMCACHED_ENDPOINT=${SCCACHE_MEMCACHED_ENDPOINT}")
99+
fi
100+
96101
docker buildx build \
97102
${BUILDER_ARGS[@]+"${BUILDER_ARGS[@]}"} \
98103
${DOCKER_PLATFORM:+--platform ${DOCKER_PLATFORM}} \
99104
${CACHE_ARGS[@]+"${CACHE_ARGS[@]}"} \
105+
${SCCACHE_ARGS[@]+"${SCCACHE_ARGS[@]}"} \
100106
-f "${DOCKERFILE}" \
101107
-t "${IMAGE_NAME}:${IMAGE_TAG}" \
102108
--provenance=false \

build/scripts/docker-publish-multiarch.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ for component in sandbox server; do
121121
if [ "$component" = "sandbox" ]; then
122122
BUILD_ARGS="--build-arg RUST_BUILD_PROFILE=${RUST_BUILD_PROFILE:-release}"
123123
fi
124+
if [ -n "${SCCACHE_MEMCACHED_ENDPOINT:-}" ]; then
125+
BUILD_ARGS="${BUILD_ARGS} --build-arg SCCACHE_MEMCACHED_ENDPOINT=${SCCACHE_MEMCACHED_ENDPOINT}"
126+
fi
124127
DOCKERFILE=$(resolve_dockerfile "${component}")
125128
FULL_IMAGE="${REGISTRY}/${IMAGE_PREFIX}${component}"
126129
docker buildx build \

deploy/docker/Dockerfile.python-wheels

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ ARG TARGETARCH
2626
ARG BUILDARCH
2727
ARG NAVIGATOR_CARGO_VERSION
2828

29-
ENV SCCACHE_DIR=/tmp/sccache
29+
ARG SCCACHE_MEMCACHED_ENDPOINT
3030

3131
WORKDIR /build
3232

@@ -55,6 +55,8 @@ RUN mkdir -p crates/navigator-cli/src crates/navigator-core/src crates/navigator
5555
touch crates/navigator-bootstrap/src/lib.rs
5656

5757
# Build dependencies only (cached unless Cargo.toml/lock changes).
58+
# sccache uses memcached in CI or the local disk cache mount for local dev.
59+
# The cargo-target mount gives cargo a persistent target/ dir for incremental rebuilds.
5860
RUN --mount=type=cache,id=cargo-registry-python-wheels-${TARGETARCH},sharing=locked,target=/root/.cargo/registry \
5961
--mount=type=cache,id=cargo-git-python-wheels-${TARGETARCH},sharing=locked,target=/root/.cargo/git \
6062
--mount=type=cache,id=cargo-target-python-wheels-${TARGETARCH},sharing=locked,target=/build/target \
@@ -68,8 +70,6 @@ COPY dev-sandbox-policy.yaml ./
6870
COPY python/ python/
6971

7072
# Touch source files to ensure they're rebuilt (not the cached dummy).
71-
# Touch build.rs and proto files to force proto code regeneration when the
72-
# cargo target cache mount retains stale OUT_DIR artifacts from prior builds.
7373
RUN touch crates/navigator-cli/src/main.rs \
7474
crates/navigator-cli/src/lib.rs \
7575
crates/navigator-bootstrap/src/lib.rs \

deploy/docker/Dockerfile.server

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
1515
COPY deploy/docker/cross-build.sh /usr/local/bin/
1616
RUN . cross-build.sh && install_cross_toolchain && install_sccache && add_rust_target
1717

18-
ENV SCCACHE_DIR=/tmp/sccache
18+
ARG SCCACHE_MEMCACHED_ENDPOINT
1919

2020
WORKDIR /build
2121

@@ -42,7 +42,10 @@ RUN mkdir -p crates/navigator-cli/src crates/navigator-core/src crates/navigator
4242
# Copy proto files needed for build
4343
COPY proto/ proto/
4444

45-
# Build dependencies only (cached unless Cargo.toml/lock changes)
45+
# Build dependencies only (cached unless Cargo.toml/lock changes).
46+
# sccache uses memcached in CI (SCCACHE_MEMCACHED_ENDPOINT) or the local
47+
# disk cache mount for local dev builds. The cargo-target mount gives cargo
48+
# a persistent target/ dir for true incremental rebuilds on source changes.
4649
RUN --mount=type=cache,id=cargo-registry-server-${TARGETARCH},sharing=locked,target=/usr/local/cargo/registry \
4750
--mount=type=cache,id=cargo-target-server-${TARGETARCH},sharing=locked,target=/build/target \
4851
--mount=type=cache,id=sccache-server-${TARGETARCH},sharing=locked,target=/tmp/sccache \

deploy/docker/cross-build.sh

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,28 @@ export_cross_env() {
8181
# Automatically wraps with sccache when available.
8282
cargo_cross_build() {
8383
export_cross_env
84+
# Unset empty SCCACHE_MEMCACHED_ENDPOINT so sccache falls back to the
85+
# local disk cache instead of erroring on an empty endpoint string.
86+
if [ -z "${SCCACHE_MEMCACHED_ENDPOINT:-}" ]; then
87+
unset SCCACHE_MEMCACHED_ENDPOINT 2>/dev/null || true
88+
fi
89+
# Default sccache local disk cache to /tmp/sccache (matches BuildKit
90+
# cache mount target in Dockerfiles) when no dir is explicitly set.
91+
export SCCACHE_DIR="${SCCACHE_DIR:-/tmp/sccache}"
8492
if command -v sccache >/dev/null 2>&1; then
8593
export RUSTC_WRAPPER=sccache
8694
fi
8795
local target_flag=""
8896
if is_cross; then target_flag="--target $(rust_target)"; fi
89-
cargo build $target_flag "$@"
97+
# Retry once after cleaning if the build fails. BuildKit cargo-target cache
98+
# mounts can retain stale .rmeta files from prior builds with different
99+
# dependency versions; cargo clean purges them so the retry succeeds.
100+
# sccache still has the compiled objects, so the clean rebuild is fast.
101+
if ! cargo build $target_flag "$@"; then
102+
echo "cargo build failed; cleaning stale target cache and retrying..." >&2
103+
cargo clean 2>/dev/null || true
104+
cargo build $target_flag "$@"
105+
fi
90106
}
91107

92108
# Print the directory containing the compiled binary.

deploy/docker/sandbox/Dockerfile.base

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
1616
COPY deploy/docker/cross-build.sh /usr/local/bin/
1717
RUN . cross-build.sh && install_cross_toolchain && install_sccache && add_rust_target
1818

19-
ENV SCCACHE_DIR=/tmp/sccache
19+
ARG SCCACHE_MEMCACHED_ENDPOINT
2020

2121
WORKDIR /build
2222

@@ -44,7 +44,9 @@ RUN mkdir -p crates/navigator-cli/src crates/navigator-core/src crates/navigator
4444
COPY proto/ proto/
4545
COPY dev-sandbox-policy.rego ./
4646

47-
# Build dependencies only (cached unless Cargo.toml/lock changes)
47+
# Build dependencies only (cached unless Cargo.toml/lock changes).
48+
# sccache uses memcached in CI or the local disk cache mount for local dev.
49+
# The cargo-target mount gives cargo a persistent target/ dir for incremental rebuilds.
4850
RUN --mount=type=cache,id=cargo-registry-sandbox-${TARGETARCH},sharing=locked,target=/usr/local/cargo/registry \
4951
--mount=type=cache,id=cargo-target-sandbox-${TARGETARCH},sharing=locked,target=/build/target \
5052
--mount=type=cache,id=sccache-sandbox-${TARGETARCH},sharing=locked,target=/tmp/sccache \

0 commit comments

Comments
 (0)