From a0c3a010e479b05e9438b439a9e21ecc443b7b3e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 27 Aug 2026 00:21:14 -0500 Subject: [PATCH] Guard the prefix for C edges, gate it on the pch knob, pin its resolution --- esphome/components/host/__init__.py | 20 +++++++++++--------- esphome/core/pch_prefix.h | 8 ++++++++ tests/unit_tests/test_host_pch_prefix.py | 21 +++++++++++++++++++++ 3 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 esphome/core/pch_prefix.h create mode 100644 tests/unit_tests/test_host_pch_prefix.py diff --git a/esphome/components/host/__init__.py b/esphome/components/host/__init__.py index f99b7985cd0..654e1602507 100644 --- a/esphome/components/host/__init__.py +++ b/esphome/components/host/__init__.py @@ -1,4 +1,4 @@ -from esphome.build_helpers.pch import pch_extra_scripts +from esphome.build_helpers.pch import pch_enabled, pch_extra_scripts import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import ( @@ -19,6 +19,9 @@ from .const import KEY_HOST # force import gpio to register pin schema from .gpio import host_pin_to_code # noqa: F401 +# Guarded wrapper: build_src_flags reaches C/assembly edges too +HOST_PCH_PREFIX = "esphome/core/pch_prefix.h" + CODEOWNERS = ["@esphome/core", "@clydebarrow"] AUTO_LOAD = ["network", "preferences"] IS_TARGET_PLATFORM = True @@ -57,14 +60,13 @@ async def to_code(config: ConfigType) -> None: cg.add_platformio_option("lib_ldf_mode", "off") cg.add_platformio_option("lib_compat_mode", "strict") cg.add_platformio_option("extra_scripts", ["pre:ccache.py", *pch_extra_scripts()]) - # Curated prefix for the pch (the script folds these plus defines.h): - # host has no framework force-includes, and the per-TU cost is the STL - # closure behind these core headers. Measured -43% compile CPU. - # macOS system clang cannot load a GCC .gch; the load probe falls back. - cg.add_platformio_option( - "build_src_flags", - "-include esphome/core/application.h -include esphome/core/automation.h", - ) + if pch_enabled(): + # Curated prefix for the pch (the script folds it plus defines.h): + # host has no framework force-includes, and the per-TU cost is the + # STL closure behind the core headers. Measured -43% compile CPU. + # Gated so ESPHOME_PCH_ENABLE=0 restores the strict view; a + # toolchain that cannot load its own pch hits the script's probe. + cg.add_platformio_option("build_src_flags", f"-include {HOST_PCH_PREFIX}") # Called by writer.py diff --git a/esphome/core/pch_prefix.h b/esphome/core/pch_prefix.h new file mode 100644 index 00000000000..07dd998e621 --- /dev/null +++ b/esphome/core/pch_prefix.h @@ -0,0 +1,8 @@ +#pragma once +// Curated precompiled-header prefix for backends that force-include it via +// build_src_flags (the pch script folds it into the .gch). Guarded because +// build_src_flags also reaches C and assembly src edges. +#ifdef __cplusplus +#include "esphome/core/application.h" +#include "esphome/core/automation.h" +#endif diff --git a/tests/unit_tests/test_host_pch_prefix.py b/tests/unit_tests/test_host_pch_prefix.py new file mode 100644 index 00000000000..ff42ce55a85 --- /dev/null +++ b/tests/unit_tests/test_host_pch_prefix.py @@ -0,0 +1,21 @@ +"""The host pch prefix must keep resolving; a rename would silently +collapse the precompiled set to defines.h with strict CI still green.""" + +from pathlib import Path +import re + +from esphome.components.host import HOST_PCH_PREFIX + +REPO = Path(__file__).parents[2] + + +def test_host_pch_prefix_resolves() -> None: + prefix = REPO / HOST_PCH_PREFIX + assert prefix.is_file() + body = prefix.read_text() + includes = re.findall(r'#include "([^"]+)"', body) + assert includes, "prefix wrapper folds nothing" + for name in includes: + assert (REPO / name).is_file(), f"{name} does not resolve" + # The C guard is what keeps build_src_flags safe on C/assembly edges + assert "#ifdef __cplusplus" in body