Merge branch 'platformio-pch-rp2' into platformio-pch-libretiny

This commit is contained in:
J. Nick Koston
2026-08-25 15:37:16 -05:00
8 changed files with 49 additions and 17 deletions
+1 -1
View File
@@ -1254,7 +1254,7 @@ def write_project(paths: InstalledPaths, ccache: str | None) -> bool:
# Relative -include (resolved from the ninja cwd, where the header
# lives): an absolute path would put the per-device build path on
# every compile command and defeat cross-device ccache sharing
cxx_parts = src_other + [f"-include {PCH_HEADER_NAME}"]
cxx_parts = src_other + [f"-Winvalid-pch -include {PCH_HEADER_NAME}"]
lines.append(f"srccxxflags = {' '.join(cxx_parts)}")
src_cxx_flags = "$srccxxflags"
src_cxx_implicit = gch
+1
View File
@@ -337,6 +337,7 @@ def _pch_cmake() -> str:
# a .gch drop out of the TU depfiles, and prepare_pch() touches the
# header whenever it rebuilds the .gch so consumers recompile.
target_compile_options(${{COMPONENT_LIB}} PRIVATE
"$<$<COMPILE_LANGUAGE:CXX>:-Winvalid-pch>"
"$<$<COMPILE_LANGUAGE:CXX>:-include>"
"$<$<COMPILE_LANGUAGE:CXX>:{PCH_HEADER_NAME}>"
)
+5 -1
View File
@@ -97,4 +97,8 @@ def effective_ccache_basedir() -> str:
wins, else the resolved build path (matching ccache_defaults_env)."""
from esphome.core import CORE
return os.environ.get("CCACHE_BASEDIR") or str(Path(CORE.build_path).resolve())
raw = os.environ.get("CCACHE_BASEDIR")
if raw is not None:
# An explicitly empty value disables ccache's rewriting; mirror it
return raw
return str(Path(CORE.build_path).resolve())
+3 -1
View File
@@ -2,7 +2,9 @@
Safe by construction when the prefix header mirrors what the TUs already
include first (ESP8266); a backend may instead inject a curated set of
self-contained core headers (ESP-IDF).
self-contained core headers (ESP-IDF). User sources from ``esphome:
includes:`` also receive the prefix, so they now see defines.h (and
Arduino.h on Arduino platforms) even when they did not include it.
"""
from __future__ import annotations
+23 -11
View File
@@ -50,11 +50,13 @@ def _include_closure(src_dir: Path, roots: list) -> dict:
def _shell_arg(element) -> str:
"""One compiler argv from one SCons element, matching the real spawn:
SCons whole-quotes spaced elements, the shell unquotes the rest."""
SCons whole-quotes spaced elements, the shell unquotes the rest. On
Windows there is no POSIX shell pass and shlex would eat path
backslashes."""
arg = str(element)
if " " in arg:
if " " in arg or os.name == "nt":
return arg.replace('\\"', '"')
return shlex.split(arg)[0] if arg else arg
return shlex.split(arg)[0] if arg.strip() else arg
def _setup_pch() -> None:
@@ -104,10 +106,13 @@ def _setup_pch() -> None:
for package in sorted(platform.packages):
try:
version = platform.get_package_version(package)
except Exception as err: # noqa: BLE001 -- absent optional package
# Folded into the digest so an unexpected lookup failure still
# invalidates instead of hashing like a fixed absence
version = f"error:{type(err).__name__}"
except KeyError:
version = None # absent optional package
except Exception as err: # noqa: BLE001
# Without trustworthy package identity a stale .gch could be
# reused across upgrades; skip the pch instead
print(f"ESPHome: skipping precompiled header: {err}")
return
digest.update(f"{package}={version}".encode())
digest.update(b"\0")
closure = _include_closure(src_dir, [*include_headers, _CORE_HEADER])
@@ -133,9 +138,13 @@ def _setup_pch() -> None:
and not inc_dir.is_relative_to(src_dir)
):
continue
for local in sorted(inc_dir.rglob("*.h")):
for local in sorted(p for p in inc_dir.rglob("*") if p.is_file()):
try:
data = local.read_bytes()
except OSError:
data = b"<unreadable>"
digest.update(str(local.relative_to(proj_dir)).encode())
digest.update(local.read_bytes())
digest.update(data)
digest.update(b"\0")
checksum = digest.hexdigest()
@@ -215,8 +224,11 @@ def _setup_pch() -> None:
# Prepended so it is processed before the build_src_flags -include
# entries: GCC only uses a .gch while no other tokens have been seen.
projenv.Prepend(CXXFLAGS=["-include", header.name]) # noqa: F821
projenv.Prepend(CXXFLAGS=["-Winvalid-pch", "-include", header.name]) # noqa: F821
print("ESPHome: Compiling with precompiled header")
_setup_pch()
try:
_setup_pch()
except Exception as err: # noqa: BLE001 -- a speedup must never break the build
print(f"ESPHome: precompiled header setup failed; compiling without it: {err}")
+11
View File
@@ -609,6 +609,17 @@ def clean_build(clear_pio_cache: bool = True, *, full: bool = False):
if idf_path.is_dir():
_LOGGER.info("Deleting %s", idf_path)
rmtree(idf_path)
# The PlatformIO pch artifacts live at the project root so the
# relative -include resolves; a partial clean must drop them too
for name in (
"esphome_pch.h",
"esphome_pch.h.gch",
"esphome_pch.h.gch.sum",
"esphome_pch.h.gch.failed",
):
pch_path = CORE.relative_build_path(name)
if pch_path.is_file():
pch_path.unlink()
# The idedata caches are derived from the build but live under the data
# dir, not the build path, so they must be removed separately in both
@@ -1766,7 +1766,7 @@ def test_write_project_pch_no_device_path_poison(tmp_path: Path) -> None:
CORE.build_path = tmp_path / name
_set_flags("-DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH")
content = _write_ninja(paths, ccache="/usr/bin/ccache")
assert "srccxxflags = -include esphome_pch.h" in content
assert "srccxxflags = -Winvalid-pch -include esphome_pch.h" in content
sums.append(
(CORE.relative_pioenvs_path(name) / "esphome_pch.h.gch.sum").read_text()
)
@@ -56,7 +56,9 @@ class _FakeSConsEnv(dict):
def _fake_cxx(tmp_path: Path, fail: bool = False) -> Path:
"""A compiler stand-in that records its argv and writes the -o target."""
cxx = tmp_path / "fake-gxx"
body = 'printf -- ---call---\\\\n >> "$0.argv"; printf \'%s\\n\' "$@" >> "$0.argv"\n'
body = (
'printf -- ---call---\\\\n >> "$0.argv"; printf \'%s\\n\' "$@" >> "$0.argv"\n'
)
if fail:
body += "echo boom >&2\nexit 1\n"
else:
@@ -95,7 +97,7 @@ def test_pch_script_builds_and_prepends_relative_include(tmp_path: Path) -> None
assert (proj / "esphome_pch.h.gch").is_file()
assert len((proj / "esphome_pch.h.gch.sum").read_text().strip()) == 64
# Relative include: an absolute path would poison ccache keys
assert scons_env.prepended == ["-include", "esphome_pch.h"]
assert scons_env.prepended == ["-Winvalid-pch", "-include", "esphome_pch.h"]
# ccache settings land on the SCons ENV only, never os.environ
assert scons_env["ENV"]["CCACHE_SLOPPINESS"] == "pch_defines,time_macros"
assert scons_env["ENV"]["CCACHE_PCH_EXTSUM"] == "true"