mirror of
https://github.com/esphome/esphome.git
synced 2026-09-11 15:27:33 +00:00
Probe the built .gch on the native paths so strict CI reds on an unloadable pch
This commit is contained in:
@@ -42,6 +42,7 @@ from esphome.build_helpers.pch import (
|
||||
pch_degraded,
|
||||
pch_enabled,
|
||||
pch_header_text,
|
||||
pch_strict,
|
||||
)
|
||||
from esphome.components.esp8266 import build_surgery
|
||||
from esphome.components.esp8266.boards import (
|
||||
@@ -1290,7 +1291,27 @@ def write_project(paths: InstalledPaths, ccache: str | None) -> bool:
|
||||
f"-Winvalid-pch -Wno-error=invalid-pch -include {PCH_HEADER_NAME}"
|
||||
]
|
||||
lines.append(f"srccxxflags = {' '.join(cxx_parts)}")
|
||||
src_cxx_override = ("$srccxxflags", gch)
|
||||
pch_dep = gch
|
||||
if pch_strict():
|
||||
# Consumers wait on the probe stamp, so an unloadable .gch
|
||||
# reds the build here instead of warning ~100 times
|
||||
lines.append("rule pchprobe")
|
||||
lines.append(
|
||||
" command = $cxx $cxxflags $flags -Winvalid-pch"
|
||||
" -Werror=invalid-pch"
|
||||
f" -include {PCH_HEADER_NAME} -fsyntax-only -x c++"
|
||||
f" {_q(Path(os.devnull))} && $stamp"
|
||||
)
|
||||
lines.append(" description = PCHPROBE $out")
|
||||
lines.append(f"build esphome_pch.probe: pchprobe {gch}")
|
||||
if src_other:
|
||||
lines.append(f" flags = {' '.join(src_other)}")
|
||||
stamp = (
|
||||
"cmd /c copy /y nul $out >nul" if os.name == "nt" else "touch $out"
|
||||
)
|
||||
lines.append(f" stamp = {stamp}")
|
||||
pch_dep = f"{gch} esphome_pch.probe"
|
||||
src_cxx_override = ("$srccxxflags", pch_dep)
|
||||
mark_pch_emitted()
|
||||
src_objs = _ninja_compile_edges(
|
||||
lines,
|
||||
|
||||
@@ -435,6 +435,42 @@ def prepare_pch(
|
||||
os.utime(header)
|
||||
pch_degraded(f"compile failed: {error[:200]}")
|
||||
return
|
||||
# Load probe: some toolchains build a .gch they then refuse to load
|
||||
# (per-process ASLR); dep flags are already stripped from cmd, so no
|
||||
# -MF is needed
|
||||
probe_cmd = [
|
||||
*cmd[: cmd.index("-x")],
|
||||
"-Winvalid-pch",
|
||||
"-include",
|
||||
str(header),
|
||||
"-fsyntax-only",
|
||||
"-x",
|
||||
"c++",
|
||||
os.devnull,
|
||||
]
|
||||
try:
|
||||
probe = subprocess.run(
|
||||
probe_cmd,
|
||||
cwd=cmd_dir,
|
||||
env={**os.environ, "LC_ALL": "C"},
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
timeout=300,
|
||||
)
|
||||
except (OSError, subprocess.SubprocessError) as err:
|
||||
_LOGGER.warning("Precompiled header probe did not run: %s", err)
|
||||
discard_pch(build_dir)
|
||||
pch_degraded(f"probe did not run: {err}")
|
||||
return
|
||||
if probe.returncode != 0 or ".gch" in probe.stderr:
|
||||
error = f"toolchain cannot load the pch: {probe.stderr.strip()[:400]}"
|
||||
_LOGGER.warning("Precompiled header failed; compiling without it: %s", error)
|
||||
discard_pch(build_dir)
|
||||
failed_marker.write_text(checksum + "\n", encoding="utf-8")
|
||||
os.utime(header)
|
||||
pch_degraded(error)
|
||||
return
|
||||
failed_marker.unlink(missing_ok=True)
|
||||
sum_path.write_text(checksum + "\n", encoding="utf-8")
|
||||
# Consumers depend on the header (depfiles cannot see through a .gch);
|
||||
|
||||
@@ -1856,3 +1856,21 @@ def test_write_project_pch_strict_raises_on_skip(
|
||||
)
|
||||
with pytest.raises(EsphomeError, match="precedes the pch"):
|
||||
_write_ninja(paths, ccache="/usr/bin/ccache")
|
||||
|
||||
|
||||
def test_write_project_pch_strict_emits_probe_edge(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""Strict mode gates C++ src edges on a hard-failing load probe."""
|
||||
paths = _make_framework(tmp_path)
|
||||
_set_flags("-DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH")
|
||||
content = _write_ninja(paths, ccache="/usr/bin/ccache")
|
||||
assert "pchprobe" not in content
|
||||
|
||||
monkeypatch.setenv("ESPHOME_PCH_STRICT", "1")
|
||||
content = _write_ninja(paths, ccache="/usr/bin/ccache")
|
||||
assert "build esphome_pch.probe: pchprobe esphome_pch.h.gch" in content
|
||||
assert "-Werror=invalid-pch" in content
|
||||
for line in content.splitlines():
|
||||
if line.startswith("build obj/src/main.cpp.o:"):
|
||||
assert line.endswith("| esphome_pch.h.gch esphome_pch.probe")
|
||||
|
||||
@@ -550,6 +550,9 @@ def test_prepare_pch_writes_header_and_sum(tmp_path: Path) -> None:
|
||||
gch = dev / "build" / "esphome_pch.h.gch"
|
||||
|
||||
def fake_compile(cmd, **kwargs):
|
||||
if "-fsyntax-only" in cmd:
|
||||
# The load probe follows a successful .gch build
|
||||
return subprocess.CompletedProcess(cmd, 0, "", "")
|
||||
# The compile must target the header, not the stub TU
|
||||
assert cmd[-5:-3] == ["c++-header", "-c"]
|
||||
gch.write_bytes(b"gch")
|
||||
@@ -1097,3 +1100,41 @@ def test_prepare_pch_strict_raises_on_missing_db(
|
||||
pytest.raises(EsphomeError, match="no usable compile command"),
|
||||
):
|
||||
prepare_pch()
|
||||
|
||||
|
||||
def test_prepare_pch_probe_rejection_latches_and_degrades(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""A toolchain that cannot load its own .gch discards it, latches the
|
||||
marker, and fails strict mode."""
|
||||
from esphome.build_gen.espidf import prepare_pch
|
||||
from esphome.core import EsphomeError
|
||||
|
||||
dev = _make_pch_device(tmp_path, "dev_p")
|
||||
CORE.build_path = dev
|
||||
gch = dev / "build" / "esphome_pch.h.gch"
|
||||
|
||||
def rejecting(cmd, **kwargs):
|
||||
if "-fsyntax-only" in cmd:
|
||||
return subprocess.CompletedProcess(
|
||||
cmd, 0, "", "warning: esphome_pch.h.gch: had text segment "
|
||||
)
|
||||
gch.write_bytes(b"gch")
|
||||
return subprocess.CompletedProcess(cmd, 0, "", "")
|
||||
|
||||
with (
|
||||
patch.object(CORE, "name", "test"),
|
||||
patch("esphome.build_helpers.pch.subprocess.run", side_effect=rejecting),
|
||||
):
|
||||
prepare_pch()
|
||||
assert not gch.exists()
|
||||
assert (dev / "build" / "esphome_pch.h.gch.failed").exists()
|
||||
|
||||
monkeypatch.setenv("ESPHOME_PCH_STRICT", "1")
|
||||
(dev / "build" / "esphome_pch.h.gch.failed").unlink()
|
||||
with (
|
||||
patch.object(CORE, "name", "test"),
|
||||
patch("esphome.build_helpers.pch.subprocess.run", side_effect=rejecting),
|
||||
pytest.raises(EsphomeError, match="cannot load the pch"),
|
||||
):
|
||||
prepare_pch()
|
||||
|
||||
Reference in New Issue
Block a user