From a7365b95118812ac5bf7d76618206276f3070069 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Fri, 21 Aug 2026 11:59:28 +0200 Subject: [PATCH] fix(ci): cache the cargo registry, not the 16 GB target dir The runner's 74 GB disk kept filling. Measured on the box: 24 GB of act cache, 23.18 GB of it created in 20 days -- ~1.15 GB/day against a 30-day-unused / 90-day-used GC, so it could never converge. Cause: src-tauri/target (16 GB locally: 9.6G debug, 3.2G release, 2.4G android) was cached under five separate keys, all keyed on hashFiles('**/Cargo.lock'). The release script stamps the version into Cargo.lock, so all five invalidated on every chore(release) -- 32 distinct lockfile revisions in three months. - Cache only registry/index, registry/cache and git/db. registry/src is omitted as well: cargo re-extracts it from the 155 MB of .crate tarballs rather than storing 1.1 GB extracted. - Collapse the five per-job keys into one shared cargo-registry key. They existed to keep debug/release target artifacts from clobbering each other; with target uncached, registry contents are target-independent and every job wants the same crates. - Split cargo-xwin into its own key. It tracks the xwin version in the builder image, not our lockfile, so keying it on Cargo.lock was re-downloading the whole Windows SDK on every release bump. - CARGO_INCREMENTAL=0: never reused across runs, 3.5 GB of the debug dir. - Installer artifact retention 30d -> 7d; tagged releases carry the binaries anyway. Inflow drops from ~5 GB to ~150 MB per lockfile change. Tradeoff: Rust jobs now compile cold every run (~31min vs ~9min on a cache hit for the Linux release build). Most runs already paid that, since a release bump invalidated every key. sccache with a hard size cap is the way back if it bites. --- .gitea/workflows/build-and-test.yml | 42 +++++++++--- .gitea/workflows/build-release.yml | 103 +++++++++++++++++++--------- 2 files changed, 102 insertions(+), 43 deletions(-) diff --git a/.gitea/workflows/build-and-test.yml b/.gitea/workflows/build-and-test.yml index 2645848d..996fee72 100644 --- a/.gitea/workflows/build-and-test.yml +++ b/.gitea/workflows/build-and-test.yml @@ -13,6 +13,10 @@ on: - '**/*.md' workflow_dispatch: +env: + # Incremental state is never reused between CI runs -- pure disk cost. + CARGO_INCREMENTAL: 0 + jobs: test: name: Run Tests @@ -33,13 +37,22 @@ jobs: - name: Cache Rust dependencies uses: actions/cache@v3 with: + # Registry only -- never src-tauri/target. That directory is ~16 GB and + # was cached under five separate keys, which filled the runner's 74 GB + # disk at ~1.15 GB/day (23 GB in 20 days, measured Aug 2026). + # registry/src is omitted too: cargo re-extracts it for free from + # registry/cache (155 MB of .crate tarballs vs 1.1 GB extracted). path: | - ~/.cargo/registry - ~/.cargo/git - src-tauri/target - key: ${{ runner.os }}-cargo-host-${{ hashFiles('**/Cargo.lock') }} + ~/.cargo/registry/index + ~/.cargo/registry/cache + ~/.cargo/git/db + # One shared key across every job. The old per-job keys existed to stop + # debug/release target artifacts clobbering each other; with target no + # longer cached, registry contents are target-independent, so all jobs + # want the same crates. First job to finish saves; the rest restore. + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} restore-keys: | - ${{ runner.os }}-cargo-host- + ${{ runner.os }}-cargo-registry- - name: Cache Node dependencies uses: actions/cache@v3 @@ -129,13 +142,22 @@ jobs: - name: Cache Rust dependencies uses: actions/cache@v3 with: + # Registry only -- never src-tauri/target. That directory is ~16 GB and + # was cached under five separate keys, which filled the runner's 74 GB + # disk at ~1.15 GB/day (23 GB in 20 days, measured Aug 2026). + # registry/src is omitted too: cargo re-extracts it for free from + # registry/cache (155 MB of .crate tarballs vs 1.1 GB extracted). path: | - ~/.cargo/registry - ~/.cargo/git - src-tauri/target - key: ${{ runner.os }}-cargo-android-${{ hashFiles('**/Cargo.lock') }} + ~/.cargo/registry/index + ~/.cargo/registry/cache + ~/.cargo/git/db + # One shared key across every job. The old per-job keys existed to stop + # debug/release target artifacts clobbering each other; with target no + # longer cached, registry contents are target-independent, so all jobs + # want the same crates. First job to finish saves; the rest restore. + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} restore-keys: | - ${{ runner.os }}-cargo-android- + ${{ runner.os }}-cargo-registry- - name: Cache Node dependencies uses: actions/cache@v3 diff --git a/.gitea/workflows/build-release.yml b/.gitea/workflows/build-release.yml index 151b4910..ab306152 100644 --- a/.gitea/workflows/build-release.yml +++ b/.gitea/workflows/build-release.yml @@ -13,6 +13,8 @@ on: env: RUST_BACKTRACE: 1 CARGO_TERM_COLOR: always + # Incremental state is never reused between CI runs -- pure disk cost. + CARGO_INCREMENTAL: 0 jobs: test: @@ -27,13 +29,22 @@ jobs: - name: Cache Rust dependencies uses: actions/cache@v3 with: + # Registry only -- never src-tauri/target. That directory is ~16 GB and + # was cached under five separate keys, which filled the runner's 74 GB + # disk at ~1.15 GB/day (23 GB in 20 days, measured Aug 2026). + # registry/src is omitted too: cargo re-extracts it for free from + # registry/cache (155 MB of .crate tarballs vs 1.1 GB extracted). path: | - ~/.cargo/registry - ~/.cargo/git - src-tauri/target - key: ${{ runner.os }}-cargo-host-${{ hashFiles('**/Cargo.lock') }} + ~/.cargo/registry/index + ~/.cargo/registry/cache + ~/.cargo/git/db + # One shared key across every job. The old per-job keys existed to stop + # debug/release target artifacts clobbering each other; with target no + # longer cached, registry contents are target-independent, so all jobs + # want the same crates. First job to finish saves; the rest restore. + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} restore-keys: | - ${{ runner.os }}-cargo-host- + ${{ runner.os }}-cargo-registry- - name: Cache Node dependencies uses: actions/cache@v3 @@ -88,21 +99,25 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - # ⚠️ Key must NOT collide with the test job's `cargo-host` key: the test - # job runs first and saves debug/clippy artifacts under its key, and - # actions/cache skips saving on an exact-key hit — so a shared key meant - # this job's *release* artifacts were never cached and every Linux release - # build compiled cold (~31min vs ~9min for the correctly-keyed Windows job). - name: Cache Rust dependencies uses: actions/cache@v3 with: + # Registry only -- never src-tauri/target. That directory is ~16 GB and + # was cached under five separate keys, which filled the runner's 74 GB + # disk at ~1.15 GB/day (23 GB in 20 days, measured Aug 2026). + # registry/src is omitted too: cargo re-extracts it for free from + # registry/cache (155 MB of .crate tarballs vs 1.1 GB extracted). path: | - ~/.cargo/registry - ~/.cargo/git - src-tauri/target - key: ${{ runner.os }}-cargo-linux-release-${{ hashFiles('**/Cargo.lock') }} + ~/.cargo/registry/index + ~/.cargo/registry/cache + ~/.cargo/git/db + # One shared key across every job. The old per-job keys existed to stop + # debug/release target artifacts clobbering each other; with target no + # longer cached, registry contents are target-independent, so all jobs + # want the same crates. First job to finish saves; the rest restore. + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} restore-keys: | - ${{ runner.os }}-cargo-linux-release- + ${{ runner.os }}-cargo-registry- - name: Cache Node dependencies uses: actions/cache@v3 @@ -160,7 +175,7 @@ jobs: with: name: jellytau-linux path: dist/linux/ - retention-days: 30 + retention-days: 7 build-windows: name: Build Windows @@ -178,14 +193,31 @@ jobs: - name: Cache Rust dependencies uses: actions/cache@v3 with: + # Registry only -- never src-tauri/target. That directory is ~16 GB and + # was cached under five separate keys, which filled the runner's 74 GB + # disk at ~1.15 GB/day (23 GB in 20 days, measured Aug 2026). + # registry/src is omitted too: cargo re-extracts it for free from + # registry/cache (155 MB of .crate tarballs vs 1.1 GB extracted). path: | - ~/.cargo/registry - ~/.cargo/git - ~/.cache/cargo-xwin - src-tauri/target - key: ${{ runner.os }}-cargo-windows-${{ hashFiles('**/Cargo.lock') }} + ~/.cargo/registry/index + ~/.cargo/registry/cache + ~/.cargo/git/db + # One shared key across every job. The old per-job keys existed to stop + # debug/release target artifacts clobbering each other; with target no + # longer cached, registry contents are target-independent, so all jobs + # want the same crates. First job to finish saves; the rest restore. + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} restore-keys: | - ${{ runner.os }}-cargo-windows- + ${{ runner.os }}-cargo-registry- + + - name: Cache Windows CRT/SDK (cargo-xwin) + uses: actions/cache@v3 + with: + path: ~/.cache/cargo-xwin + # Contents track the xwin version baked into the builder image, not our + # lockfile -- keying this on Cargo.lock re-downloaded the whole SDK on + # every release bump. Bump the suffix by hand if the image's xwin moves. + key: ${{ runner.os }}-cargo-xwin-v1 - name: Cache Node dependencies uses: actions/cache@v3 @@ -216,7 +248,7 @@ jobs: with: name: jellytau-windows path: dist/windows/ - retention-days: 30 + retention-days: 7 build-android: name: Build Android @@ -235,17 +267,22 @@ jobs: - name: Cache Rust dependencies uses: actions/cache@v3 with: + # Registry only -- never src-tauri/target. That directory is ~16 GB and + # was cached under five separate keys, which filled the runner's 74 GB + # disk at ~1.15 GB/day (23 GB in 20 days, measured Aug 2026). + # registry/src is omitted too: cargo re-extracts it for free from + # registry/cache (155 MB of .crate tarballs vs 1.1 GB extracted). path: | - ~/.cargo/registry - ~/.cargo/git - src-tauri/target - # `-release` suffix keeps this distinct from build-and-test.yml's - # android-check key, whose `cargo check` artifacts would otherwise - # claim the key first and block this job's release cache from ever - # being saved (same collision as the Linux job above). - key: ${{ runner.os }}-cargo-android-release-${{ hashFiles('**/Cargo.lock') }} + ~/.cargo/registry/index + ~/.cargo/registry/cache + ~/.cargo/git/db + # One shared key across every job. The old per-job keys existed to stop + # debug/release target artifacts clobbering each other; with target no + # longer cached, registry contents are target-independent, so all jobs + # want the same crates. First job to finish saves; the rest restore. + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} restore-keys: | - ${{ runner.os }}-cargo-android-release- + ${{ runner.os }}-cargo-registry- - name: Cache Node dependencies uses: actions/cache@v3 @@ -309,7 +346,7 @@ jobs: with: name: jellytau-android path: dist/android/ - retention-days: 30 + retention-days: 7 create-release: name: Create Release