From 19eb70deba80c0120d69b7ec87e5c40247978b63 Mon Sep 17 00:00:00 2001 From: foxnne Date: Thu, 8 Oct 2026 09:01:25 -0500 Subject: [PATCH] ci: a Zig cache is one cold build, saved once Every other CI run on main built from nothing. setup-zig saved each run's `.zig-cache` under a new key and restored the newest, so a warm run saved what it restored plus everything it rebuilt: Linux's test cache went from 3.7 GB to 6-7 GB in one run, passed the 5 GiB limit, and setup-zig cleared it and saved it empty (run 37682171347: "Cache directory reached 6943035149 bytes ... clearing cache"). The next run restored the empty cache (37785306976: 188 B) and started cold. The near-duplicates also filled the repo's 10 GB of Actions cache. setup-zig's caching is off (`use-cache: false`; it still points ZIG_GLOBAL_CACHE_DIR and ZIG_LOCAL_CACHE_DIR at `.zig-cache`). Each job restores and saves that directory with actions/cache under a fixed key: Zig version, job, OS, arch, and the hash of every build.zig.zon, taken before anything is unpacked. A fixed key can't be overwritten, so only a run that missed saves, and only if it succeeded. A job's cache is one complete cold build, and every later run with the same dependencies starts from it. The Windows cross-build's key was still worked out inline, the bug the test job's comment describes: it saved under a hash taken after the fetch unpacked zig-pkg/, so every restore missed. It now takes its key from a step like the others. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 96 ++++++++++++++++++++++++++++++---------- 1 file changed, 72 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cec5b7c84..b95c8f906 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,31 +76,40 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - # The cache key, worked out once, here, before anything is unpacked. setup-zig reads its - # `cache-key` input twice — to restore at the start and to save in its post step — and - # GitHub evaluates the expression afresh each time. By the post step the build has - # unpacked every dependency into zig-pkg/, each with its own build.zig.zon, so a - # `hashFiles('**/build.zig.zon')` in the input saved under a hash no restore ever asked - # for: every run missed. A step output is fixed once set. + # The Zig cache's key, worked out once, here, before anything is unpacked. The fetch below + # unpacks every dependency into zig-pkg/, each with its own build.zig.zon, so a + # `hashFiles('**/build.zig.zon')` evaluated after it names a hash no restore ever asked + # for. A step output is fixed once set. - name: Zig cache key id: zig-cache-key shell: bash - run: echo "key=${{ hashFiles('**/build.zig.zon') }}" >> "$GITHUB_OUTPUT" + run: echo "key=zig-${{ env.ZIG_VERSION }}-${{ github.job }}-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/build.zig.zon') }}" >> "$GITHUB_OUTPUT" - uses: mlugg/setup-zig@v2 with: version: ${{ env.ZIG_VERSION }} - cache-key: ${{ steps.zig-cache-key.outputs.key }} - # MiB. Past it setup-zig clears the cache and saves it empty. A Linux run's cache - # is ~3.5 GB (the tests, the wasm build and four example apps, each compiling its - # own SDL3), so the default 2 GiB threw it away every run. - cache-size-limit: 5120 - # setup-zig exports ZIG_GLOBAL_CACHE_DIR / ZIG_LOCAL_CACHE_DIR and caches that - # directory across runs. Do NOT override those vars: pointing them elsewhere - # leaves the action's cache empty, so every run re-downloads every dependency - # from codeload.github.com — which is where `HttpConnectionClosing` comes from. - # (Zig 0.16 caches the downloaded *tarballs* under $ZIG_GLOBAL_CACHE_DIR/p and - # unpacks them into the project-local zig-pkg/; only the tarballs are cached, - # so a cache hit still costs an unpack but no network.) + # The Zig cache is restored and saved by this job's own steps; see below. + use-cache: false + # setup-zig points ZIG_GLOBAL_CACHE_DIR and ZIG_LOCAL_CACHE_DIR at `.zig-cache`, the + # directory cached here. Do NOT override those vars: pointing them elsewhere leaves the + # cache empty, so every run re-downloads every dependency from codeload.github.com — + # which is where `HttpConnectionClosing` comes from. (Zig 0.16 caches the downloaded + # *tarballs* under $ZIG_GLOBAL_CACHE_DIR/p and unpacks them into the project-local + # zig-pkg/; a cache hit still costs an unpack but no network.) + # + # A job's cache is one cold build, saved once; every later run with the same + # dependencies, Zig and OS starts from it. setup-zig's own caching saved every run under + # a new key and restored the newest, so a warm run saved what it restored plus everything + # it rebuilt: Linux's grew from 3.7 GB to 6-7 GB in one run, passed the size limit, and + # was cleared and saved empty, and the run after it started cold. Every other run on + # main built from nothing, and the near-duplicates filled the repo's 10 GB. Here the key + # is fixed and a key can't be overwritten, so only a run that missed saves, and only if + # it succeeded: a run cancelled or failed mid-build never leaves a partial cache behind. + - name: Restore Zig cache + id: zig-cache + uses: actions/cache/restore@v4 + with: + path: .zig-cache + key: ${{ steps.zig-cache-key.outputs.key }} - name: Pre-create Zig cache tmp/ shell: bash run: mkdir -p "$ZIG_GLOBAL_CACHE_DIR/tmp" "$ZIG_LOCAL_CACHE_DIR/tmp" @@ -151,6 +160,13 @@ jobs: shell: bash run: bash scripts/ci-retry.sh zig build test-sdk-version --summary all + - name: Save Zig cache + if: success() && steps.zig-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v4 + with: + path: .zig-cache + key: ${{ steps.zig-cache-key.outputs.key }} + # The headless integration tests: real widgets, layouts and drags driven on dvui's testing # backend (no window, no GPU), plus the SDK, sizing and plugin-loader suites. The suite most # likely to catch a layout or drag regression, and until this job nothing ran it. Linux only: @@ -167,16 +183,21 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - # See the test job: the key is fixed before anything is unpacked. + # The Zig cache as in the test job: a fixed key, one cold build saved once. - name: Zig cache key id: zig-cache-key shell: bash - run: echo "key=${{ hashFiles('**/build.zig.zon') }}" >> "$GITHUB_OUTPUT" + run: echo "key=zig-${{ env.ZIG_VERSION }}-${{ github.job }}-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/build.zig.zon') }}" >> "$GITHUB_OUTPUT" - uses: mlugg/setup-zig@v2 with: version: ${{ env.ZIG_VERSION }} - cache-key: ${{ steps.zig-cache-key.outputs.key }} - cache-size-limit: 5120 + use-cache: false + - name: Restore Zig cache + id: zig-cache + uses: actions/cache/restore@v4 + with: + path: .zig-cache + key: ${{ steps.zig-cache-key.outputs.key }} - name: Pre-create Zig cache tmp/ shell: bash run: mkdir -p "$ZIG_GLOBAL_CACHE_DIR/tmp" "$ZIG_LOCAL_CACHE_DIR/tmp" @@ -187,6 +208,13 @@ jobs: shell: bash run: bash scripts/ci-retry.sh zig build test-integration --summary all + - name: Save Zig cache + if: success() && steps.zig-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v4 + with: + path: .zig-cache + key: ${{ steps.zig-cache-key.outputs.key }} + # Fizzy's own backend on Windows — the default there too now (`build/exe.zig`'s # `defaultNativeBackend`) — which nothing else here builds: the consumer apps above run on Linux only. # Cross-compiled from Linux for the GNU ABI, which needs no MSVC SDK (release.yml's msvc @@ -198,10 +226,23 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + # The Zig cache as in the test job. This job's key used to be worked out inline, so + # setup-zig saved under a hash taken after the fetch had unpacked zig-pkg/ and every + # restore missed. + - name: Zig cache key + id: zig-cache-key + shell: bash + run: echo "key=zig-${{ env.ZIG_VERSION }}-${{ github.job }}-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/build.zig.zon') }}" >> "$GITHUB_OUTPUT" - uses: mlugg/setup-zig@v2 with: version: ${{ env.ZIG_VERSION }} - cache-key: ${{ hashFiles('**/build.zig.zon') }} + use-cache: false + - name: Restore Zig cache + id: zig-cache + uses: actions/cache/restore@v4 + with: + path: .zig-cache + key: ${{ steps.zig-cache-key.outputs.key }} - name: Pre-create Zig cache tmp/ run: mkdir -p "$ZIG_GLOBAL_CACHE_DIR/tmp" "$ZIG_LOCAL_CACHE_DIR/tmp" - name: Fetch dependencies (with retries) @@ -209,6 +250,13 @@ jobs: - name: Build for x86_64-windows-gnu with -Dnative-backend=fizzy run: bash scripts/ci-retry.sh zig build -Dtarget=x86_64-windows-gnu -Dnative-backend=fizzy -Dno-emit --summary all + - name: Save Zig cache + if: success() && steps.zig-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v4 + with: + path: .zig-cache + key: ${{ steps.zig-cache-key.outputs.key }} + # The required check. Passes when every job above passed or was skipped (a docs-only change # skips the builds); fails on any failure or cancellation, `changes` included. A job added to # this workflow joins the gate by being added to `needs`.