mirror of
https://github.com/esphome/esphome.git
synced 2026-09-10 06:48:45 +00:00
Merge branch 'esp32-idf-pch' into platformio-pch-rp2
This commit is contained in:
@@ -195,6 +195,7 @@ def parse_entry(
|
||||
defines: list[str] = []
|
||||
includes: list[str] = []
|
||||
cxx_flags: list[str] = []
|
||||
unresolved_force_includes: list[str] = []
|
||||
|
||||
it = iter(tokens[1:])
|
||||
for tok in it:
|
||||
@@ -207,11 +208,11 @@ def parse_entry(
|
||||
raw = next(it, "")
|
||||
if not raw:
|
||||
_LOGGER.warning("Dropping -include with no argument")
|
||||
elif Path(resolved := _include(raw)).is_file():
|
||||
cxx_flags.extend(("-include", resolved))
|
||||
else:
|
||||
resolved = _include(raw)
|
||||
cxx_flags.extend(
|
||||
("-include", resolved if Path(resolved).is_file() else raw)
|
||||
)
|
||||
unresolved_force_includes.append(raw)
|
||||
cxx_flags.extend(("-include", raw))
|
||||
elif tok.startswith("-D"):
|
||||
# ``.strip()`` handles tokens like ``-D CONFIGURED=1`` (a single
|
||||
# quoted arg with a space after -D) that some flags arrive as.
|
||||
@@ -230,6 +231,15 @@ def parse_entry(
|
||||
pass # input/output files
|
||||
else:
|
||||
cxx_flags.append(tok)
|
||||
for raw in unresolved_force_includes:
|
||||
# A deleted build artifact (clean_build removes esphome_pch.h) would
|
||||
# otherwise surface only as an opaque downstream tooling error
|
||||
if not any((Path(inc) / raw).is_file() for inc in includes):
|
||||
_LOGGER.warning(
|
||||
"-include %s found neither next to the compile nor on the "
|
||||
"include path; cached idedata may not resolve it",
|
||||
raw,
|
||||
)
|
||||
return cxx_path, defines, includes, cxx_flags
|
||||
|
||||
|
||||
|
||||
@@ -82,6 +82,14 @@ def ccache_pch_env() -> dict[str, str]:
|
||||
export these process-wide; only time_macros affects non-pch TUs."""
|
||||
if not pch_enabled():
|
||||
return {}
|
||||
user_sloppiness = os.environ.get("CCACHE_SLOPPINESS")
|
||||
if user_sloppiness is not None and "pch_defines" not in user_sloppiness:
|
||||
# EXTSUM without pch_defines makes ccache silently decline every
|
||||
# pch-consuming compile
|
||||
_LOGGER.warning(
|
||||
"CCACHE_SLOPPINESS lacks pch_defines; ccache will not cache "
|
||||
"compiles that use the precompiled header"
|
||||
)
|
||||
return {k: v for k, v in _CCACHE_PCH_ENV.items() if k not in os.environ}
|
||||
|
||||
|
||||
@@ -121,10 +129,14 @@ def _include_closure(src_dir: Path, roots: Iterable[str]) -> dict[str, bytes]:
|
||||
try:
|
||||
data = (src_dir / rel).read_bytes()
|
||||
except OSError as err:
|
||||
# Hash a marker so an unreadable header invalidates instead of
|
||||
# silently vanishing from the digest
|
||||
# mtime/size keep a changed-but-unreadable header shifting the
|
||||
# digest without device paths in it
|
||||
_LOGGER.warning("Could not read %s for the pch checksum: %s", rel, err)
|
||||
data = b"<unreadable>"
|
||||
try:
|
||||
st = (src_dir / rel).stat()
|
||||
data = f"<unreadable:{st.st_mtime_ns}:{st.st_size}>".encode()
|
||||
except OSError:
|
||||
data = b"<unreadable>"
|
||||
seen[rel] = data
|
||||
parent = posixpath.dirname(rel)
|
||||
stack.extend((inc.decode(), parent) for inc in _INCLUDE_RE.findall(data))
|
||||
|
||||
@@ -46,7 +46,11 @@ def _include_closure(src_dir: Path, roots: list) -> dict:
|
||||
data = (src_dir / rel).read_bytes()
|
||||
except OSError as err:
|
||||
print(f"ESPHome: could not read {rel} for the pch checksum: {err}")
|
||||
data = b"<unreadable>"
|
||||
try:
|
||||
st = (src_dir / rel).stat()
|
||||
data = f"<unreadable:{st.st_mtime_ns}:{st.st_size}>".encode()
|
||||
except OSError:
|
||||
data = b"<unreadable>"
|
||||
seen[rel] = data
|
||||
parent = posixpath.dirname(rel)
|
||||
stack.extend((inc.decode(), parent) for inc in _INCLUDE_RE.findall(data))
|
||||
@@ -262,6 +266,8 @@ 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.
|
||||
# The relative name also reaches "pio run -t idedata" output; external
|
||||
# consumers replaying cxx_flags must run from the project dir.
|
||||
projenv.Prepend(CXXFLAGS=["-Winvalid-pch", "-include", header.name]) # noqa: F821
|
||||
print("ESPHome: Compiling with precompiled header")
|
||||
|
||||
|
||||
@@ -117,6 +117,27 @@ def test_parse_entry_keeps_search_chain_force_include(tmp_path: Path) -> None:
|
||||
assert cxx_flags[cxx_flags.index("-include") + 1] == "Arduino.h"
|
||||
|
||||
|
||||
def test_parse_entry_warns_on_vanished_force_include(
|
||||
tmp_path: Path, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""A build-dir force-include deleted by clean_build must leave a trail;
|
||||
a name resolvable via the -I chain must not warn."""
|
||||
inc = tmp_path / "inc"
|
||||
inc.mkdir()
|
||||
(inc / "Arduino.h").write_text("")
|
||||
entry = _entry(
|
||||
str(tmp_path),
|
||||
f"{tmp_path}/src/esphome/x.cpp",
|
||||
f"g++ -I{inc} -include Arduino.h -include esphome_pch.h -c x.cpp",
|
||||
)
|
||||
|
||||
_, _, _, cxx_flags = idedata.parse_entry(entry)
|
||||
|
||||
assert "Arduino.h" in cxx_flags
|
||||
assert "esphome_pch.h" in caplog.text
|
||||
assert "Arduino.h" not in caplog.text
|
||||
|
||||
|
||||
def test_parse_entry_drops_trailing_force_include(
|
||||
tmp_path: Path, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
|
||||
@@ -45,11 +45,22 @@ def test_ccache_pch_env_disabled() -> None:
|
||||
assert pch.ccache_pch_env() == {}
|
||||
|
||||
|
||||
def test_ccache_pch_env_respects_user_values() -> None:
|
||||
def test_ccache_pch_env_respects_user_values(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""A user CCACHE_SLOPPINESS wins, but one without pch_defines silently
|
||||
stops ccache from caching pch consumers, so it must warn."""
|
||||
with patch.dict(os.environ, {"CCACHE_SLOPPINESS": "locale"}, clear=True):
|
||||
env = pch.ccache_pch_env()
|
||||
assert "CCACHE_SLOPPINESS" not in env
|
||||
assert env["CCACHE_PCH_EXTSUM"] == "true"
|
||||
assert "lacks pch_defines" in caplog.text
|
||||
caplog.clear()
|
||||
with patch.dict(
|
||||
os.environ, {"CCACHE_SLOPPINESS": "pch_defines,locale"}, clear=True
|
||||
):
|
||||
pch.ccache_pch_env()
|
||||
assert "lacks pch_defines" not in caplog.text
|
||||
|
||||
|
||||
def test_pch_header_text_preserves_order() -> None:
|
||||
@@ -118,7 +129,9 @@ def test_include_closure_marks_unreadable(
|
||||
closure = pch._include_closure(tmp_path, ["a.h"])
|
||||
finally:
|
||||
locked.chmod(0o644)
|
||||
assert closure["locked.h"] == b"<unreadable>"
|
||||
# stat still works, so the marker varies with mtime/size and a later
|
||||
# edit to the unreadable file still shifts the digest
|
||||
assert closure["locked.h"].startswith(b"<unreadable:")
|
||||
assert "Could not read locked.h" in caplog.text
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user