Merge branch 'esp32-idf-pch' into platformio-pch-rp2

This commit is contained in:
J. Nick Koston
2026-08-25 23:03:13 -05:00
5 changed files with 30 additions and 7 deletions
+1 -1
View File
@@ -1246,7 +1246,7 @@ def write_project(paths: InstalledPaths, ccache: str | None) -> bool:
flags_id,
),
)
except OSError as err:
except (OSError, UnicodeError) as err:
# Identity unknown: a stale cache entry must never be served
_LOGGER.warning(
"Could not establish the pch identity; compiling without it: %s", err
+2 -2
View File
@@ -170,7 +170,7 @@ def pch_checksum(
digest.update(closure[name])
digest.update(b"\0")
for item in extra:
digest.update(item.encode())
digest.update(item.encode(errors="surrogateescape"))
digest.update(b"\0")
return digest.hexdigest()
@@ -317,7 +317,7 @@ def prepare_pch(
cmd_id,
),
)
except OSError as err:
except (OSError, UnicodeError) as err:
# Identity unknown: a stale cache entry must never be served
_LOGGER.warning(
"Could not establish the pch identity; compiling without it: %s", err
+9 -4
View File
@@ -158,20 +158,25 @@ def _setup_pch() -> None:
# -include entries, so an unguarded user header folded here would be
# included twice. An unfolded header simply stays consumer-only and
# ccache hashes it directly off the command line.
folded = [name for name in include_headers if (src_dir / name).is_file()]
folded = [
name
for name in include_headers
# An absolute name would sneak past src_dir /: keep it consumer-only
if not Path(name).is_absolute() and (src_dir / name).is_file()
]
if unfolded := [n for n in include_headers if n not in folded]:
print(f"ESPHome: not precompiling non-src force-includes: {unfolded}")
content = "".join(f'#include "{name}"\n' for name in (*folded, _CORE_HEADER))
digest = hashlib.sha256()
digest.update(content.encode())
digest.update(cxx.encode())
digest.update(content.encode(errors="surrogateescape"))
digest.update(cxx.encode(errors="surrogateescape"))
# Mirror CCACHE_BASEDIR: strip the per-device build path so identical
# configs produce identical .sum files and share cache entries
flags_id = " ".join(flags)
if basedir := os.environ.get("CCACHE_BASEDIR"):
flags_id = flags_id.replace(basedir, "")
digest.update(flags_id.encode())
digest.update(flags_id.encode(errors="surrogateescape"))
# GCC never validates a .gch against its source headers, and PlatformIO
# package paths carry no version, so a package bump must invalidate here
platform = env.PioPlatform() # noqa: F821
@@ -175,3 +175,9 @@ def test_include_closure_survives_non_utf8_include_name(tmp_path: Path) -> None:
(tmp_path / "b.h").write_text("")
closure = pch._include_closure(tmp_path, ["a.h"])
assert set(closure) == {"a.h", "b.h"}
def test_pch_checksum_survives_surrogate_extra(tmp_path: Path) -> None:
"""Install paths from non-UTF-8 filesystems carry surrogates; hashing
them must not raise past the caller's identity-unknown guard."""
assert pch.pch_checksum(tmp_path, [], ["/opt/bad\udcff/framework"])
@@ -175,6 +175,18 @@ def test_pch_script_preserves_spaced_flag_elements(tmp_path: Path) -> None:
assert pch.splitlines()[0] == '#include "other.h"'
def test_pch_script_leaves_absolute_force_includes_unfolded(
tmp_path: Path,
) -> None:
"""An absolute -include resolves through src_dir / name; it must still
stay consumer-only or the host path enters the .sum."""
outside = tmp_path / "outside.h"
outside.write_text("")
_run_script(tmp_path, flags=["-DX=1", "-include", str(outside)])
pch = (tmp_path / "dev" / "esphome_pch.h").read_text()
assert "outside.h" not in pch
def test_pch_script_leaves_non_src_force_includes_unfolded(
tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None: