Match glob archive specs in the fragment belt and quote the list expansion

This commit is contained in:
J. Nick Koston
2026-08-28 12:07:38 -05:00
parent 4a815946e1
commit 923be0736b
4 changed files with 37 additions and 5 deletions
+1 -1
View File
@@ -56,7 +56,7 @@ if(COMMAND __ldgen_get_lib_deps_of_target)
"nothing; app edits will regenerate sections.ld.")
endif()
endif()
set(${out_list_var} ${${out_list_var}} PARENT_SCOPE)
set(${out_list_var} "${${out_list_var}}" PARENT_SCOPE)
endfunction()
else()
message(@MISSING@ "ESPHome ldgen override target not found; "
+17 -2
View File
@@ -1,6 +1,7 @@
"""ESP-IDF direct build API for ESPHome."""
from dataclasses import dataclass, field
import fnmatch
import hashlib
import json
import logging
@@ -480,7 +481,21 @@ def _patch_memory_segments():
_LDGEN_FRAGMENTS_RE = re.compile(r'--fragments-list\s+"([^"]+)"')
_APP_ARCHIVE_MAPPED_RE = re.compile(r"^\s*archive:\s*libsrc\.a\b", re.MULTILINE)
_LDGEN_ARCHIVE_RE = re.compile(r"^\s*archive:\s*(\S+)", re.MULTILINE)
def _fragment_maps_app_archive(text: str) -> bool:
"""True when an archive: spec selects libsrc.a, the archive of the src
component excluded as idf::src/__idf_src in build_gen/espidf.py.
The bare * is IDF's stock catch-all; its archive-level entries resolve
in the linker against all link inputs, so it stays safe when the
archive is excluded from ldgen's own inputs.
"""
return any(
value != "*" and fnmatch.fnmatch("libsrc.a", value)
for value in _LDGEN_ARCHIVE_RE.findall(text)
)
def _ldgen_check_skip(msg: str, strict: bool) -> None:
@@ -512,7 +527,7 @@ def _warn_if_app_archive_mapped() -> None:
except OSError as e:
_ldgen_check_skip(f"could not read {fragment}: {e}", strict)
continue
if _APP_ARCHIVE_MAPPED_RE.search(text):
if _fragment_maps_app_archive(text):
msg = (
f"Linker fragment {fragment} maps the app archive; its "
"entries may be skipped. Set ESPHOME_LDGEN_FULL_DEPS=1 "
@@ -173,6 +173,8 @@ def test_get_project_cmakelists_emits_ldgen_override(
monkeypatch.delenv("ESPHOME_LDGEN_STRICT", raising=False)
content = _render(minimal=minimal)
assert "REMOVE_ITEM ${out_list_var} idf::src __idf_src" in content
# Quoted so spaced elements survive and an empty list stays defined
assert 'set(${out_list_var} "${${out_list_var}}" PARENT_SCOPE)' in content
assert 'message(WARNING "ESPHome ldgen app archive exclusion' in content
assert 'message(STATUS "ESPHome ldgen override target not found' in content
assert 'message(WARNING "ESPHome ldgen override never filtered' in content
+17 -2
View File
@@ -690,13 +690,28 @@ def test_warn_if_app_archive_mapped_strict_raises(
toolchain._warn_if_app_archive_mapped()
def test_warn_if_app_archive_mapped_glob(
setup_core: Path, tmp_path: Path, caplog: pytest.LogCaptureFixture
) -> None:
"""A glob archive spec that selects the app archive is also flagged."""
_setup_build(setup_core)
frag = tmp_path / "linker.lf"
frag.write_text("[mapping:evil]\narchive: lib*\n")
_write_fragments_build_ninja(tmp_path, [frag])
toolchain._warn_if_app_archive_mapped()
assert "maps the app archive" in caplog.text
def test_warn_if_app_archive_mapped_clean(
setup_core: Path, tmp_path: Path, caplog: pytest.LogCaptureFixture
) -> None:
"""Normal fragments produce no warning."""
"""Normal fragments, including IDF's stock archive: * catch-all,
produce no warning."""
_setup_build(setup_core)
frag = tmp_path / "linker.lf"
frag.write_text("[mapping:freertos]\narchive: libfreertos.a\n")
frag.write_text(
"[mapping:freertos]\narchive: libfreertos.a\n[mapping:default]\narchive: *\n"
)
_write_fragments_build_ninja(tmp_path, [frag])
toolchain._warn_if_app_archive_mapped()
assert "maps the app archive" not in caplog.text