mirror of
https://github.com/esphome/esphome.git
synced 2026-10-07 11:26:39 +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] = []
|
defines: list[str] = []
|
||||||
includes: list[str] = []
|
includes: list[str] = []
|
||||||
cxx_flags: list[str] = []
|
cxx_flags: list[str] = []
|
||||||
|
unresolved_force_includes: list[str] = []
|
||||||
|
|
||||||
it = iter(tokens[1:])
|
it = iter(tokens[1:])
|
||||||
for tok in it:
|
for tok in it:
|
||||||
@@ -207,11 +208,11 @@ def parse_entry(
|
|||||||
raw = next(it, "")
|
raw = next(it, "")
|
||||||
if not raw:
|
if not raw:
|
||||||
_LOGGER.warning("Dropping -include with no argument")
|
_LOGGER.warning("Dropping -include with no argument")
|
||||||
|
elif Path(resolved := _include(raw)).is_file():
|
||||||
|
cxx_flags.extend(("-include", resolved))
|
||||||
else:
|
else:
|
||||||
resolved = _include(raw)
|
unresolved_force_includes.append(raw)
|
||||||
cxx_flags.extend(
|
cxx_flags.extend(("-include", raw))
|
||||||
("-include", resolved if Path(resolved).is_file() else raw)
|
|
||||||
)
|
|
||||||
elif tok.startswith("-D"):
|
elif tok.startswith("-D"):
|
||||||
# ``.strip()`` handles tokens like ``-D CONFIGURED=1`` (a single
|
# ``.strip()`` handles tokens like ``-D CONFIGURED=1`` (a single
|
||||||
# quoted arg with a space after -D) that some flags arrive as.
|
# quoted arg with a space after -D) that some flags arrive as.
|
||||||
@@ -230,6 +231,15 @@ def parse_entry(
|
|||||||
pass # input/output files
|
pass # input/output files
|
||||||
else:
|
else:
|
||||||
cxx_flags.append(tok)
|
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
|
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."""
|
export these process-wide; only time_macros affects non-pch TUs."""
|
||||||
if not pch_enabled():
|
if not pch_enabled():
|
||||||
return {}
|
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}
|
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:
|
try:
|
||||||
data = (src_dir / rel).read_bytes()
|
data = (src_dir / rel).read_bytes()
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
# Hash a marker so an unreadable header invalidates instead of
|
# mtime/size keep a changed-but-unreadable header shifting the
|
||||||
# silently vanishing from the digest
|
# digest without device paths in it
|
||||||
_LOGGER.warning("Could not read %s for the pch checksum: %s", rel, err)
|
_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
|
seen[rel] = data
|
||||||
parent = posixpath.dirname(rel)
|
parent = posixpath.dirname(rel)
|
||||||
stack.extend((inc.decode(), parent) for inc in _INCLUDE_RE.findall(data))
|
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()
|
data = (src_dir / rel).read_bytes()
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
print(f"ESPHome: could not read {rel} for the pch checksum: {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
|
seen[rel] = data
|
||||||
parent = posixpath.dirname(rel)
|
parent = posixpath.dirname(rel)
|
||||||
stack.extend((inc.decode(), parent) for inc in _INCLUDE_RE.findall(data))
|
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
|
# Prepended so it is processed before the build_src_flags -include
|
||||||
# entries: GCC only uses a .gch while no other tokens have been seen.
|
# 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
|
projenv.Prepend(CXXFLAGS=["-Winvalid-pch", "-include", header.name]) # noqa: F821
|
||||||
print("ESPHome: Compiling with precompiled header")
|
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"
|
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(
|
def test_parse_entry_drops_trailing_force_include(
|
||||||
tmp_path: Path, caplog: pytest.LogCaptureFixture
|
tmp_path: Path, caplog: pytest.LogCaptureFixture
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
@@ -45,11 +45,22 @@ def test_ccache_pch_env_disabled() -> None:
|
|||||||
assert pch.ccache_pch_env() == {}
|
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):
|
with patch.dict(os.environ, {"CCACHE_SLOPPINESS": "locale"}, clear=True):
|
||||||
env = pch.ccache_pch_env()
|
env = pch.ccache_pch_env()
|
||||||
assert "CCACHE_SLOPPINESS" not in env
|
assert "CCACHE_SLOPPINESS" not in env
|
||||||
assert env["CCACHE_PCH_EXTSUM"] == "true"
|
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:
|
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"])
|
closure = pch._include_closure(tmp_path, ["a.h"])
|
||||||
finally:
|
finally:
|
||||||
locked.chmod(0o644)
|
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
|
assert "Could not read locked.h" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user