diff --git a/esphome/build_gen/espidf.py b/esphome/build_gen/espidf.py index cec6c4e273..f5965c0a27 100644 --- a/esphome/build_gen/espidf.py +++ b/esphome/build_gen/espidf.py @@ -6,7 +6,7 @@ from pathlib import Path from esphome.build_helpers import pch from esphome.build_helpers.pch import ( - PCH_CORE_HEADER, + PCH_DEFAULT_HEADERS, PCH_HEADER_NAME, pch_enabled, pch_header_text, @@ -29,26 +29,6 @@ from esphome.helpers import mkdir_p, write_file_if_changed _LOGGER = logging.getLogger(__name__) -# Prefix-header contents, defines.h first so USE_* macros exist for the -# rest. Deliberately hard-coded: frequency-derived sets measured no better -# and kept selecting headers that cannot compile standalone (X-macro, -# platform-variant). Every entry must be safe to include first in an -# empty TU. Caveat: application.h/automation.h become ambiently visible, -# so a TU missing those #includes still builds here but not on other -# platforms; ESPHOME_PCH_ENABLE=0 restores the strict view. -_PCH_HEADERS = ( - PCH_CORE_HEADER, - "esphome/core/component.h", - "esphome/core/helpers.h", - "esphome/core/log.h", - "esphome/core/application.h", - "esphome/core/automation.h", -) - -# Header and .gch/.sum sidecars, relative to the device dir; see -# _pch_cmake() and prepare_pch() for the layout rationale -_PCH_BUILD_HEADER = f"build/{PCH_HEADER_NAME}" - # Replaces the IDF default C++ standard (-std=gnu++2b appended to # CXX_COMPILE_OPTIONS by project.cmake's __build_init) with the one set via # cg.set_cpp_standard(). Emitted between include(project.cmake) and project(), @@ -355,7 +335,7 @@ def prepare_pch() -> None: sdkconfig = f"unreadable:{type(err).__name__}:{err.errno}" pch.prepare_pch( CORE.relative_build_path("build"), - _PCH_HEADERS, + PCH_DEFAULT_HEADERS, ( str(idf_version()), CORE.cpp_standard or "", @@ -387,7 +367,8 @@ def write_project( if pch_enabled(): write_file_if_changed( - CORE.relative_build_path(_PCH_BUILD_HEADER), pch_header_text(_PCH_HEADERS) + CORE.relative_build_path("build", PCH_HEADER_NAME), + pch_header_text(PCH_DEFAULT_HEADERS), ) # Snapshot the exclusion set so has_outdated_files() can trigger a diff --git a/esphome/build_helpers/pch.py b/esphome/build_helpers/pch.py index 6fa09e7457..aa3a65cd3b 100644 --- a/esphome/build_helpers/pch.py +++ b/esphome/build_helpers/pch.py @@ -43,6 +43,23 @@ PCH_ARTIFACT_NAMES = ( # The core defines header every backend anchors its prefix on. PCH_CORE_HEADER = "esphome/core/defines.h" +# Prefix-header contents for backends that inject a curated set (rather +# than mirroring the TUs' own force-includes), defines.h first so USE_* +# macros exist for the rest. Deliberately hard-coded: frequency-derived +# sets measured no better and kept selecting headers that cannot compile +# standalone (X-macro, platform-variant). Every entry must be safe to +# include first in an empty TU. Caveat: application.h/automation.h become +# ambiently visible, so a TU missing those #includes still builds on such +# backends; ESPHOME_PCH_ENABLE=0 restores the strict view. +PCH_DEFAULT_HEADERS = ( + PCH_CORE_HEADER, + "esphome/core/component.h", + "esphome/core/helpers.h", + "esphome/core/log.h", + "esphome/core/application.h", + "esphome/core/automation.h", +) + # ccache cannot hash through a .gch; CCACHE_PCH_EXTSUM makes it hash the # .sum sidecar instead of the .gch bytes, which are not reproducible. # Keep in sync with the literals in platformio/pch.py.script. diff --git a/tests/unit_tests/build_gen/test_espidf.py b/tests/unit_tests/build_gen/test_espidf.py index a1dc30b958..ad520b9190 100644 --- a/tests/unit_tests/build_gen/test_espidf.py +++ b/tests/unit_tests/build_gen/test_espidf.py @@ -494,10 +494,10 @@ def test_get_component_cmakelists_no_compile_features() -> None: def _make_pch_device(tmp_path: Path, name: str) -> Path: """A device dir with the pch source headers and a stub compile_commands.""" - from esphome.build_gen.espidf import _PCH_HEADERS + from esphome.build_helpers.pch import PCH_DEFAULT_HEADERS dev = tmp_path / name - for header in _PCH_HEADERS: + for header in PCH_DEFAULT_HEADERS: path = dev / "src" / header path.parent.mkdir(parents=True, exist_ok=True) path.write_text("") @@ -512,7 +512,7 @@ def _make_pch_device(tmp_path: Path, name: str) -> Path: build.mkdir(exist_ok=True) from esphome.build_helpers.pch import pch_header_text - (build / "esphome_pch.h").write_text(pch_header_text(_PCH_HEADERS)) + (build / "esphome_pch.h").write_text(pch_header_text(PCH_DEFAULT_HEADERS)) # Native separators: mixed f-string paths break the src-prefix match # on Windows src_file = str(dev / "src" / "a.cpp") @@ -674,7 +674,7 @@ def test_pch_compile_command_rejects_unusable_entries(tmp_path: Path) -> None: def test_pch_header_list_order_is_in_checksum( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: - """Reordering _PCH_HEADERS keeps the include closure identical, but the + """Reordering PCH_DEFAULT_HEADERS keeps the include closure identical, but the generated header text differs, so the .gch must rebuild.""" import esphome.build_gen.espidf as espidf_mod @@ -693,7 +693,9 @@ def test_pch_header_list_order_is_in_checksum( espidf_mod.prepare_pch() first = (dev / "build" / "esphome_pch.h.gch.sum").read_text() monkeypatch.setattr( - espidf_mod, "_PCH_HEADERS", tuple(reversed(espidf_mod._PCH_HEADERS)) + espidf_mod, + "PCH_DEFAULT_HEADERS", + tuple(reversed(espidf_mod.PCH_DEFAULT_HEADERS)), ) espidf_mod.prepare_pch() assert (dev / "build" / "esphome_pch.h.gch.sum").read_text() != first @@ -818,8 +820,8 @@ def test_write_project_pch_disabled_writes_no_header( def test_write_project_writes_pch_header(tmp_path: Path) -> None: """The header write_project emits is what _pch_cmake() force-includes; this pairing is the one non-fail-safe path in the design.""" - from esphome.build_gen.espidf import _PCH_HEADERS, write_project - from esphome.build_helpers.pch import pch_header_text + from esphome.build_gen.espidf import write_project + from esphome.build_helpers.pch import PCH_DEFAULT_HEADERS, pch_header_text _write_project_description(tmp_path, {}) CORE.build_path = tmp_path @@ -829,7 +831,7 @@ def test_write_project_writes_pch_header(tmp_path: Path) -> None: ): write_project() assert (tmp_path / "build" / "esphome_pch.h").read_text() == pch_header_text( - _PCH_HEADERS + PCH_DEFAULT_HEADERS )