diff --git a/esphome/arduino/library.py b/esphome/arduino/library.py index 7e79ac6b73..208c0e71ad 100644 --- a/esphome/arduino/library.py +++ b/esphome/arduino/library.py @@ -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 - _LOGGER.warning( - "Bundled library %s declares dependencies, which are not " - "resolved automatically; add them with add_library() if needed", - name, - ) + # 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()): diff --git a/tests/unit_tests/test_arduino_library.py b/tests/unit_tests/test_arduino_library.py index 9e53cd443a..73a28bb875 100644 --- a/tests/unit_tests/test_arduino_library.py +++ b/tests/unit_tests/test_arduino_library.py @@ -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