|
| 1 | +# Cross-run CI cache strategy |
| 2 | + |
| 3 | +This is the consumer-facing checklist for carrying fbuild state across GitHub |
| 4 | +Actions runs. For the deeper design notes and matrix examples, see |
| 5 | +[CI_CACHING.md](CI_CACHING.md). For the composite action inputs and outputs, see |
| 6 | +[`.github/actions/setup/README.md`](../.github/actions/setup/README.md). |
| 7 | + |
| 8 | +## What to cache |
| 9 | + |
| 10 | +Cache these paths together with a key that identifies the runner, fbuild build, |
| 11 | +board, and build-graph inputs: |
| 12 | + |
| 13 | +| Path | Why it matters | |
| 14 | +|---|---| |
| 15 | +| `FBUILD_CACHE_DIR` | fbuild's package cache: downloaded archives, extracted toolchains/frameworks/libraries, and `index.sqlite`. The setup action pins this to `$RUNNER_TEMP/fbuild-cache`. | |
| 16 | +| `$ZCCACHE_DIR` | zccache's compiled-object store. This is required for cross-run per-translation-unit reuse. | |
| 17 | +| `.fbuild/build` | Project build outputs and fingerprints. This is what lets a second build become a near no-op when inputs have not changed. | |
| 18 | + |
| 19 | +Do not cache `~/.fbuild/*/daemon/`. It contains runtime-only PID, port, log, |
| 20 | +and status files for a daemon that will not exist in the next CI run. |
| 21 | + |
| 22 | +## Key composition |
| 23 | + |
| 24 | +For a single board on one pinned runner image, compose keys from: |
| 25 | + |
| 26 | +1. Cache schema version: a manual `v1`, `v2`, or date-stamped bump. |
| 27 | +2. Runner identity: the fixed image label plus `${{ runner.os }}-${{ runner.arch }}`. |
| 28 | +3. Installed fbuild content hash: use the setup action's `fbuild-hash` output. |
| 29 | +4. Board or environment name: for example `esp32dev`. |
| 30 | +5. Build-graph inputs: `platformio.ini`, board JSON, lock files, toolchain pins, or any file that changes libraries/frameworks/build flags. |
| 31 | + |
| 32 | +The fbuild content hash is important. Version strings alone do not protect |
| 33 | +against local installs, dev builds, or a wheel re-uploaded with the same version. |
| 34 | +The setup action computes the hash for you and uses it in its built-in |
| 35 | +`FBUILD_CACHE_DIR` cache key. |
| 36 | + |
| 37 | +## Invalidation pattern |
| 38 | + |
| 39 | +Keep normal invalidation automatic by hashing graph inputs. Keep forced |
| 40 | +invalidation explicit by carrying a manual cache version in workflow `env`: |
| 41 | + |
| 42 | +```yaml |
| 43 | +env: |
| 44 | + FBUILD_CACHE_VERSION: v1 |
| 45 | +``` |
| 46 | +
|
| 47 | +Bump that value when you change runner images, alter cache layout assumptions, |
| 48 | +or suspect a poisoned cache. If you use the setup action's built-in cache, pass |
| 49 | +the same value to `cache-version` so both fbuild's package cache and your |
| 50 | +consumer-managed zccache/build-output cache roll together. |
| 51 | + |
| 52 | +## Copy-paste GitHub Actions block |
| 53 | + |
| 54 | +This example is for one board on one pinned runner image. It lets the setup |
| 55 | +action cache fbuild's package/tool cache, then adds a second cache entry for |
| 56 | +zccache and project build outputs. |
| 57 | + |
| 58 | +```yaml |
| 59 | +jobs: |
| 60 | + build: |
| 61 | + runs-on: ubuntu-24.04 |
| 62 | + env: |
| 63 | + FBUILD_BOARD: esp32dev |
| 64 | + FBUILD_RUNNER_IMAGE: ubuntu-24.04 |
| 65 | + FBUILD_CACHE_VERSION: v1 |
| 66 | + steps: |
| 67 | + - uses: actions/checkout@v4 |
| 68 | +
|
| 69 | + - name: Setup fbuild |
| 70 | + id: fbuild |
| 71 | + uses: FastLED/fbuild/.github/actions/setup@main |
| 72 | + with: |
| 73 | + cache-version: ${{ env.FBUILD_CACHE_VERSION }} |
| 74 | + cache-key-extra: ${{ env.FBUILD_RUNNER_IMAGE }}-${{ env.FBUILD_BOARD }}-${{ hashFiles('platformio.ini', 'rust-toolchain.toml', '.fbuild-cache-version') }} |
| 75 | +
|
| 76 | + - name: Restore zccache store and fbuild build outputs |
| 77 | + uses: actions/cache@v4 |
| 78 | + with: |
| 79 | + path: | |
| 80 | + ${{ steps.fbuild.outputs.zccache-store-path }} |
| 81 | + .fbuild/build |
| 82 | + key: zccache-${{ env.FBUILD_CACHE_VERSION }}-${{ env.FBUILD_RUNNER_IMAGE }}-${{ runner.os }}-${{ runner.arch }}-${{ steps.fbuild.outputs.fbuild-hash }}-${{ env.FBUILD_BOARD }}-${{ hashFiles('platformio.ini', 'rust-toolchain.toml', '.fbuild-cache-version') }} |
| 83 | + restore-keys: | |
| 84 | + zccache-${{ env.FBUILD_CACHE_VERSION }}-${{ env.FBUILD_RUNNER_IMAGE }}-${{ runner.os }}-${{ runner.arch }}-${{ steps.fbuild.outputs.fbuild-hash }}-${{ env.FBUILD_BOARD }}- |
| 85 | + zccache-${{ env.FBUILD_CACHE_VERSION }}-${{ env.FBUILD_RUNNER_IMAGE }}-${{ runner.os }}-${{ runner.arch }}-${{ steps.fbuild.outputs.fbuild-hash }}- |
| 86 | + zccache-${{ env.FBUILD_CACHE_VERSION }}-${{ env.FBUILD_RUNNER_IMAGE }}-${{ runner.os }}-${{ runner.arch }}- |
| 87 | +
|
| 88 | + - name: Build |
| 89 | + run: fbuild build examples/Blink -e "$FBUILD_BOARD" |
| 90 | +``` |
| 91 | + |
| 92 | +Adjust the `hashFiles(...)` list to match your repository. If your board list, |
| 93 | +library pins, or build flags live somewhere else, include those files in the |
| 94 | +hash. |
| 95 | + |
| 96 | +## Expected numbers |
| 97 | + |
| 98 | +Use these as order-of-magnitude expectations, not hard pass/fail thresholds. |
| 99 | +[CI_CACHING.md](CI_CACHING.md#expected-timings) has the current CI-oriented |
| 100 | +summary; [PERF_WARM_BUILD.md](PERF_WARM_BUILD.md) records the local profiling |
| 101 | +context behind the warm-path work. |
| 102 | + |
| 103 | +| Scenario | Observed shape | |
| 104 | +|---|---| |
| 105 | +| First cache-miss ESP32 build | Can be dominated by downloads and full framework compile; the local benchmark saw about 14 minutes on Windows for `tests/platform/esp32dev`. | |
| 106 | +| Warm small AVR build | About 250 ms wall-clock in the local benchmark once disk cache and build outputs were populated. | |
| 107 | +| Steady-state warm ESP32 build | About 280 ms wall-clock for runs 3+ with the same daemon. | |
| 108 | +| Current CI warm no-op target | Near-warm builds should be single-digit seconds including CLI and daemon overhead; the internal hot path can be much lower. | |
| 109 | + |
| 110 | +CI cache restore should remove package download and extraction time. It does not |
| 111 | +guarantee every restored build is as fast as a hot-daemon local no-op, especially |
| 112 | +for ESP32 paths that still need warm-path validation. A restored build that still |
| 113 | +downloads toolchains or frameworks usually means the package cache key/path is |
| 114 | +wrong; a restored build that skips downloads but spends time validating project |
| 115 | +outputs points at warm-path behavior instead. |
| 116 | + |
| 117 | +## Common pitfalls |
| 118 | + |
| 119 | +- Caching `~/.fbuild/prod/` wholesale restores daemon state. Cache |
| 120 | + `FBUILD_CACHE_DIR` instead. |
| 121 | +- Sharing one key across OSes, architectures, or runner images can restore |
| 122 | + incompatible toolchains or path-embedded response files. |
| 123 | +- Keying only on `platformio.ini` can miss board JSON, lock file, or toolchain |
| 124 | + changes that should invalidate stale outputs. |
| 125 | +- Omitting `fbuild-hash` can keep stale artifacts after an fbuild upgrade or dev |
| 126 | + build change. |
| 127 | +- Restoring zccache without `.fbuild/build` still helps compile reuse, but it |
| 128 | + will not preserve fbuild's project-level warm-build fast path. |
| 129 | +- Letting cache entries grow past GitHub Actions limits can make saves fail. If |
| 130 | + needed, cap fbuild's package cache with `FBUILD_CACHE_BUDGET=8G`. |
| 131 | + |
| 132 | +## See also |
| 133 | + |
| 134 | +- [CI_CACHING.md](CI_CACHING.md) - detailed cache design, raw snippets, and |
| 135 | + matrix guidance. |
| 136 | +- [`.github/actions/setup/README.md`](../.github/actions/setup/README.md) - |
| 137 | + setup action contract and examples. |
| 138 | +- [PERF_WARM_BUILD.md](PERF_WARM_BUILD.md) - benchmark context behind the |
| 139 | + expected warm-build numbers. |
0 commit comments