Match the pch source TU by resolved path so symlinked build dirs keep the pch

This commit is contained in:
J. Nick Koston
2026-08-27 12:31:53 -05:00
parent 96beeed2c2
commit c6b1a41e76
2 changed files with 41 additions and 3 deletions
+16 -3
View File
@@ -212,6 +212,18 @@ _PCH_STRIP_FLAGS_WITH_ARG = frozenset({"-o", "-c", "-MT", "-MF", "-MQ"})
_PCH_STRIP_FLAGS = frozenset({"-MD", "-MMD", "-MP", "-MM", "-M"})
def _db_source_path(entry: dict, build_dir: Path) -> Path:
"""A compile database entry's source file as a resolved absolute path."""
path = Path(entry["file"])
if not path.is_absolute():
directory = entry.get("directory")
base = (
Path(directory) if isinstance(directory, str) and directory else build_dir
)
path = base / path
return path.resolve()
def pch_compile_command(
build_dir: Path, header: Path, gch: Path
) -> tuple[list[str], Path] | None:
@@ -232,16 +244,17 @@ def pch_compile_command(
if not isinstance(entries, list):
_LOGGER.warning("Malformed compile database, skipping pch")
return None
# Windows compile DBs use backslashes; normalize both sides
src_prefix = str(CORE.relative_src_path()).replace("\\", "/")
# CMake may spell paths through a symlink differently than CORE does
# (macOS /tmp vs /private/tmp), so compare resolved paths
src_root = Path(CORE.relative_src_path()).resolve()
entry = next(
(
e
for e in entries
if isinstance(e, dict)
and isinstance(e.get("file"), str)
and e["file"].replace("\\", "/").startswith(src_prefix)
and e["file"].endswith(CXX_SOURCE_SUFFIXES)
and _db_source_path(e, build_dir).is_relative_to(src_root)
),
None,
)
+25
View File
@@ -657,6 +657,31 @@ def test_pch_compile_command_variants(tmp_path: Path) -> None:
assert cmd_dir == build
@pytest.mark.skipif(os.name == "nt", reason="symlinks need privileges on Windows")
def test_pch_compile_command_matches_src_through_symlink(tmp_path: Path) -> None:
"""Find the src TU when CMake spells paths through a different symlink (macOS /tmp)."""
from esphome.build_helpers.pch import pch_compile_command
real = tmp_path / "real"
(real / "src" / "esphome").mkdir(parents=True)
link = tmp_path / "link"
link.symlink_to(real, target_is_directory=True)
CORE.build_path = str(real)
build = real / "build"
build.mkdir()
header = build / "esphome_pch.h"
gch = build / "esphome_pch.h.gch"
src_file = str(link / "src" / "esphome" / "a.cpp")
(build / "compile_commands.json").write_text(
json.dumps([{"command": f"g++ -DX=1 -o a.obj -c {src_file}", "file": src_file}])
)
cmd, cmd_dir = pch_compile_command(build, header, gch)
assert cmd[:2] == ["g++", "-DX=1"]
assert cmd_dir == build
def test_pch_compile_command_rejects_unusable_entries(tmp_path: Path) -> None:
"""Malformed DB shapes and command-less entries skip cleanly instead of
producing a compiler-less argv retried every build."""