mirror of
https://github.com/esphome/esphome.git
synced 2026-09-06 04:56:04 +00:00
Escalate all strict consumers, keep cached-probe rejections unlatched, drop the dead probe knob
This commit is contained in:
@@ -1303,7 +1303,7 @@ def write_project(paths: InstalledPaths, ccache: str | None) -> bool:
|
||||
if pch_strict():
|
||||
# Consumers wait on the probe stamp, so an unloadable .gch
|
||||
# reds the build here instead of warning ~100 times
|
||||
probe = " ".join(pch_probe_args(PCH_HEADER_NAME, fatal=True))
|
||||
probe = " ".join(pch_probe_args(PCH_HEADER_NAME))
|
||||
lines.append("rule pchprobe")
|
||||
# $out only expands in rule text, hence the inline stamp
|
||||
lines.append(
|
||||
|
||||
@@ -298,12 +298,15 @@ def _pch_cmake() -> str:
|
||||
"""
|
||||
if not pch_enabled():
|
||||
return ""
|
||||
# Strict inverts: a per-process consumer rejection reds the build
|
||||
escalation = "-Werror=invalid-pch" if pch.pch_strict() else "-Wno-error=invalid-pch"
|
||||
return f"""
|
||||
# ESPHome precompiled header (see esphome/build_helpers/pch.py).
|
||||
# OBJECT_DEPENDS is on the header, not the .gch: pch-baked headers drop
|
||||
# out of TU depfiles, and prepare_pch() touches the header on rebuild.
|
||||
target_compile_options(${{COMPONENT_LIB}} PRIVATE
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:-Winvalid-pch>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:{escalation}>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:-include>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:{PCH_HEADER_NAME}>"
|
||||
)
|
||||
|
||||
@@ -120,15 +120,15 @@ def pch_disabled_degraded() -> None:
|
||||
pch_degraded("pch disabled by ESPHOME_PCH_ENABLE")
|
||||
|
||||
|
||||
def pch_probe_args(header: str, fatal: bool = False) -> list[str]:
|
||||
def pch_probe_args(header: str) -> list[str]:
|
||||
"""Flags that load-check a built .gch via a syntax-only compile.
|
||||
|
||||
``fatal`` escalates a rejected pch to an error for consumers that
|
||||
cannot inspect stderr (the ninja probe edge).
|
||||
Rejection must be a nonzero exit (never just a wording match), so the
|
||||
invalid-pch class is always escalated.
|
||||
"""
|
||||
return [
|
||||
"-Winvalid-pch",
|
||||
*(["-Werror=invalid-pch"] if fatal else []),
|
||||
"-Werror=invalid-pch",
|
||||
"-include",
|
||||
header,
|
||||
"-fsyntax-only",
|
||||
@@ -452,30 +452,37 @@ def prepare_pch(
|
||||
os.utime(header)
|
||||
pch_degraded(f"{reason}: {error[:200]}")
|
||||
|
||||
def _probe() -> None:
|
||||
def _probe(latch: bool = True) -> None:
|
||||
"""Load-check the built .gch: some toolchains build one they then
|
||||
refuse to load (per-process ASLR). Dep flags are already stripped
|
||||
from cmd, so no -MF is needed; cmd ends with the fixed
|
||||
"-x c++-header -c -o" tail."""
|
||||
"-x c++-header -c -o" tail. A cached-header rejection may not
|
||||
reproduce (per-process), so that caller passes latch=False."""
|
||||
# The fixed tail pch_compile_command appends; the slice below
|
||||
# depends on it
|
||||
assert cmd[-6:-4] == ["-x", "c++-header"], cmd[-6:]
|
||||
# fatal: rejection must be a nonzero exit, not a wording match
|
||||
probe = _run([*cmd[:-6], *pch_probe_args(str(header), fatal=True)], "probe")
|
||||
probe = _run([*cmd[:-6], *pch_probe_args(str(header))], "probe")
|
||||
if probe is None:
|
||||
return
|
||||
if probe.returncode != 0 or ".gch" in probe.stderr:
|
||||
_fail(
|
||||
probe.stderr.strip() or f"exit code {probe.returncode}",
|
||||
"toolchain cannot load the pch",
|
||||
)
|
||||
error = probe.stderr.strip() or f"exit code {probe.returncode}"
|
||||
if latch:
|
||||
_fail(error, "toolchain cannot load the pch")
|
||||
else:
|
||||
_LOGGER.warning(
|
||||
"Precompiled header failed; compiling without it: %s",
|
||||
error[:400],
|
||||
)
|
||||
discard_pch(build_dir)
|
||||
pch_degraded(f"toolchain cannot load the pch: {error[:200]}")
|
||||
|
||||
if gch.is_file() and _read_stamp(sum_path) == checksum:
|
||||
_log_pch_in_use()
|
||||
if pch_strict():
|
||||
# Rejection is per-process, so a cached .gch must re-prove
|
||||
# loadability for the strict gate (CI-only cost)
|
||||
_probe()
|
||||
# loadability for the strict gate (CI-only cost); no latch,
|
||||
# since the rejection may not reproduce either
|
||||
_probe(latch=False)
|
||||
return
|
||||
if _read_stamp(failed_marker) == checksum:
|
||||
_LOGGER.info(
|
||||
|
||||
@@ -383,7 +383,13 @@ def _setup_pch() -> bool | None:
|
||||
# -Wno-error: the per-process probe can pass while a later cc1plus
|
||||
# rejects the .gch; that must stay a warning under user -Werror.
|
||||
projenv.Prepend( # noqa: F821
|
||||
CXXFLAGS=["-Winvalid-pch", "-Wno-error=invalid-pch", "-include", header.name]
|
||||
CXXFLAGS=[
|
||||
"-Winvalid-pch",
|
||||
# Strict inverts: a per-process consumer rejection reds the build
|
||||
"-Werror=invalid-pch" if _STRICT else "-Wno-error=invalid-pch",
|
||||
"-include",
|
||||
header.name,
|
||||
]
|
||||
)
|
||||
projenv["ENV"].update(ccache_updates) # noqa: F821
|
||||
print("ESPHome: Compiling with precompiled header")
|
||||
|
||||
@@ -604,6 +604,11 @@ def test_component_cmakelists_pch_block(monkeypatch: pytest.MonkeyPatch) -> None
|
||||
|
||||
content = get_component_cmakelists()
|
||||
assert '"$<$<COMPILE_LANGUAGE:CXX>:-include>"' in content
|
||||
assert "-Wno-error=invalid-pch" in content
|
||||
monkeypatch.setenv("ESPHOME_PCH_STRICT", "1")
|
||||
strict_content = get_component_cmakelists()
|
||||
assert "-Werror=invalid-pch" in strict_content
|
||||
monkeypatch.delenv("ESPHOME_PCH_STRICT")
|
||||
assert '"$<$<COMPILE_LANGUAGE:CXX>:esphome_pch.h>"' in content
|
||||
monkeypatch.setenv("ESPHOME_PCH_ENABLE", "0")
|
||||
assert "-include" not in get_component_cmakelists()
|
||||
@@ -1296,3 +1301,6 @@ def test_prepare_pch_strict_reprobes_cached_gch(
|
||||
):
|
||||
prepare_pch()
|
||||
assert not gch.exists()
|
||||
# Per-process rejection may not reproduce: the cached path must not
|
||||
# latch the pch off for later non-strict builds
|
||||
assert not (dev / "build" / "esphome_pch.h.gch.failed").exists()
|
||||
|
||||
@@ -542,4 +542,6 @@ def test_pch_script_strict_projenv_skip_gated_on_nobuild(
|
||||
|
||||
def test_pch_script_strict_passes_on_success(tmp_path: Path) -> None:
|
||||
scons_env = _run_script(tmp_path, env_vars={"ESPHOME_PCH_STRICT": "1"})
|
||||
assert scons_env.prepended
|
||||
# Strict escalates the consumer edges too
|
||||
assert "-Werror=invalid-pch" in scons_env.prepended
|
||||
assert "-Wno-error=invalid-pch" not in scons_env.prepended
|
||||
|
||||
Reference in New Issue
Block a user