8000
Skip to content

[espidf] Install only the toolchains for the variants being built - #17688

Merged
bdraco merged 9 commits into
devfrom
espidf-per-variant-targets
Jul 27, 2026
Merged

[espidf] Install only the toolchains for the variants being built#17688
bdraco merged 9 commits into
devfrom
espidf-per-variant-targets

Conversation

@bdraco
@bdraco bdraco commented Jul 19, 2026
Copy link
Copy Markdown
Member

What does this implement/fix?

Chained on #17687; #17686 below it has merged. ESPHome passes --targets=all to idf_tools.py install, so every user downloads both compiler families even though a config builds exactly one variant; riscv32-esp-elf is a 292 MB download and 2.0 GB on disk, xtensa-esp-elf is 177 MB and 1.1 GB. This passes the configured variant instead, so a user who only builds xtensa chips never downloads the riscv toolchain and vice versa, roughly halving the fresh install.

Not intended for backport. Like #17687 this is purely an install size and speed optimization; #17686, already merged, is the one fixing a real reported issue.

How it works: the variant from CORE.data becomes the --targets= argument (via a shared variant_to_idf_target helper, also adopted by the extra script runner which had its own copy of the mapping). idf_tools.py accumulates targets in idf-env.json across runs, so building a second variant later installs just its toolchain incrementally while everything already present reports "already installed". The install stamp now records the union of installed targets and treats a superset as valid, so alternating builds of two variants never re-run the installer, and existing stamps holding all stay valid forever, meaning current installs see zero churn on upgrade. The ESP-IDF python env rebuild is also decoupled from tool installs; it depends on the framework version and features, not on which toolchains are present, so adding a variant no longer wipes the venv. An explicitly set ESPHOME_IDF_DEFAULT_TARGETS still overrides everything, so a builder image can pre-warm all targets. CI keeps installing every target regardless of the variant a job builds, so the shared toolchain cache stays complete. As a separate saving, tools.json is patched to drop the xtensa chips from riscv32-esp-elf's supported targets; that entry exists only for the S2/S3 ULP coprocessor, which ESPHome never builds for, so xtensa only installs skip the riscv compiler entirely.

Verified end to end with the bundled idf_tools.py from IDF 5.5.5 in a fresh tools path: install --targets=esp32 required downloads only xtensa-esp-elf, esp-rom-elfs, the tool path export succeeds against that install, and a follow up install --targets=esp32c3 required accumulates both targets in idf-env.json, skips the already installed tools, and pulls only the riscv toolchain.

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected) — policy
  • Developer breaking change (an API change that could break external components) — policy
  • Undocumented C++ API cha 7440 nge (removal or change of undocumented public methods that lambda users may depend on) — policy
  • Code quality improvements to existing code or addition of tests
  • Other

Related issue or feature (if applicable):

  • fixes

Pull request in esphome.io with documentation (if applicable):

  • esphome/esphome.io#<esphome.io PR number goes here>

Test Environment

  • ESP32
  • ESP32 IDF
  • ESP8266
  • RP2040/RP2350
  • BK72xx
  • RTL87xx
  • LN882x
  • nRF52840

Example entry for config.yaml:

# Tooling change only, no configuration impact

Checklist:

  • The code change is tested and works locally.
  • Tests have been added to verify that the new code works (under tests/ folder).

If user exposed functionality or configuration variables are added/changed:

@bdraco
bdraco commented Jul 19, 2026
Copy link
Copy Markdown
Member Author

@esphbot review

@esphome
esphome Bot commented Jul 19, 2026
Copy link
Copy Markdown
Contributor

To use the changes in this PR:

# Clone the repository:
git clone https://github.com/esphome/esphome
cd esphome

# Checkout the PR branch:
git fetch origin pull/17688/head:espidf-per-variant-targets
git checkout espidf-per-variant-targets

# Install the development version:
script/setup

# Activate the development version:
source venv/bin/activate

Now you can run esphome as usual to test the changes in this PR.


(Added by the PR bot)

@esphome esphome Bot added chained-pr This PR is chained to another PR which must be merged before this one code-quality component: esp32 has-tests platform: esp32 labels Jul 19, 2026
@codecov
codecov Bot commented Jul 19, 2026
Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.53%. Comparing base (9dd7dcb) to head (3499f05).
⚠️ Report is 3 commits behind head on dev.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##              dev   #17688      +/-   ##
==========================================
+ Coverage   86.49%   86.53%   +0.04%     
==========================================
  Files          60       60              
  Lines       14137    14182      +45     
  Branches     2132     2141       +9     
