mirror of
https://github.com/esphome/esphome.git
synced 2026-08-26 08:06:21 +00:00
Merge branch 'esp8266-native-library-backend' into esp8266-native-build-spec
This commit is contained in:
@@ -161,6 +161,10 @@ def parse_entry(
|
||||
raw = os.path.normpath(directory / raw)
|
||||
return raw.replace("\\", "/")
|
||||
|
||||
if not tokens:
|
||||
# _split_command("") is [] by design; fail like _pick_entry does
|
||||
# instead of an IndexError traceback
|
||||
raise ValueError(f"empty compile command for {entry.get('file')}")
|
||||
# A launcher-wrapped command ("ccache g++ ...") names the compiler second
|
||||
if launcher is not None and tokens[0] == launcher:
|
||||
tokens = tokens[1:]
|
||||
@@ -290,6 +294,19 @@ def load_or_build_idedata(
|
||||
return data
|
||||
|
||||
|
||||
def reject_launcher_compiler(cxx_path: str) -> None:
|
||||
"""Reject a compile DB that names a launcher (ccache) as the compiler.
|
||||
|
||||
Reject before the toolchain probe, which would fail opaquely on a
|
||||
launcher; the unusable compile DB must never be cached or consumed.
|
||||
"""
|
||||
if _is_launcher(cxx_path):
|
||||
raise EsphomeError(
|
||||
f"compile_commands.json names the launcher {cxx_path} as the "
|
||||
"compiler; the compile database is unusable"
|
||||
)
|
||||
|
||||
|
||||
def idedata_from_build(compile_commands: Path, launcher: str | None = None) -> dict:
|
||||
"""Parse compile_commands.json into the idedata fields consumers expect.
|
||||
|
||||
@@ -304,17 +321,12 @@ def idedata_from_build(compile_commands: Path, launcher: str | None = None) -> d
|
||||
|
||||
representative = _pick_entry(entries)
|
||||
cxx_path, defines, rep_includes, cxx_flags = parse_entry(representative, launcher)
|
||||
if _is_launcher(cxx_path):
|
||||
# Reject before the toolchain probe, which would fail opaquely on
|
||||
# a launcher; never cache the unusable compile DB
|
||||
raise EsphomeError(
|
||||
f"compile_commands.json names the launcher {cxx_path} as the "
|
||||
"compiler; the compile database is unusable"
|
||||
)
|
||||
reject_launcher_compiler(cxx_path)
|
||||
|
||||
# Seed with the representative's includes so it is not parsed twice
|
||||
has_esphome_tu = _is_esphome_src(representative["file"])
|
||||
build_includes: dict[str, None] = dict.fromkeys(
|
||||
rep_includes if _is_esphome_src(representative["file"]) else ()
|
||||
rep_includes if has_esphome_tu else ()
|
||||
)
|
||||
|
||||
def _shape(entry: dict) -> str:
|
||||
@@ -331,18 +343,21 @@ def idedata_from_build(compile_commands: Path, launcher: str | None = None) -> d
|
||||
for entry in entries:
|
||||
if entry is representative or not _is_esphome_src(entry["file"]):
|
||||
continue
|
||||
has_esphome_tu = True
|
||||
if (shape := _shape(entry)) in seen_shapes:
|
||||
continue
|
||||
seen_shapes.add(shape)
|
||||
for inc in parse_entry(entry, launcher)[2]:
|
||||
build_includes.setdefault(inc, None)
|
||||
|
||||
if not build_includes:
|
||||
# No ESPHome translation unit contributed includes: idedata with an
|
||||
# empty build include set breaks clang-tidy/IDE consumers silently
|
||||
_LOGGER.warning(
|
||||
"No ESPHome source includes found in %s; idedata will be incomplete",
|
||||
compile_commands,
|
||||
if not has_esphome_tu:
|
||||
# _pick_entry fell back to an arbitrary C++ entry: idedata built
|
||||
# from it breaks clang-tidy/IDE consumers, and a one-time warning
|
||||
# would be cached into permanence. The best-effort call sites
|
||||
# downgrade this to a build warning.
|
||||
raise EsphomeError(
|
||||
f"No ESPHome translation unit found in {compile_commands}; "
|
||||
"refusing to cache unusable idedata"
|
||||
)
|
||||
|
||||
return {
|
||||
|
||||
@@ -23,7 +23,11 @@ from dataclasses import dataclass
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from esphome.build_helpers.idedata import get_toolchain_includes, parse_entry
|
||||
from esphome.build_helpers.idedata import (
|
||||
get_toolchain_includes,
|
||||
parse_entry,
|
||||
reject_launcher_compiler,
|
||||
)
|
||||
|
||||
TIDY_PROJECT_NAME = "esphome_tidy"
|
||||
|
||||
@@ -422,6 +426,7 @@ def _idedata_from_tidy_project(compile_commands: Path) -> dict:
|
||||
if entry is None:
|
||||
raise RuntimeError(f"tidy.cpp not found in {compile_commands}")
|
||||
cxx_path, defines, includes, cxx_flags = parse_entry(entry)
|
||||
reject_launcher_compiler(cxx_path)
|
||||
|
||||
return {
|
||||
"cxx_path": cxx_path,
|
||||
|
||||
@@ -1123,6 +1123,7 @@ def test_should_run_esp32_platformio_with_branch() -> None:
|
||||
# Shared native-build modules the IDF build imports -> trigger
|
||||
(["esphome/build_helpers/idedata.py"], True),
|
||||
(["esphome/platformio/library.py"], True),
|
||||
(["esphome/framework_helpers.py"], True),
|
||||
(["esphome/platformio/extra_script.py"], True),
|
||||
# PlatformIO build gen, its toolchain, and the esp32 component are
|
||||
# NOT IDF-infra triggers
|
||||
|
||||
@@ -151,11 +151,16 @@ def test_is_esphome_src_handles_backslash_paths() -> None:
|
||||
assert not idedata._is_esphome_src(r"C:\b\src\esphome\core\app.h")
|
||||
|
||||
|
||||
def test_idedata_from_build_empty_includes_warns(
|
||||
tmp_path: Path, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""A compile DB with no ESPHome TU yields no build includes; that is
|
||||
never a usable idedata, so it must be diagnosable."""
|
||||
def test_parse_entry_empty_command_raises() -> None:
|
||||
"""A blank command fails with a named ValueError, not an IndexError."""
|
||||
entry = {"directory": "/b", "file": "/b/src/x.cpp", "command": ""}
|
||||
with pytest.raises(ValueError, match="empty compile command"):
|
||||
idedata.parse_entry(entry)
|
||||
|
||||
|
||||
def test_idedata_from_build_empty_includes_raises(tmp_path: Path) -> None:
|
||||
"""A compile DB with no ESPHome TU is never usable idedata and must
|
||||
not be cached (call sites downgrade the raise to a build warning)."""
|
||||
compile_commands = tmp_path / "compile_commands.json"
|
||||
compile_commands.write_text(
|
||||
json.dumps(
|
||||
@@ -168,10 +173,11 @@ def test_idedata_from_build_empty_includes_warns(
|
||||
]
|
||||
)
|
||||
)
|
||||
with patch.object(idedata, "get_toolchain_includes", return_value=[]):
|
||||
data = idedata.idedata_from_build(compile_commands)
|
||||
assert data["includes"]["build"] == []
|
||||
assert "idedata will be incomplete" in caplog.text
|
||||
with (
|
||||
patch.object(idedata, "get_toolchain_includes", return_value=[]),
|
||||
pytest.raises(EsphomeError, match="No ESPHome translation unit found"),
|
||||
):
|
||||
idedata.idedata_from_build(compile_commands)
|
||||
|
||||
|
||||
def test_idedata_from_build_dedupes_identical_command_shapes(
|
||||
|
||||
Reference in New Issue
Block a user