Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer

This commit is contained in:
J. Nick Koston
2026-08-23 19:30:47 -05:00
4 changed files with 59 additions and 7 deletions
+7 -5
View File
@@ -39,11 +39,13 @@ ZEPHYR_FRAMEWORK = "zephyr"
def _escape(p: PathType) -> str:
# In CMakeLists.txt, backslashes need to be escaped (mirrors the ESP-IDF
# backend's escape_entry). Doubling -- rather than rewriting '\' -> '/' --
# preserves content, so it's safe for arbitrary build flags (e.g. a -D value
# containing a backslash) as well as Windows paths.
return f'"{str(p)}"'.replace("\\", "\\\\")
# In CMakeLists.txt, backslashes and embedded quotes need escaping
# (mirrors the ESP-IDF backend's escape_entry; the lex round-trip makes
# a literal quote in a -D value reachable). Doubling backslashes --
# rather than rewriting '\' -> '/' -- preserves content, so it's safe
# for arbitrary build flags as well as Windows paths.
escaped = str(p).replace("\\", "\\\\").replace('"', '\\"')
return f'"{escaped}"'
def generate_module_yml(component: ConvertedLibrary) -> str:
+16 -2
View File
@@ -714,6 +714,10 @@ def normalize_dependencies(
list, and a plain (possibly comma-separated) string; normalize them all
so callers see a uniform list. ``manifest_name`` names the manifest in the
warning for entries that cannot be normalized.
Bare-name spellings carry no version; the dependency walk later drops
version-less entries at DEBUG, since they are usually names of bundled
framework libraries (Wire, SPI) that need no registry install.
"""
if dependencies is None:
return []
@@ -1133,15 +1137,25 @@ def convert_libraries(
malformed = not isinstance(component.data, dict)
if not malformed:
build = component.data.get("build", {})
esphome_data = component.data.get(ESPHOME_DATA_KEY, {})
malformed = (
not isinstance(build, dict)
or not isinstance(component.data.get(ESPHOME_DATA_KEY, {}), dict)
or not isinstance(esphome_data, dict)
or not isinstance(
esphome_data.get(ESPHOME_DATA_LINK_FLAGS_KEY, []), list
)
or not isinstance(build.get("srcDir", ""), str)
or not isinstance(build.get("includeDir", ""), str)
or not isinstance(build.get("srcFilter", ""), (str, list))
)
if malformed:
raise EsphomeError(f"Library {key} has a malformed manifest")
# Fail fast only for a library the user asked for; a defect
# in an unrequested corner of the graph must not block the
# build
if key in top_level_keys:
raise EsphomeError(f"Library {key} has a malformed manifest")
_LOGGER.warning("Skipping dependency %s: malformed manifest", key)
continue
warn_properties_depends(component.name, component.data)
try:
@@ -826,6 +826,7 @@ def test_normalize_dependencies_forms(caplog) -> None:
{"name": "A", "build": {"srcDir": 123}},
{"name": "A", "build": {"includeDir": ["inc"]}},
{"name": "A", "build": {"srcFilter": {"+": "src"}}},
{"name": "A", "ESPHOME": {"LINK_FLAGS": "-Wl,-x"}},
],
)
def test_convert_libraries_malformed_manifest_raises(
@@ -838,6 +839,29 @@ def test_convert_libraries_malformed_manifest_raises(
convert_libraries([Library("esphome/A", None, None)], _backend())
def test_convert_libraries_malformed_transitive_dep_skips(
tmp_path, monkeypatch, caplog: pytest.LogCaptureFixture
) -> None:
"""A malformed manifest on a dependency the user never asked for warns
and skips; only a top-level library fails the build."""
_patch_download_with_manifests(
monkeypatch,
tmp_path,
{
"esphome/A": {
"name": "A",
"dependencies": [{"name": "B", "owner": "esphome", "version": "1.0"}],
},
"esphome/B": {"name": "B", "build": {"srcDir": 123}},
},
)
components = convert_libraries([Library("esphome/A", None, None)], _backend())
names = [c.name for c in components]
assert "esphome/A" in names
assert "esphome/B" not in names
assert "Skipping dependency esphome/B: malformed manifest" in caplog.text
def test_walk_warns_for_properties_only_depends(
tmp_path, monkeypatch, caplog: pytest.LogCaptureFixture
) -> None:
+12
View File
@@ -66,6 +66,18 @@ def test_generate_cmakelists_txt_flags_and_includes(tmp_path):
assert "-lm" in out
def test_generate_cmakelists_txt_escapes_embedded_quotes(tmp_path):
"""A define value carrying a literal quote (reachable via the lex
round-trip) survives as an escaped quote, not a broken CMake string."""
c = _make_component(tmp_path)
(tmp_path / "src").mkdir()
(tmp_path / "src" / "a.c").write_text("")
c.data = {"build": {"flags": ['-DMSG=\\"hi\\"']}}
out = generate_cmakelists_txt(c)
assert '"-DMSG=\\"hi\\""' in out
def test_generate_cmakelists_txt_lexes_spaced_flags(tmp_path):
"""A spaced -I entry routes to include dirs instead of landing verbatim
in compile options; same shared lexer as the espidf emitter."""