Skip to content

Latest commit

 

History

History
180 lines (141 loc) · 10.4 KB

File metadata and controls

180 lines (141 loc) · 10.4 KB

FastLED/fbuild/setup - composite GitHub Action

One-line fbuild setup for consumer CI pipelines. Installs fbuild, wires actions/cache@v5 with sensible defaults, and exports FBUILD_CACHE_DIR plus ZCCACHE_DIR so subsequent steps Just Work.

For the underlying cache design (what's cached, why, and how to tune the key for your project) see ../../docs/CI_CACHING.md.

Minimal usage

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: FastLED/fbuild/.github/actions/setup@main
        with:
          cache-key-extra: ${{ hashFiles('platformio.ini') }}
      - run: fbuild build examples/Blink -e esp32dev

Full matrix example

jobs:
  build:
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        board: [uno, esp32dev, esp32s3, teensy41]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4

      - uses: FastLED/fbuild/.github/actions/setup@main
        id: fbuild
        with:
          cache-key-extra: ${{ matrix.board }}-${{ hashFiles('platformio.ini') }}

      - run: fbuild build examples/Blink -e ${{ matrix.board }}

      - name: Second build should be a near-no-op (smoke test cache warmth)
        shell: bash
        run: |
          start=$SECONDS
          fbuild build examples/Blink -e ${{ matrix.board }}
          elapsed=$((SECONDS - start))
          echo "second-build elapsed: ${elapsed}s"
          test "$elapsed" -lt 15 || {
            echo "::error::cache restore did not produce a warm build (took ${elapsed}s)"
            exit 1
          }

Split caches (cache-mode: split)

For large board matrices, split mode keeps packages shared per platform family and build payloads per board (#1433). It needs an fbuild release with fbuild install.

- uses: FastLED/fbuild/.github/actions/setup@main
  id: fbuild
  with:
    cache-mode: split
    environments: ${{ matrix.board }}
    cache-key-extra: ${{ hashFiles('platformio.ini') }}
    save: ${{ github.event_name != 'pull_request' }}
- run: fbuild build examples/Blink -e ${{ matrix.board }}

In split mode the action:

  1. Runs fbuild install --dry-run --json to learn each environment's platform, without touching the network.
  2. Restores the packages cache (toolchains, platforms, packages, libraries, archives, installed, index.sqlite) by the prefix fbuild-pkgs-<cache-version>-<os>-<arch>-<family>-.
  3. Runs fbuild install as its own step, then saves the packages cache under that prefix plus the packages_hash. It skips the save when the restored key already matches.
  4. Restores the build-payload cache (core, framework-libs, library-selection, the zccache store) keyed by fbuild hash, board and cache-key-extra, without falling back to other boards. It is saved at the end of the job unless save is false.

Caching the zccache store

The built-in cache: true wiring covers the fbuild package/tool cache rooted at FBUILD_CACHE_DIR. If you also want cross-run reuse of zccache's object store, add your own actions/cache@v5 step for the resolved zccache directory and your project build outputs:

- name: Setup fbuild
  id: fbuild-setup
  uses: FastLED/fbuild/.github/actions/setup@main
  with:
    cache-key-extra: ${{ matrix.board }}-${{ hashFiles('platformio.ini') }}

- name: Restore zccache store + project build outputs
  uses: actions/cache@v5
  with:
    path: |
      ${{ steps.fbuild-setup.outputs.zccache-store-path }}
      .fbuild/build
    key: zccache-v1-${{ runner.os }}-${{ runner.arch }}-${{ steps.fbuild-setup.outputs.fbuild-hash }}-${{ matrix.board }}-${{ hashFiles('platformio.ini', 'rust-toolchain.toml') }}
    restore-keys: |
      zccache-v1-${{ runner.os }}-${{ runner.arch }}-${{ steps.fbuild-setup.outputs.fbuild-hash }}-${{ matrix.board }}-
      zccache-v1-${{ runner.os }}-${{ runner.arch }}-${{ steps.fbuild-setup.outputs.fbuild-hash }}-
      zccache-v1-${{ runner.os }}-${{ runner.arch }}-

- run: fbuild build examples/Blink -e ${{ matrix.board }}

Use steps.<id>.outputs.zccache-store-path inside workflow expressions. The same path is also exported as ZCCACHE_DIR via $GITHUB_ENV, which is convenient for later shell steps:

- name: Print resolved cache paths
  shell: bash
  run: |
    echo "fbuild cache: $FBUILD_CACHE_DIR"
    echo "zccache store: $ZCCACHE_DIR"

Inputs

Input Default Description
fbuild-version latest PyPI version spec. Pin to an exact version (2.1.16) for reproducible CI.
python-version 3.10 Python used to install fbuild. Must be >= 3.10.
cache true Set to false to install fbuild without wiring actions/cache.
cache-mode combined combined keeps one FBUILD_CACHE_DIR entry per key. split restores a packages cache shared per platform family and a per-board build-payload cache, and runs fbuild install as its own step.
save true false restores caches without saving; use it on pull requests.
project-dir . Split mode: project passed to fbuild install.
environments "" Split mode: space-separated environments to provision. Required in split mode.
board "" Split mode: name in the build-payload cache key. Defaults to environments joined with _.
cache-key-extra "" String baked into the cache key. Use hashFiles(...) over your graph inputs so edits invalidate stale artifacts.
cache-version v1 Manual cache bump. Increment when you want to force-invalidate across your matrix.
cache-dir $RUNNER_TEMP/fbuild-cache Override if you need a different cache root.
install-cache-key-extra "" Extra string for the install cache (separate from the build artifact cache). Pass hashFiles('pyproject.toml') so a fbuild pin bump invalidates the cached binary. The install cache is intentionally board/platform-independent so every job in a matrix shares it.

Outputs

Output Description
cache-hit Combined mode: true if the cache was restored from an exact key match, false otherwise.
platform-family Split mode: the platform family the packages cache is shared across.
packages-hash Split mode: packages_hash from fbuild install --json.
packages-cache-hit Split mode: the restored packages cache key, empty on a miss.
build-cache-hit Split mode: true if the build-payload cache was restored from an exact key match.
cache-dir Resolved cache directory path. Useful for diagnostic steps.
fbuild-hash sha256 prefix (16 hex chars) of the installed fbuild wheel's RECORD file. Baked into the cache key so any fbuild change, including a re-released wheel at the same version, invalidates stale cache artifacts.
zccache-store-path Resolved zccache object-store directory. The same path is exported to later steps as ZCCACHE_DIR, so consumer-managed actions/cache@v5 blocks can reuse it without guessing platform-specific defaults.

What this action does

  1. Sets up Python.
  2. Resolves a stable fbuild cache directory under $RUNNER_TEMP (survives across steps of the same job; not a surprise $HOME path) and the zccache store path the job will use for compiler-object caching.
  3. Exports FBUILD_CACHE_DIR and ZCCACHE_DIR via $GITHUB_ENV so every later step inherits them, and exposes the zccache location as the zccache-store-path output.
  4. Restores the install cache (binary + dist-info) into $RUNNER_TEMP/fbuild-install keyed by os/arch/python/pip-spec/install-cache-key-extra. On hit, the install step is skipped entirely. The key intentionally omits anything matrix-specific (board, OS-tag, etc.) so every job in a repo's matrix shares the cached binary.
  5. Installs fbuild from PyPI at the requested version (skipped on install-cache hit). Install uses pip install --target=$RUNNER_TEMP/fbuild-install so the cached directory is the entire install surface.
  6. Activates the install dir by appending bin/ (POSIX) and Scripts/ (Windows) to $GITHUB_PATH and prepending PYTHONPATH.
  7. Computes the installed fbuild's content hash (sha256 of its dist-info RECORD) and bakes it into the build artifact cache key. This guarantees the artifact cache is tied to the exact fbuild you're running, not just the PyPI version string, so latest is safe and a re-released wheel won't poison the cache.
  8. Restores (and on job-end, saves) the fbuild build artifact cache via actions/cache@v5. With save: false it only restores. In cache-mode: split this step is replaced by the packages and build-payload caches described in Split caches.

Why hash-pinning matters

The fbuild cache stores toolchains, frameworks, and build outputs whose internal layout is tied to fbuild's own fingerprint format, response-file generation, and path embedding. If fbuild itself changes without the cache key changing, the restored cache can encode stale paths or obsolete fingerprints: silent cache poisoning.

Baking the wheel's RECORD hash into the key means:

  • fbuild-version: latest is reproducible-by-construction: a new release produces a new hash, which rolls the cache.
  • A re-uploaded wheel (same version, different contents) invalidates correctly.
  • Per-platform wheel differences (manylinux vs. macOS vs. Windows) produce different hashes and do not cross-pollute caches.

It does not cache ~/.fbuild/*/daemon/ - that's ephemeral runtime state and restoring it across runs causes broken daemon discovery on the next client call. The action sidesteps the whole ~/.fbuild/ tree by redirecting fbuild's cache to $RUNNER_TEMP/fbuild-cache via FBUILD_CACHE_DIR.

It also does not automatically cache zccache's object store for you. That store lives at ZCCACHE_DIR / zccache-store-path, and consumers who want cross-run per-TU reuse should add their own actions/cache@v5 step as shown above.

Version pinning

For reproducible CI, reference a release tag rather than @main:

- uses: FastLED/fbuild/.github/actions/setup@v1

At time of writing there is no v1 tag - use @main and pin fbuild-version to an explicit PyPI version if reproducibility matters.

Related

  • docs/CI_CACHING.md - detailed design of the underlying cache, plus raw actions/cache@v5 snippets for consumers who prefer to avoid the action dependency.
  • #101 - the issue that tracked creation of this action.