mirror of
https://github.com/esphome/esphome.git
synced 2026-09-11 15:27:33 +00:00
Merge branch 'esp8266-native-pch' into esp32-idf-pch
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user