Degrade on header-generation failure, surface probe and touch errors

This commit is contained in:
J. Nick Koston
2026-08-27 12:38:42 -05:00
parent a638ac3982
commit 17d1874ca3
4 changed files with 48 additions and 7 deletions
+4 -1
View File
@@ -221,8 +221,11 @@ def guarded_prepare(build_dir: Path, prepare: Callable[[], None]) -> None:
raise
header = build_dir / PCH_HEADER_NAME
if not header.exists():
with suppress(OSError):
try:
header.touch()
except OSError as err:
# The coming OBJECT_DEPENDS error would hide the real cause
_LOGGER.warning("Could not create the pch placeholder: %s", err)
_LOGGER.warning(
"Precompiled header setup failed; compiling without it", exc_info=True
)
+13 -4
View File
@@ -6,6 +6,7 @@ import logging
from pathlib import Path
import re
import shutil
import stat
import subprocess
from esphome import pins
@@ -838,9 +839,12 @@ def _app_build_dir(build_dir: Path) -> Path:
non-sysbuild zephyr/ output dir has no CMakeCache.txt) so it stays
truthful mid-build, unlike an SDK-version check."""
sysbuild_app = build_dir / "zephyr"
if (sysbuild_app / "CMakeCache.txt").is_file():
return sysbuild_app
return build_dir
try:
cache = (sysbuild_app / "CMakeCache.txt").stat()
except (FileNotFoundError, NotADirectoryError):
return build_dir
# Other stat errors propagate; is_file() would silently mislocate the pch
return sysbuild_app if stat.S_ISREG(cache.st_mode) else build_dir
def _prepare_pch(app_dir: Path) -> None:
@@ -976,7 +980,12 @@ def run_compile(args, config: ConfigType) -> bool:
stream_output=True,
cwd=str(paths["framework_path"]),
):
raise EsphomeError("nRF52 Zephyr header generation failed")
# A pch-only prerequisite: degrade, let the real build report
_LOGGER.warning(
"Zephyr header generation failed; compiling without the pch"
)
pch.discard_pch(app_dir)
pch.pch_degraded("zephyr_generated_headers failed")
else:
app_dir = _app_build_dir(build_dir)
@@ -249,6 +249,7 @@ def test_pch_cmake_consumer_substitutes_target_and_sources(
def test_pch_cmake_consumer_strict_escalates(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.delenv("ESPHOME_PCH_ENABLE", raising=False)
monkeypatch.setenv("ESPHOME_PCH_STRICT", "1")
assert "-Werror=invalid-pch" in pch.pch_cmake_consumer("app", "${APP_SOURCES}")
+30 -2
View File
@@ -98,6 +98,20 @@ def test_app_build_dir_top_level_layout(build_dir: Path) -> None:
assert nrf52._app_build_dir(build_dir) == build_dir
def test_app_build_dir_ignores_cache_directory(build_dir: Path) -> None:
(build_dir / "zephyr" / "CMakeCache.txt").mkdir(parents=True)
assert nrf52._app_build_dir(build_dir) == build_dir
def test_app_build_dir_propagates_stat_errors(build_dir: Path) -> None:
# is_file() would swallow this and mislocate the pch
with (
patch.object(Path, "stat", side_effect=PermissionError("denied")),
pytest.raises(PermissionError),
):
nrf52._app_build_dir(build_dir)
def test_prepare_pch_extras_carry_build_identity(build_dir: Path) -> None:
_write_autoconf(build_dir)
with (
@@ -203,10 +217,24 @@ class TestRunCompilePhases:
# 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:
def test_generated_headers_failure_degrades(
self, compile_ctx, caplog: pytest.LogCaptureFixture
) -> None:
run_cmd, prepare, _ = compile_ctx
# headers target fails, the real build still runs (and fails here)
run_cmd.side_effect = [True, False, False]
with pytest.raises(EsphomeError, match="nRF52 native build failed"):
self._run()
assert "Zephyr header generation failed" in caplog.text
assert prepare.called
def test_generated_headers_failure_strict_raises(
self, monkeypatch: pytest.MonkeyPatch, compile_ctx
) -> None:
monkeypatch.setenv("ESPHOME_PCH_STRICT", "1")
run_cmd, prepare, _ = compile_ctx
run_cmd.side_effect = [True, False]
with pytest.raises(EsphomeError, match="header generation failed"):
with pytest.raises(EsphomeError, match="ESPHOME_PCH_STRICT"):
self._run()
assert not prepare.called