diff --git a/esphome/arduino/library.py b/esphome/arduino/library.py index 1c5840cd10..dee58b5fed 100644 --- a/esphome/arduino/library.py +++ b/esphome/arduino/library.py @@ -8,10 +8,11 @@ resolution/download pipeline in ``esphome.platformio.library``. Nothing here is core-specific: the caller names the PlatformIO platform, MCU, and cache key of the Arduino core it builds. -Known deviation: 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, only user-supplied ones -carrying sources in unusual subdirectories. +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. 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 @@ -87,8 +88,9 @@ def _library_info(name: str, read_path: Path, data: dict) -> ArduinoLibrary: # PlatformIO shell-lexes each build.flags entry flag_tokens = lex_build_flags(build.get("flags", []), f"library {name}") - # PIO precedence: build.libArchive, else the Arduino-format - # dot_a_linkage property, else archive (PlatformIO's default) + # build.libArchive is PIO behavior; dot_a_linkage is honored as a + # deliberate extra (Arduino IDE's property, which PIO ignores) so + # properties-only libraries can opt out of archiving too if "libArchive" in build: lib_archive = bool(build["libArchive"]) elif "dot_a_linkage" in data: @@ -245,11 +247,19 @@ def resolve_libraries( try: check_library_data(dep, pio_platform, "arduino") except InvalidLibrary as err: - # check_library_data's only raise is the platform filter, and - # rejecting another platform's dependency of a cross-platform - # manifest is routine (every ESPAsyncWebServer build hits it); - # a warning here would be noise, and the reason is in the log. - _LOGGER.debug("Skipping bundled dependency %s: %s", name, err) + # Rejecting another platform's dependency of a cross-platform + # manifest is routine (every ESPAsyncWebServer build hits + # it), so the platform filter stays at debug; any other + # cause means a dropped dependency and must be visible + if "platform" in str(err).lower(): + _LOGGER.debug("Skipping bundled dependency %s: %s", name, err) + else: + _LOGGER.warning( + "Skipping bundled dependency %s of %s: %s", + name, + component.name, + err, + ) continue bundled_names.add(name) bundled.append(_bundled_library(framework_path, name)) @@ -266,7 +276,7 @@ def resolve_libraries( _add_bundled_dependencies(component) if external: - convert_libraries( + resolved = convert_libraries( external, LibraryBackend( platform=pio_platform, @@ -275,5 +285,14 @@ def resolve_libraries( cache_key=cache_key, ), ) + if len(resolved) < len(external): + # A requested library the converter dropped would otherwise + # surface only as link errors far from the cause + _LOGGER.warning( + "%d of %d requested libraries were not resolved (resolved: %s)", + len(external) - len(resolved), + len(external), + ", ".join(sorted(c.name for c in resolved)) or "none", + ) return bundled + converted diff --git a/tests/unit_tests/test_arduino_library.py b/tests/unit_tests/test_arduino_library.py index 81e17490aa..f0bff7b729 100644 --- a/tests/unit_tests/test_arduino_library.py +++ b/tests/unit_tests/test_arduino_library.py @@ -409,3 +409,52 @@ def test_resolve_libraries_dep_warnings( assert "malformed dependency entry" in caplog.text assert "Orphan" in caplog.text assert "owner but no version" in caplog.text + + +def test_resolve_libraries_warns_when_converter_drops_a_request( + tmp_path: Path, caplog: pytest.LogCaptureFixture +) -> None: + """A requested external library the converter drops is named, not lost.""" + framework = _make_framework(tmp_path) + _add_library("pngle", "1.0.0") + with patch.object(component, "convert_libraries", return_value=[]): + component.resolve_libraries( + framework, + pio_platform="espressif8266", + board_mcu="esp8266", + cache_key="arduino8266", + ) + assert "1 of 1 requested libraries were not resolved" in caplog.text + + +def test_bundled_dependency_nonplatform_rejection_warns( + tmp_path: Path, caplog: pytest.LogCaptureFixture +) -> None: + """An InvalidLibrary whose cause is not the platform filter is visible.""" + from esphome.platformio.library import InvalidLibrary + + framework = _make_framework(tmp_path) + _add_library("ESP32Async/ESPAsyncWebServer", "3.9.6") + lib_dir = tmp_path / "converted" / "webserver" + (lib_dir / "src").mkdir(parents=True) + converted = _converted( + "esp32async__ESPAsyncWebServer", + lib_dir, + {"build": {}, "dependencies": [{"name": "Wire"}]}, + ) + with ( + _emitting_converter(converted), + patch.object( + component, + "check_library_data", + side_effect=InvalidLibrary("manifest is corrupt"), + ), + ): + component.resolve_libraries( + framework, + pio_platform="espressif8266", + board_mcu="esp8266", + cache_key="arduino8266", + ) + assert "Skipping bundled dependency Wire" in caplog.text + assert "manifest is corrupt" in caplog.text