From 77f644f57649bf2bc3bf564fe7aff3defb67b874 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 5 Jun 2026 16:42:51 -0400 Subject: [PATCH] [ci] Share a cached native ESP-IDF install across clang-tidy and build jobs (#16841) --- .github/actions/cache-esp-idf/action.yml | 46 +++++++++++++++++++ .github/workflows/ci.yml | 56 ++++++++++++++++-------- 2 files changed, 83 insertions(+), 19 deletions(-) create mode 100644 .github/actions/cache-esp-idf/action.yml diff --git a/.github/actions/cache-esp-idf/action.yml b/.github/actions/cache-esp-idf/action.yml new file mode 100644 index 0000000000..7a17c222a3 --- /dev/null +++ b/.github/actions/cache-esp-idf/action.yml @@ -0,0 +1,46 @@ +name: Cache ESP-IDF +description: > + Resolve the pinned ESP-IDF version and cache the native ESP-IDF install + (toolchains + source) at ~/.esphome-idf. Every job that installs ESP-IDF + natively (clang-tidy for IDF/Arduino and the native-IDF component build) + shares one cache, since the install is identical (ESPHOME_IDF_DEFAULT_TARGETS + defaults to "all", so all toolchains are present regardless of the chip). + Callers must set env ESPHOME_ESP_IDF_PREFIX: ~/.esphome-idf and have the + Python venv already restored. +inputs: + framework: + description: 'Which pinned IDF version to key on: "espidf" (recommended) or "arduino".' + default: espidf +runs: + using: composite + steps: + - name: Resolve ESP-IDF version for cache key + # The native-IDF version is pinned in code, not in any file that feeds the + # other cache keys, so resolve it explicitly. Keying on it means the cache + # invalidates on a version bump (actions/cache never overwrites a key). + id: version + shell: bash + run: | + . venv/bin/activate + if [ "${{ inputs.framework }}" = "arduino" ]; then + version=$(python -c 'from esphome.components.esp32 import ARDUINO_FRAMEWORK_VERSION_LOOKUP as A, ARDUINO_IDF_VERSION_LOOKUP as L; print(L[A["recommended"]])') + else + version=$(python -c 'from esphome.components.esp32 import ESP_IDF_FRAMEWORK_VERSION_LOOKUP as L; print(L["recommended"])') + fi + echo "version=$version" >> "$GITHUB_OUTPUT" + # Mirror the adjacent PlatformIO cache: only dev-branch runs write the + # shared cache (so it lives in the default-branch scope readable by all + # PRs), and PRs are restore-only -- they never push multi-GB artifacts into + # their own scope / the repo quota (e.g. on a version-bump PR). + - name: Cache ESP-IDF install (write on dev) + if: github.ref == 'refs/heads/dev' + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: ~/.esphome-idf + key: ${{ runner.os }}-esphome-idf-${{ steps.version.outputs.version }} + - name: Cache ESP-IDF install (restore-only off dev) + if: github.ref != 'refs/heads/dev' + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: ~/.esphome-idf + key: ${{ runner.os }}-esphome-idf-${{ steps.version.outputs.version }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ae3f4e2b98..40267240d8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -472,6 +472,8 @@ jobs: if: needs.determine-jobs.outputs.clang-tidy == 'true' env: GH_TOKEN: ${{ github.token }} + # esp32-arduino-tidy installs ESP-IDF natively; share the native IDF cache. + ESPHOME_ESP_IDF_PREFIX: ~/.esphome-idf strategy: fail-fast: false max-parallel: 2 @@ -484,6 +486,7 @@ jobs: - id: clang-tidy name: Run script/clang-tidy for ESP32 Arduino options: --environment esp32-arduino-tidy --grep USE_ARDUINO + cache_idf: true - id: clang-tidy name: Run script/clang-tidy for ZEPHYR options: --environment nrf52-tidy --grep USE_ZEPHYR --grep USE_NRF52 @@ -517,6 +520,13 @@ jobs: path: ~/.platformio key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }} + - name: Cache ESP-IDF install + # Shared with the IDF tidy + native-IDF build jobs (same install). + if: matrix.cache_idf + uses: ./.github/actions/cache-esp-idf + with: + framework: arduino + - name: Register problem matchers run: | echo "::add-matcher::.github/workflows/matchers/gcc.json" @@ -568,6 +578,8 @@ jobs: if: needs.determine-jobs.outputs.clang-tidy-mode == 'nosplit' env: GH_TOKEN: ${{ github.token }} + # esp32-idf-tidy installs ESP-IDF natively; share the native IDF cache. + ESPHOME_ESP_IDF_PREFIX: ~/.esphome-idf steps: - name: Check out code from GitHub uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 @@ -581,6 +593,10 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} cache-key: ${{ needs.common.outputs.cache-key }} + - name: Cache ESP-IDF install + # Shared with the Arduino tidy + native-IDF build jobs (same install). + uses: ./.github/actions/cache-esp-idf + - name: Register problem matchers run: | echo "::add-matcher::.github/workflows/matchers/gcc.json" @@ -631,6 +647,8 @@ jobs: if: needs.determine-jobs.outputs.clang-tidy-mode == 'split' env: GH_TOKEN: ${{ github.token }} + # esp32-idf-tidy installs ESP-IDF natively; share the native IDF cache. + ESPHOME_ESP_IDF_PREFIX: ~/.esphome-idf strategy: fail-fast: false max-parallel: 3 @@ -659,6 +677,10 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} cache-key: ${{ needs.common.outputs.cache-key }} + - name: Cache ESP-IDF install + # Shared with the Arduino tidy + native-IDF build jobs (same install). + uses: ./.github/actions/cache-esp-idf + - name: Register problem matchers run: | echo "::add-matcher::.github/workflows/matchers/gcc.json" @@ -785,7 +807,7 @@ jobs: fi echo "" - # Show disk space before validation (after bind mounts setup) + # Show disk space before validation echo "Disk space before config validation:" df -h echo "" @@ -861,33 +883,20 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} cache-key: ${{ needs.common.outputs.cache-key }} - - name: Cache ESPHome - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 - with: - path: ~/.esphome-idf - key: ${{ runner.os }}-esphome-${{ needs.common.outputs.cache-key }} - - - name: Run native ESP-IDF compile test + - name: Prepare build storage on /mnt + # Bind-mount the larger /mnt disk over the IDF install + build dirs BEFORE + # restoring the cache, so the ~4.5GB restore lands on the roomier volume + # instead of being shadowed by a mount set up later in the run step. run: | - . venv/bin/activate - - # Check if /mnt has more free space than / before bind mounting - # Extract available space in KB for comparison root_avail=$(df -k / | awk 'NR==2 {print $4}') mnt_avail=$(df -k /mnt 2>/dev/null | awk 'NR==2 {print $4}') - echo "Available space: / has ${root_avail}KB, /mnt has ${mnt_avail}KB" - - # Only use /mnt if it has more space than / if [ -n "$mnt_avail" ] && [ "$mnt_avail" -gt "$root_avail" ]; then echo "Using /mnt for build files (more space available)" - # Bind mount PlatformIO directory to /mnt (tools, packages, build cache all go there) sudo mkdir -p /mnt/esphome-idf sudo chown $USER:$USER /mnt/esphome-idf mkdir -p ~/.esphome-idf sudo mount --bind /mnt/esphome-idf ~/.esphome-idf - - # Bind mount test build directory to /mnt sudo mkdir -p /mnt/test_build_components_build sudo chown $USER:$USER /mnt/test_build_components_build mkdir -p tests/test_build_components/build @@ -896,10 +905,19 @@ jobs: echo "Using / for build files (more space available than /mnt or /mnt unavailable)" fi + - name: Cache ESP-IDF install + # Shared with the IDF/Arduino clang-tidy jobs (same install); restores + # into the /mnt bind-mount prepared above when present. + uses: ./.github/actions/cache-esp-idf + + - name: Run native ESP-IDF compile test + run: | + . venv/bin/activate + echo "Testing components: $TEST_COMPONENTS" echo "" - # Show disk space before validation (after bind mounts setup) + # Show disk space before validation echo "Disk space before config validation:" df -h echo ""