Catch the depends= spelling, warn on bundled extraScripts, trust top-level requests

The bundled-dependency visibility warning also reads the
library.properties depends= key the shared parser returns raw, since
properties is the format bundled libraries actually ship. A bundled
manifest declaring an extraScript warns that it never runs (a new
Known-deviations entry). A version-less manifest dependency the config
separately requests as a top-level library is already in the build, so
the skipping warning no longer fires for it and cannot teach users to
ignore the real drop.
This commit is contained in:
J. Nick Koston
2026-08-21 17:38:25 -05:00
parent e18852e084
commit d96ddf3612
2 changed files with 100 additions and 9 deletions
+25 -4
View File
@@ -12,7 +12,8 @@ Known deviations: flat-layout (``library.properties``, no ``src/``)
libraries get the recursive default source filter rather than PlatformIO's
root-only Arduino-1.0 filter (no bundled library is affected), and the
Arduino ``dot_a_linkage`` property is honored even though PlatformIO
ignores it.
ignores it. Bundled libraries never run a manifest ``extraScript`` (a
warning names the library if one declares it).
Mirrors PlatformIO's ``lib_ldf_mode=off`` behavior: each library builds into
its own static archive and every library's include dir joins one global
@@ -188,15 +189,27 @@ def _bundled_library(framework_path: Path, name: str) -> ArduinoLibrary:
else:
manifest = lib_dir / "library.properties"
data = parse_library_properties(manifest) if manifest.is_file() else {}
if isinstance(data, dict) and data.get("dependencies"):
if isinstance(data, dict):
# The dependency walk never runs for bundled libraries (a no-op for
# the ESP8266 core, whose bundled manifests declare none); on a core
# where one does, the skip must be visible before link errors
# where one does, the skip must be visible before link errors.
# "depends" is the library.properties spelling, which the shared
# parser returns raw.
if data.get("dependencies") or data.get("depends"):
_LOGGER.warning(
"Bundled library %s declares dependencies, which are not "
"resolved automatically; add them with add_library() if needed",
name,
)
build = data.get("build")
if isinstance(build, dict) and build.get("extraScript"):
# apply_extra_script only runs on the converted path; a bundled
# manifest relying on one would build with missing flags
_LOGGER.warning(
"Bundled library %s declares an extraScript, which is not "
"run for bundled libraries",
name,
)
return _library_info(name, lib_dir, data)
@@ -239,6 +252,10 @@ def resolve_libraries(
converted: list[ArduinoLibrary] = []
bundled_names = {lib.name for lib in bundled}
# Short names of the separately-requested externals: a manifest
# dependency matching one is already in the build, not a drop (a false
# "skipping" warning teaches users to ignore the real one)
external_short_names = {lib.name.split("/")[-1] for lib in external if lib.name}
def _add_bundled_dependencies(component: ConvertedLibrary) -> None:
# A version-less bare-name dependency ("Hash" in ESPAsyncWebServer)
@@ -253,7 +270,11 @@ def resolve_libraries(
component.name,
)
continue
if name in bundled_names or is_lib_ignored(name, lib_ignore):
if (
name in bundled_names
or name in external_short_names
or is_lib_ignored(name, lib_ignore)
):
continue
bundled_dir = framework_path / "libraries" / name
if "version" in dep and (dep.get("owner") or not bundled_dir.is_dir()):
+70
View File
@@ -675,3 +675,73 @@ def test_library_info_dot_a_linkage_parses_strictly(
lib = component._library_info("x", read_path, {"dot_a_linkage": value, "build": {}})
assert lib.lib_archive is expected
assert ("unrecognized dot_a_linkage" in caplog.text) is warns
def test_bundled_library_properties_depends_warns(
tmp_path: Path, caplog: pytest.LogCaptureFixture
) -> None:
"""The library.properties depends= spelling reaches the visibility
warning too; the shared parser returns it raw."""
framework = _make_framework(tmp_path)
wire = framework / "libraries" / "Wire"
(wire / "library.properties").write_text("name=Wire\nversion=1.0\ndepends=SPI\n")
_add_library("Wire", None)
component.resolve_libraries(
framework,
pio_platform="espressif8266",
board_mcu="esp8266",
cache_key="arduino8266",
)
assert "Bundled library Wire declares dependencies" in caplog.text
def test_bundled_library_extra_script_warns(
tmp_path: Path, caplog: pytest.LogCaptureFixture
) -> None:
"""A bundled manifest relying on an extraScript is a named deviation,
not a silently miscompiled library."""
framework = _make_framework(tmp_path)
wire = framework / "libraries" / "Wire"
(wire / "library.json").write_text(
'{"name": "Wire", "build": {"extraScript": "extra.py"}}'
)
_add_library("Wire", None)
component.resolve_libraries(
framework,
pio_platform="espressif8266",
board_mcu="esp8266",
cache_key="arduino8266",
)
assert "declares an extraScript" in caplog.text
def test_dependency_requested_top_level_is_not_a_drop(
tmp_path: Path, caplog: pytest.LogCaptureFixture
) -> None:
"""A version-less manifest dependency the config separately requests is
already in the build; the skipping warning must not fire for it."""
from esphome.platformio.library import request_key
framework = _make_framework(tmp_path)
_add_library("ESP32Async/ESPAsyncWebServer", "3.9.6")
_add_library("ESP32Async/ESPAsyncTCP", "2.0.0")
ws_dir = tmp_path / "converted" / "webserver"
(ws_dir / "src").mkdir(parents=True)
tcp_dir = tmp_path / "converted" / "tcp"
(tcp_dir / "src").mkdir(parents=True)
ws = _converted(
"esp32async__ESPAsyncWebServer",
ws_dir,
{"build": {}, "dependencies": [{"name": "ESPAsyncTCP"}]},
)
tcp = _converted("esp32async__ESPAsyncTCP", tcp_dir, {"build": {}})
for conv, lib in zip((ws, tcp), CORE.platformio_libraries.values(), strict=True):
conv.node_key = request_key(lib)
with _emitting_converter(ws, tcp):
component.resolve_libraries(
framework,
pio_platform="espressif8266",
board_mcu="esp8266",
cache_key="arduino8266",
)
assert "is not bundled with the framework" not in caplog.text