==========================================
+ Hits        12228    12273      +45     
  Misses       1596     1596              
  Partials      313      313              
Files with missing lines Coverage Δ
esphome/espidf/component.py 92.50% <100.00%> (+0.06%) ⬆️
esphome/espidf/framework.py 100.00% <100.00%> (ø)
esphome/espidf/toolchain.py 45.39% <100.00%> (+0.94%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
github-actions Bot commented Jul 19, 2026
Copy link
Copy Markdown
Contributor

Memory Impact Analysis

Components: esp32
Platform: esp32-idf

Metric Target Branch This PR Change
RAM 44,636 bytes 44,636 bytes ➡️ +0 bytes (0.00%)
Flash 669,615 bytes 669,615 bytes ➡️ +0 bytes (0.00%)

Note: This analysis measures static RAM and Flash usage only (compile-time allocation).
Dynamic memory (heap) cannot be measured automatically.
⚠️ You must test this PR on a real device to measure free heap and ensure no runtime memory issues.

This analysis runs automatically when components change. Memory usage is measured from a representative test configuration.

@esphbot
esphbot commented Jul 19, 2026
Copy link
Copy Markdown
Contributor

Previous review — superseded by a newer review below.

@esphbot esphbot left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

No blocking issues found — ready to merge.

@bdraco
bdraco commented Jul 19, 2026
Copy link
Copy Markdown
Member Author

@esphbot review

@esphbot
esphbot commented Jul 19, 2026
Copy link
Copy Markdown
Contributor

Previous review — superseded by a newer review below.

@esphbot esphbot left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

No blocking issues found — ready to merge.

@bdraco
bdraco commented Jul 19, 2026
Copy link
Copy Markdown
Member Author

@esphbot review

@esphbot
esphbot commented Jul 19, 2026
Copy link
Copy Markdown
Contributor

Previous review — superseded by a newer review below.

@esphbot esphbot left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

No blocking issues found — ready to merge.

@bdraco
bdraco force-pushed the espidf-demote-unused-tools branch from aa165a0 to 33fb12f Compare July 20, 2026 05:40
@bdraco
bdraco force-pushed the espidf-per-variant-targets branch from 3e8d1af to 74479b8 Compare July 20, 2026 05:40
@bdraco
bdraco commented Jul 20, 2026
Copy link
Copy Markdown
Member Author

@esphbot review

@esphbot
esphbot commented Jul 20, 2026
Copy link
Copy Markdown
Contributor

Previous review — superseded by a newer review below.

@esphbot esphbot left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

No blocking issues found — ready to merge.

@bdraco
bdraco commented Jul 20, 2026
Copy link
Copy Markdown
Member Author

e694f52

@bdraco
bdraco commented Jul 20, 2026
Copy link
Copy Markdown
Member Author

@esphbot review

@esphbot
esphbot commented Jul 20, 2026
Copy link
Copy Markdown
Contributor

Previous review — superseded by a newer review below.

@esphbot esphbot left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

No blocking issues found — ready to merge.

Base automatically changed from espidf-demote-unused-tools to dev July 26, 2026 06:07
@esphome esphome Bot removed the chained-pr This PR is chained to another PR which must be merged before this one label Jul 26, 2026
@esphome
esphome Bot commented Jul 26, 2026
Copy link
Copy Markdown
Contributor

👋 Hi there! This PR modifies 1 file(s) with codeowners.

@esphome/core - As codeowner(s) of the affected files, your review would be appreciated! 🙏

Note: Automatic review request may have failed, but you're still welcome to review.

Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Optimizes ESP-IDF tool installation in ESPHome by restricting idf_tools.py install targets to the currently configured ESP32 variant (unless explicitly overridden), reducing unnecessary toolchain downloads and avoiding unnecessary Python venv rebuilds while keeping existing “all targets” installs valid.

Changes:

  • Pass per-variant ESP-IDF targets (derived from CORE.data variant) into check_esp_idf_install, with an explicit ESPHOME_IDF_DEFAULT_TARGETS override still forcing all.
  • Update framework install stamping logic to treat target supersets as valid, merge installed targets over time, and decouple Python venv rebuilds from toolchain installs.
  • Add/extend unit tests covering configured targets, stamp coverage/merge behavior, and tools.json patching for demoting unused tools and pruning xtensa targets from riscv32-esp-elf.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/unit_tests/test_espidf_toolchain.py Adds unit tests for deriving IDF targets from the configured ESP32 variant and updated check_esp_idf_install call signature.
tests/unit_tests/test_espidf_framework.py Adds test coverage for stamp superset semantics, target merge behavior, env override precedence, and tools.json patch behavior.
esphome/espidf/toolchain.py Uses configured variant to compute targets passed to check_esp_idf_install, avoiding installing unused toolchains.
esphome/espidf/framework.py Implements stamp reading and “covers” logic, merges installed targets, prevents venv rebuilds on tools-only installs, and refines target selection precedence.
esphome/espidf/component.py Reuses shared variant_to_idf_target helper for extra script target mapping.
esphome/components/esp32/const.py Adds shared variant_to_idf_target() mapping helper.
Comments suppressed due to low confidence (1)

esphome/espidf/framework.py:257

  • _write_stamp() writes stamp dicts that include lists (e.g. targets, tools, features), so data: dict[str, str] is no longer accurate. Widen the annotation to avoid incorrect typing and future confusion.
def _write_stamp(file: PathType, data: dict[str, str]):

Comment thread esphome/espidf/framework.py Outdated
Comment thread esphome/espidf/framework.py
@swoboda1337
Copy link
Copy Markdown
Member

Will cause issues with caching in CI. Right now the IDF cache is written from any clang tidy run. After this change it will only cache a single variant and won't warm all variants. This will result in only half the cache getting saved and then longer CI run times.

@bdraco
bdraco marked this pull request as draft July 26, 2026 18:02
@bdraco
bdraco commented Jul 26, 2026
Copy link
Copy Markdown
Member Author

@esphbot review

@esphbot
esphbot commented Jul 26, 2026
Copy link
Copy Markdown
Contributor

@swoboda1337: Will cause issues with caching in CI. Right now the IDF cache is written from any clang tidy run. Af...

Already handled. _get_configured_targets() returns None whenever CI set — see esphome/espidf/toolchain.py. CI then falls through to ESPHOME_IDF_DEFAULT_TARGETS, still all. Every runner installs every toolchain. Shared cache stays complete. Covered by test_get_configured_targets_ci_installs_all.

One gap worth closing: .github/actions/cache-esp-idf/action.yml justifies its single shared cache only by that all default. Now depends on this guard too. Add a pointer there.

@esphbot
esphbot commented Jul 26, 2026
Copy link
Copy Markdown
Contributor

Previous review — superseded by a newer review below.

@esphbot esphbot left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

No blocking issues found — ready to merge.

@bdraco
bdraco commented Jul 26, 2026
Copy link
Copy Markdown
Member Author

1c018a4

@bdraco
bdraco commented Jul 26, 2026
Copy link
Copy Markdown
Member Author

@esphbot review

@bdraco
bdraco commented Jul 26, 2026
Copy link
Copy Markdown
Member Author

@esphbot review

@bdraco
bdraco commented Jul 26, 2026
Copy link
Copy Markdown
Member Author

@esphbot review

@esphbot
esphbot commented Jul 26, 2026
Copy link
Copy Markdown
Contributor

Previous review — superseded by a newer review below.

@esphbot esphbot left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

No blocking issues found — ready to merge.

@bdraco
bdraco commented Jul 26, 2026
Copy link
Copy Markdown
Member Author
C859

3499f05

@bdraco
bdraco commented Jul 26, 2026
Copy link
Copy Markdown
Member Author

@esphbot review

@esphbot
esphbot commented Jul 26, 2026
Copy link
Copy Markdown
Contributor

PR Review — [espidf] Install only the toolchains for the variants being built

Merge-ready. Every finding from the previous round is fixed, and the one remaining note is a pre-existing consistency nit outside the diff.

What I verified this round, specifically:

  • The suggestion from last round is closed: test_framework_install_all_request_collapses_merged_stamp_to_all now pins the ["all"] if "all" in merged branch, so a future edit that wrote sorted(merged) unconditionally would fail CI instead of silently producing ["all", "esp32"].

  • Both silent-failure findings are addressed at the right level, not papered over: _read_stamp now warns on corrupt JSON and on OSError (a persistent I/O fault re-runs a multi-hundred-MB install every build, so a debug line was too quiet), and the non-dict case became an explicit branch naming the offending type instead of a bare isinstance ternary. Three new tests cover the branches, and test_read_stamp_missing_file_is_silent guards the other direction — the normal first-install path must stay quiet.

  • Test isolation holds for the new stamp-file tests that write real files: the module's autouse _isolate_idf_install_path pins ESPHOME_ESP_IDF_PREFIX to a tmp dir, and conftest.py's autouse reset_core clears the CORE.data[KEY_ESP32] the new _get_configured_targets tests plant, so nothing leaks into the source-override tests that assert targets=None.

  • _get_configured_targets reads the variant with .get() rather than get_esp32_variant()'s direct indexing, so tooling paths with no variant fall through to the existing all default instead of raising. The variant is set by set_core_data during config load, well before any compile-time call to check_esp_idf_install, so the per-variant narrowing actually applies to real builds.

  • 149 tests in the two touched files pass locally; ruff check and ruff format --check are clean on the touched paths.

  • One non-blocking nit: esphome/build_gen/espidf.py:78 still open-codes the variant→target mapping the PR just centralized.


✅ Resolved since last review (1)

Previously-flagged issues verified fixed
  • esphome/espidf/framework.py:955 The "all" collapse branch of the stamp merge lost its test


Checklist

  • Prior suggestion fixed: "all" collapse branch now has a regression test
  • Prior MEDIUM fixed: faulted stamp reads warn instead of debug
  • Prior MEDIUM fixed: non-dict stamp logs the unexpected type
  • Stamp coverage/merge logic correct for subset, superset, all-collapse and tools-change cases
  • Backward compatibility: existing 'all' stamps stay valid, no upgrade churn
  • Variant lookup is fault-tolerant and set before compile-time install
  • venv decoupling safe (its inputs change only on fresh extract)
  • CI shared toolchain cache stays complete; action.yml documents the guard
  • Test isolation (tmp install root, CORE reset) and tests assert behavior
  • Lint, format and the touched test files pass locally (149 tests)
  • PR description covers every significant change in the diff
  • Shared helper adopted by every copy of the mapping
ℹ️ Triage summary

1 pre-existing finding(s) on unchanged code suppressed (freeze).


Silent Failure Analysis

🟡 **MEDIUM** — unvalidated override silently discards caller input
esphome/espidf/framework.py:52-55

Risk: The "explicit" flag is derived from the raw env string but the value used is the parsed list, so a value that parses to nothing (e.g. ESPHOME_IDF_DEFAULT_TARGETS=";" or whitespace) is treated as an explicit override, silently throws away the caller's valid per-variant targets, runs the installer with an empty --targets=, and then makes _stamp_covers return True forever after because the empty set is a subset of any stored target list — no toolchain is installed and nothing reports it until the compile fails with a missing compiler.

_IDF_DEFAULT_TARGETS_EXPLICIT = bool(os.environ.get("ESPHOME_IDF_DEFAULT_TARGETS"))
...
if _IDF_DEFAULT_TARGETS_EXPLICIT or not targets:
    targets = ESPHOME_IDF_DEFAULT_TARGETS

Fix: Gate on the parsed list (_IDF_DEFAULT_TARGETS_EXPLICIT = bool(ESPHOME_IDF_DEFAULT_TARGETS)) and reject an empty resolved targets in check_esp_idf_install with a clear error instead of passing it to the installer.

🟡 **MEDIUM** — silent false return on malformed data
esphome/espidf/framework.py:270-280

Risk: A stamp that parses fine but carries a missing or non-list targets silently forces a full toolchain reinstall with no diagnostic, even though the sibling _read_stamp deliberately warns on every comparable fault class (corrupt JSON, unreadable file, non-dict payload) for exactly this reason; the same silent-skip also disables the target-merge branch, so the union of installed targets is quietly dropped from the rewritten stamp.

stored_targets = stored.get("targets")
if not isinstance(stored_targets, list):
    return False
return "all" in stored_targets or set(requested["targets"]) <= set(stored_targets)

Fix: Log a warning in the non-list branch (matching _read_stamp's treatment of a malformed stamp) so an unexplained reinstall and a reset target set are diagnosable.


Automated review by Kōan (Claude) HEAD=3499f05 4 min 31s

@esphbot esphbot left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

No blocking issues found — ready to merge.

@bdraco
bdraco marked this pull request as ready for review July 26, 2026 22:27
@bdraco
bdraco commented Jul 27, 2026
Copy link
Copy Markdown
Member Author

Thanks

@bdraco
bdraco merged commit c0aa121 into dev Jul 27, 2026
57 checks passed
@bdraco
bdraco deleted the espidf-per-variant-targets branch July 27, 2026 18:39
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 29, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

0