diff --git a/.github/workflows/ci-preflight.yml b/.github/workflows/ci-preflight.yml index 49dfe4d4..b7bbf838 100644 --- a/.github/workflows/ci-preflight.yml +++ b/.github/workflows/ci-preflight.yml @@ -50,6 +50,14 @@ on: description: Run the repository Dylint gate. Enabled only for the native Linux x86 lane. type: boolean default: false + cache-targets: + description: Cache Cargo target artifacts. False retains only the Cargo registry. + type: boolean + default: true + cache-key-suffix: + description: Stable cache family name for this lane. + type: string + default: shared permissions: contents: read @@ -102,6 +110,7 @@ jobs: uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.ref == 'refs/heads/main' }} + cache-targets: ${{ inputs.cache-targets }} # Keep cache path discovery scoped to this checkout's workspace # target root. rust-cache's automatic discovery can otherwise # register a nested `target/tests/target` path and fail its post-job @@ -109,7 +118,7 @@ jobs: workspaces: ". -> target" # Key kept from the fan-out era so existing warm caches carry # over. OS + Cargo.lock hash are included via Swatinem defaults. - key: ${{ inputs.label }}-shared + key: ${{ inputs.label }}-${{ inputs.cache-key-suffix }} cache-on-failure: true - name: Sync venv (frozen, no project install) diff --git a/.github/workflows/ci-windows.yml b/.github/workflows/ci-windows.yml index 0dd1517d..f7dc3c98 100644 --- a/.github/workflows/ci-windows.yml +++ b/.github/workflows/ci-windows.yml @@ -37,3 +37,5 @@ jobs: label: windows-arm rustdoc: false integration-test: true + cache-targets: false + cache-key-suffix: registry diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 42884dee..d6baa0ca 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -70,7 +70,10 @@ jobs: uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.ref == 'refs/heads/main' }} - key: ubuntu-24.04-coverage + # Coverage target artifacts dominate its cache and are rebuilt on + # every source/lock change; retain registry downloads under a new key. + cache-targets: false + key: ubuntu-24.04-coverage-registry cache-on-failure: true - name: Sync diff --git a/ci/cache_budget.py b/ci/cache_budget.py index b5a10796..8cde3226 100644 --- a/ci/cache_budget.py +++ b/ci/cache_budget.py @@ -74,10 +74,15 @@ "v0-rust-release-binaries-x86_64-pc-windows-msvc-build-binaries-", "v0-rust-release-binaries-aarch64-pc-windows-msvc-build-binaries-", ) +SUPERSEDED_TARGET_CACHE_PREFIXES = ( + "v0-rust-windows-arm-shared-preflight-", + "v0-rust-ubuntu-24.04-coverage-coverage-", +) RETIRED_CACHE_PREFIXES = ( *ALL_FEATURES_CACHE_PREFIXES, *RELEASE_BUILD_CACHE_PREFIXES, *RELEASE_BINARY_CACHE_PREFIXES, + *SUPERSEDED_TARGET_CACHE_PREFIXES, ) diff --git a/docs/cache-budget.md b/docs/cache-budget.md index e9ef9158..c4e953ac 100644 --- a/docs/cache-budget.md +++ b/docs/cache-budget.md @@ -14,5 +14,21 @@ caches are also restore-only and retired from the main ref. The measured 12-entry family occupied 4,078,226,358 bytes. Release packaging, target builds, and validation still run unchanged; the tradeoff is that a future release can start those Rust target builds cold instead of restoring the old multi-gigabyte -archives. Shared development/preflight, coverage, musl, security, and Dylint -caches remain eligible for main saves. +archives. + +Windows ARM preflight and coverage retain Cargo registry downloads but no +workspace target artifacts. Their prior main-ref target archives occupied +2,105,526,302 and 944,115,359 bytes, respectively, in the observed full +no-eviction cycle. The replacement registry-only keys use new namespaces, so +the budget workflow retires only the superseded target-bearing keys and keeps +the smaller replacements. Test coverage remains unchanged; Windows ARM and +coverage builds may take longer when their target artifacts are cold. The +measured no-eviction steady-state projection after these retirements is about +8.44 billion bytes, leaving roughly 1.06 billion bytes below the 9.5-billion +guard. Future Cargo.lock key churn can temporarily add replacement entries; +the main writer barrier and post-writer cleanup keep obsolete disabled-family +entries out of the settled budget, but that transient headroom should be +rechecked if future full cycles grow materially. + +Other development/preflight, musl, security, and Dylint caches remain eligible +for main saves. diff --git a/tests/test_cache_budget.py b/tests/test_cache_budget.py index 5fe7124a..39812dbe 100644 --- a/tests/test_cache_budget.py +++ b/tests/test_cache_budget.py @@ -107,6 +107,22 @@ def test_selects_only_exact_disabled_families_on_main(self) -> None: "v0-rust-release-binaries-x86_64-pc-windows-msvc-build-binaries-Linux-x64-hash-version", ref="refs/tags/v4.10.15", ), + entry( + 21, + "v0-rust-windows-arm-shared-preflight-Windows_NT-arm64-hash-version", + ), + entry( + 22, + "v0-rust-ubuntu-24.04-coverage-coverage-Linux-x64-hash-version", + ), + entry( + 23, + "v0-rust-windows-arm-registry-preflight-Windows_NT-arm64-hash-version", + ), + entry( + 24, + "v0-rust-ubuntu-24.04-coverage-registry-coverage-Linux-x64-hash-version", + ), ] self.assertEqual( @@ -115,7 +131,48 @@ def test_selects_only_exact_disabled_families_on_main(self) -> None: for cache in candidates if cache_budget.is_retired_cache(cache) ], - list(range(1, 4)) + list(range(7, 19)), + list(range(1, 4)) + list(range(7, 19)) + [21, 22], + ) + + def test_high_volume_lanes_keep_registry_only_under_new_namespaces(self) -> None: + root = Path(__file__).resolve().parents[1] + windows = (root / ".github/workflows/ci-windows.yml").read_text( + encoding="utf-8" + ) + arm_job = re.search(r"(?ms)^ arm:.*?(?=^ \w+:|\Z)", windows) + self.assertIsNotNone(arm_job) + self.assertIn("cache-targets: false", arm_job.group()) + self.assertIn("cache-key-suffix: registry", arm_job.group()) + + preflight = (root / ".github/workflows/ci-preflight.yml").read_text( + encoding="utf-8" + ) + self.assertIn("cache-targets: ${{ inputs.cache-targets }}", preflight) + self.assertIn( + "key: ${{ inputs.label }}-${{ inputs.cache-key-suffix }}", preflight + ) + + coverage = (root / ".github/workflows/coverage.yml").read_text(encoding="utf-8") + coverage_cache = next( + block + for block in coverage.split("\n - ") + if "key: ubuntu-24.04-coverage-registry" in block + ) + self.assertIn("cache-targets: false", coverage_cache) + self.assertIn("save-if: ${{ github.ref == 'refs/heads/main' }}", coverage_cache) + self.assertTrue( + { + "v0-rust-windows-arm-shared-preflight-", + "v0-rust-ubuntu-24.04-coverage-coverage-", + } + <= set(cache_budget.SUPERSEDED_TARGET_CACHE_PREFIXES) + ) + self.assertFalse( + any( + prefix.endswith("registry-preflight-") + or prefix.endswith("registry-coverage-") + for prefix in cache_budget.RETIRED_CACHE_PREFIXES + ) ) def test_release_target_caches_are_restore_only(self) -> None: @@ -252,6 +309,26 @@ def test_enforcer_retires_only_the_disabled_main_families(self) -> None: "v0-rust-release-binaries-aarch64-pc-windows-msvc-build-binaries-Linux-x64-hash-version", size=500, ), + entry( + 7, + "v0-rust-windows-arm-shared-preflight-Windows_NT-arm64-hash-version", + size=2100, + ), + entry( + 8, + "v0-rust-ubuntu-24.04-coverage-coverage-Linux-x64-hash-version", + size=900, + ), + entry( + 9, + "v0-rust-windows-arm-registry-preflight-Windows_NT-arm64-hash-version", + size=50, + ), + entry( + 10, + "v0-rust-ubuntu-24.04-coverage-registry-coverage-Linux-x64-hash-version", + size=50, + ), ] class FakeAPI: @@ -276,9 +353,9 @@ def delete_cache(self, cache_id: int) -> None: api, sleep=lambda _seconds: None, attempts=2 ) - self.assertEqual(api.deleted, [1, 2, 3, 5, 6]) - self.assertEqual([int(cache["id"]) for cache in api.caches], [4]) - self.assertEqual(budget, 200) + self.assertEqual(api.deleted, [1, 2, 3, 5, 6, 7, 8]) + self.assertEqual([int(cache["id"]) for cache in api.caches], [4, 9, 10]) + self.assertEqual(budget, 300) def test_repeated_retirement_catches_a_cache_recreated_by_an_active_writer( self,