diff --git a/esphome/build_helpers/pch.py b/esphome/build_helpers/pch.py index 385ac311f8..6277520e7d 100644 --- a/esphome/build_helpers/pch.py +++ b/esphome/build_helpers/pch.py @@ -165,7 +165,12 @@ def pch_cmake_consumer(target: str, sources_var: str) -> str: return "" escalation = pch_consumer_escalation() return f""" -# ESPHome precompiled header (see esphome/build_helpers/pch.py) +# ESPHome precompiled header (see esphome/build_helpers/pch.py). +# The touch keeps OBJECT_DEPENDS satisfiable when the build system itself +# wiped the build dir after the header was written (west --pristine) +if(NOT EXISTS "${{CMAKE_BINARY_DIR}}/{PCH_HEADER_NAME}") + file(TOUCH "${{CMAKE_BINARY_DIR}}/{PCH_HEADER_NAME}") +endif() target_compile_options({target} PRIVATE "$<$:-Winvalid-pch>" "$<$:{escalation}>" diff --git a/esphome/components/nrf52/__init__.py b/esphome/components/nrf52/__init__.py index 42d4946306..b32eb3c9eb 100644 --- a/esphome/components/nrf52/__init__.py +++ b/esphome/components/nrf52/__init__.py @@ -955,6 +955,7 @@ def run_compile(args, config: ConfigType) -> bool: # flags. Only when the app DB is missing: input changes wipe the # build dir, so an existing DB is settled and --cmake-only would # reconfigure for nothing. + prepare = True app_dir = _app_build_dir(build_dir) if not (app_dir / "compile_commands.json").is_file(): if not run_command_ok( @@ -980,16 +981,21 @@ def run_compile(args, config: ConfigType) -> bool: stream_output=True, cwd=str(paths["framework_path"]), ): - # A pch-only prerequisite: degrade, let the real build report + # A pch-only prerequisite: degrade, let the real build report. + # Also skip the .gch compile: it would fail on the missing + # headers and latch .gch.failed until an identity change _LOGGER.warning( "Zephyr header generation failed; compiling without the pch" ) pch.discard_pch(app_dir) pch.pch_degraded("zephyr_generated_headers failed") + prepare = False else: + prepare = True app_dir = _app_build_dir(build_dir) - pch.guarded_prepare(app_dir, partial(_prepare_pch, app_dir)) + if prepare: + pch.guarded_prepare(app_dir, partial(_prepare_pch, app_dir)) if not run_command_ok( west_cmd, diff --git a/tests/unit_tests/build_helpers/test_pch.py b/tests/unit_tests/build_helpers/test_pch.py index 28347b6343..56ab121431 100644 --- a/tests/unit_tests/build_helpers/test_pch.py +++ b/tests/unit_tests/build_helpers/test_pch.py @@ -244,6 +244,8 @@ def test_pch_cmake_consumer_substitutes_target_and_sources( assert '"$<$:esphome_pch.h>"' in block assert "set_source_files_properties(${APP_SOURCES} PROPERTIES" in block assert 'OBJECT_DEPENDS "${CMAKE_BINARY_DIR}/esphome_pch.h"' in block + # Placeholder guard: survives a build-system-side pristine wipe + assert 'file(TOUCH "${CMAKE_BINARY_DIR}/esphome_pch.h")' in block def test_pch_cmake_consumer_strict_escalates( diff --git a/tests/unit_tests/components/nrf52/test_pch.py b/tests/unit_tests/components/nrf52/test_pch.py index ab733107d9..6c483e3ff0 100644 --- a/tests/unit_tests/components/nrf52/test_pch.py +++ b/tests/unit_tests/components/nrf52/test_pch.py @@ -230,7 +230,8 @@ class TestRunCompilePhases: with pytest.raises(EsphomeError, match="nRF52 native build failed"): self._run() assert "Zephyr header generation failed" in caplog.text - assert prepare.called + # The doomed .gch compile is skipped: it would latch .gch.failed + assert not prepare.called def test_generated_headers_failure_strict_raises( self, monkeypatch: pytest.MonkeyPatch, compile_ctx: CompileCtx