diff --git a/esphome/components/nrf52/__init__.py b/esphome/components/nrf52/__init__.py index df86c1693e..9107ff5149 100644 --- a/esphome/components/nrf52/__init__.py +++ b/esphome/components/nrf52/__init__.py @@ -963,15 +963,29 @@ def run_compile(args, config: ConfigType) -> bool: # so an existing DB is settled, and --cmake-only reconfigures. # Sysbuild configures the app image during its own configure, so the # app's flags and autoconf.h are settled after this phase too. - if not ( - _app_build_dir(build_dir) / "compile_commands.json" - ).is_file() and not run_command_ok( - west_cmd + ["--cmake-only", "--", "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"], - env=env, - stream_output=True, - cwd=str(paths["framework_path"]), - ): - raise EsphomeError("nRF52 native build failed") + if not (_app_build_dir(build_dir) / "compile_commands.json").is_file(): + if not run_command_ok( + west_cmd + ["--cmake-only", "--", "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"], + env=env, + stream_output=True, + cwd=str(paths["framework_path"]), + ): + raise EsphomeError("nRF52 native build failed") + # The pch includes zephyr/kernel.h, whose syscall headers are + # generated at build time (same target the clang-tidy flow uses) + if not run_command_ok( + [ + "cmake", + "--build", + str(_app_build_dir(build_dir)), + "--target", + "zephyr_generated_headers", + ], + env=env, + stream_output=True, + cwd=str(paths["framework_path"]), + ): + raise EsphomeError("nRF52 native build failed") # An optional speedup must never abort the build app_dir = _app_build_dir(build_dir) diff --git a/tests/unit_tests/components/nrf52/test_pch.py b/tests/unit_tests/components/nrf52/test_pch.py index 370a5c43ba..ae90570c4a 100644 --- a/tests/unit_tests/components/nrf52/test_pch.py +++ b/tests/unit_tests/components/nrf52/test_pch.py @@ -195,7 +195,8 @@ class TestRunCompilePhases: def test_missing_db_runs_cmake_phase(self, compile_ctx) -> None: run_cmd, prepare, build_dir = compile_ctx - results = iter([True, False]) # cmake-only ok, final build fails + # cmake-only ok, generated headers ok, final build fails + results = iter([True, True, False]) def west(cmd, **kwargs): # Phase 1 configures the sysbuild app domain @@ -208,10 +209,22 @@ class TestRunCompilePhases: with pytest.raises(EsphomeError, match="nRF52 native build failed"): self._run() assert "--cmake-only" in run_cmd.call_args_list[0].args[0] - assert "--cmake-only" not in run_cmd.call_args_list[1].args[0] + # Generated syscall headers are built in the app domain pre-pch + headers_cmd = run_cmd.call_args_list[1].args[0] + assert headers_cmd[:2] == ["cmake", "--build"] + assert str(build_dir / "zephyr") in headers_cmd + assert "zephyr_generated_headers" in headers_cmd + assert "--cmake-only" not in run_cmd.call_args_list[2].args[0] # The pch is prepared in the app domain dir, not the sysbuild root assert prepare.call_args.args[0] == build_dir / "zephyr" + def test_generated_headers_failure_raises(self, compile_ctx) -> None: + run_cmd, prepare, _ = compile_ctx + run_cmd.side_effect = [True, False] + with pytest.raises(EsphomeError, match="nRF52 native build failed"): + self._run() + assert not prepare.called + def test_cmake_phase_failure_raises(self, compile_ctx) -> None: run_cmd, prepare, _ = compile_ctx run_cmd.side_effect = [False] @@ -264,10 +277,10 @@ class TestRunCompilePhases: ) -> None: run_cmd, prepare, _ = compile_ctx prepare.side_effect = RuntimeError("boom") - run_cmd.side_effect = [True, False] + run_cmd.side_effect = [True, True, False] with pytest.raises(EsphomeError, match="nRF52 native build failed"): self._run() - assert run_cmd.call_count == 2 + assert run_cmd.call_count == 3 assert "Precompiled header setup failed" in caplog.text def test_prepare_failure_strict_raises( @@ -276,6 +289,6 @@ class TestRunCompilePhases: monkeypatch.setenv("ESPHOME_PCH_STRICT", "1") run_cmd, prepare, _ = compile_ctx prepare.side_effect = RuntimeError("boom") - run_cmd.side_effect = [True] + run_cmd.side_effect = [True, True] with pytest.raises(RuntimeError, match="boom"): self._run()