mirror of
https://github.com/esphome/esphome.git
synced 2026-10-01 17:00:21 +00:00
[esp8266] Precompile the prefix header on the native toolchain (#19821)
This commit is contained in:
@@ -364,7 +364,87 @@ def test_write_project_link_line_and_exclusions(tmp_path: Path) -> None:
|
||||
line for line in content.splitlines() if line.startswith(" flags = ")
|
||||
]
|
||||
assert flags_lines
|
||||
assert all(line == " flags = $srcflags" for line in flags_lines)
|
||||
# C++ src edges consume the precompiled header; C/assembly keep srcflags
|
||||
assert set(flags_lines) == {" flags = $srcflags", " flags = $srccxxflags"}
|
||||
|
||||
|
||||
def test_write_project_pch(tmp_path: Path) -> None:
|
||||
paths = _make_framework(tmp_path)
|
||||
_set_flags("-DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH")
|
||||
content = _write_ninja(paths, ccache="/usr/bin/ccache")
|
||||
build_dir = CORE.relative_pioenvs_path(CORE.name)
|
||||
assert "rule pch" in content
|
||||
# Compiled from the include list, and again when the checksum changes
|
||||
gch_edge = next(
|
||||
line
|
||||
for line in content.splitlines()
|
||||
if line.startswith("build esphome_pch.h.gch: pch ")
|
||||
)
|
||||
assert gch_edge.endswith("esphome_pch_src.h | esphome_pch.h.gch.sum")
|
||||
for line in content.splitlines():
|
||||
# C++ edges wait on the .gch; the C edge must not reference it
|
||||
if line.startswith("build obj/src/main.cpp.o:"):
|
||||
assert line.endswith("| esphome_pch.h.gch")
|
||||
if line.startswith("build obj/src/esphome/vendor.c.o:"):
|
||||
assert "esphome_pch" not in line
|
||||
assert (build_dir / "esphome_pch_src.h").read_text().splitlines() == [
|
||||
'#include "esphome/components/esp8266/throw_stubs.h"',
|
||||
'#include "esphome/core/pch_prefix.h"',
|
||||
]
|
||||
assert "#error" in (build_dir / "esphome_pch.h").read_text()
|
||||
assert (build_dir / "esphome_pch.h.gch.sum").read_text().strip()
|
||||
|
||||
|
||||
def test_write_project_pch_folds_joined_src_force_include(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""-includefoo.h in build_src_flags must fold into the pch like the
|
||||
separated spelling, not precede and defeat it."""
|
||||
paths = _make_framework(tmp_path)
|
||||
_set_flags("-DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH")
|
||||
CORE.platformio_options["build_src_flags"] = "-includeesphome/core/defines.h"
|
||||
content = _write_ninja(paths, ccache="/usr/bin/ccache")
|
||||
assert "build esphome_pch.h.gch: pch" in content
|
||||
assert "srccxxflags" in content
|
||||
assert "-includeesphome" not in content
|
||||
|
||||
|
||||
@pytest.mark.parametrize("flag", ["-includefoo.h", "--include=foo.h"])
|
||||
def test_write_project_pch_skipped_for_other_force_include_spellings(
|
||||
tmp_path: Path, caplog: pytest.LogCaptureFixture, flag: str
|
||||
) -> None:
|
||||
paths = _make_framework(tmp_path)
|
||||
_set_flags("-DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH", flag)
|
||||
content = _write_ninja(paths, ccache="/usr/bin/ccache")
|
||||
assert "esphome_pch" not in content
|
||||
assert "prevents the precompiled header" in caplog.text
|
||||
|
||||
|
||||
def test_write_project_pch_skipped_when_user_force_include_precedes(
|
||||
tmp_path: Path, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""A -include in build_flags lands ahead of the pch include, so GCC
|
||||
would never load the .gch; skip it and say so."""
|
||||
paths = _make_framework(tmp_path)
|
||||
_set_flags(
|
||||
"-DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH", "-include foo.h"
|
||||
)
|
||||
content = _write_ninja(paths, ccache="/usr/bin/ccache")
|
||||
assert "esphome_pch" not in content
|
||||
assert "srccxxflags" not in content
|
||||
assert "prevents the precompiled header" in caplog.text
|
||||
|
||||
|
||||
def test_write_project_pch_disabled(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
monkeypatch.setenv("ESPHOME_PCH_ENABLE", "0")
|
||||
paths = _make_framework(tmp_path)
|
||||
_set_flags("-DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH")
|
||||
content = _write_ninja(paths)
|
||||
assert "esphome_pch" not in content
|
||||
assert "srccxxflags" not in content
|
||||
assert " flags = $srcflags" in content
|
||||
|
||||
|
||||
def test_write_project_scanf_float_and_waveform_kept(tmp_path: Path) -> None:
|
||||
@@ -1716,3 +1796,37 @@ def test_write_project_rejects_spaced_ldscript_override(tmp_path: Path) -> None:
|
||||
CORE.relative_src_path().mkdir(parents=True, exist_ok=True)
|
||||
with pytest.raises(EsphomeError, match="Invalid flash linker script name"):
|
||||
arduino8266.write_project(paths, None)
|
||||
|
||||
|
||||
def test_write_project_pch_no_device_path_poison(tmp_path: Path) -> None:
|
||||
"""Regression: the -include stays relative and the .sum carries no
|
||||
per-device path, or cross-device ccache sharing breaks."""
|
||||
paths = _make_framework(tmp_path / "shared")
|
||||
sums = []
|
||||
for name in ("dev_a", "dev_b"):
|
||||
CORE.name = name
|
||||
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 = -Winvalid-pch -Werror=invalid-pch "
|
||||
"-include esphome_pch.h" in content
|
||||
)
|
||||
sums.append(
|
||||
(CORE.relative_pioenvs_path(name) / "esphome_pch.h.gch.sum").read_text()
|
||||
)
|
||||
assert sums[0] == sums[1]
|
||||
|
||||
|
||||
def test_write_project_pch_sum_tracks_src_flags(tmp_path: Path) -> None:
|
||||
"""The header compiles with the src flags too, so they are part of what
|
||||
ccache is told about it."""
|
||||
paths = _make_framework(tmp_path)
|
||||
_set_flags("-DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH")
|
||||
sum_path = CORE.relative_pioenvs_path(CORE.name) / "esphome_pch.h.gch.sum"
|
||||
sums = []
|
||||
for value in ("1", "2"):
|
||||
CORE.platformio_options["build_src_flags"] = f"-DSRC_ONLY={value}"
|
||||
_write_ninja(paths)
|
||||
sums.append(sum_path.read_text())
|
||||
assert sums[0] != sums[1]
|
||||
|
||||
@@ -10,6 +10,7 @@ from unittest.mock import patch
|
||||
import pytest
|
||||
|
||||
from esphome.build_helpers import ccache
|
||||
from esphome.core import CORE
|
||||
|
||||
|
||||
def test_resolve_opt_out() -> None:
|
||||
@@ -122,6 +123,20 @@ def test_parse_enable_env_spelling_tables(
|
||||
assert ccache.parse_enable_env("ESPHOME_CCACHE_ENABLE") is expected
|
||||
|
||||
|
||||
def test_effective_ccache_basedir_prefers_user_value(tmp_path: Path) -> None:
|
||||
CORE.build_path = tmp_path
|
||||
# Drive-qualified on Windows: "/custom/base" is not absolute there
|
||||
base = "C:\\custom\\base" if os.name == "nt" else "/custom/base"
|
||||
with patch.dict(os.environ, {"CCACHE_BASEDIR": base}, clear=True):
|
||||
assert ccache.effective_ccache_basedir() == base
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
assert ccache.effective_ccache_basedir() == str(tmp_path.resolve())
|
||||
# Degenerate values would strip substrings ccache never rewrites
|
||||
for bad in ("", "/", "a/b"):
|
||||
with patch.dict(os.environ, {"CCACHE_BASEDIR": bad}, clear=True):
|
||||
assert ccache.effective_ccache_basedir() == str(tmp_path.resolve())
|
||||
|
||||
|
||||
def test_resolve_absolute_ccache_path(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
@@ -165,3 +180,18 @@ def test_ccache_defaults_env_escapes_a_dollar(
|
||||
env = ccache.ccache_defaults_env(tmp_path / "c$d")
|
||||
assert env["CCACHE_DIR"].endswith("c$$d")
|
||||
assert env["CCACHE_BASEDIR"].endswith("a$$b")
|
||||
|
||||
|
||||
def _pch_ccache_env(tmp_path: Path, environ: dict[str, str]) -> dict[str, str]:
|
||||
CORE.build_path = tmp_path / "build"
|
||||
spec = ("ESPHOME_TEST_PREFIX", "test")
|
||||
environ = {"ESPHOME_TEST_PREFIX": str(tmp_path / "cache"), **environ}
|
||||
with patch.dict(os.environ, environ, clear=True):
|
||||
return ccache.ccache_env("/usr/bin/ccache", spec)
|
||||
|
||||
|
||||
def test_ccache_env_includes_pch_settings(tmp_path: Path) -> None:
|
||||
"""A native build exports the ccache settings the pch needs."""
|
||||
env = _pch_ccache_env(tmp_path, {})
|
||||
assert env["CCACHE_SLOPPINESS"] == "pch_defines,time_macros"
|
||||
assert env["CCACHE_PCH_EXTSUM"] == "true"
|
||||
|
||||
@@ -57,6 +57,11 @@ def test_ccache_pch_env_disabled() -> None:
|
||||
assert pch.ccache_pch_env() == {}
|
||||
|
||||
|
||||
def test_pch_header_text_preserves_order() -> None:
|
||||
text = pch.pch_header_text(["b.h", "a.h"])
|
||||
assert text == '#include "b.h"\n#include "a.h"\n'
|
||||
|
||||
|
||||
def test_include_closure_resolves_relative_and_root(tmp_path: Path) -> None:
|
||||
"""Sibling includes resolve against the includer's directory first,
|
||||
full paths against the src root; unresolvable names end the walk."""
|
||||
|
||||
Reference in New Issue
Block a user