From ca47309a36b9d5457e2fd88e73b5ebf4b0e273ee Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 20 Aug 2026 15:26:17 -0500 Subject: [PATCH] Move the Arduino library backend to esphome/arduino and close_fds the ar shim --- esphome/arduino/__init__.py | 0 .../component.py => arduino/library.py} | 31 ++++++----- esphome/build_gen/build_tool.py | 4 +- ...6_component.py => test_arduino_library.py} | 53 +++++++++++++++---- 4 files changed, 65 insertions(+), 23 deletions(-) create mode 100644 esphome/arduino/__init__.py rename esphome/{arduino8266/component.py => arduino/library.py} (90%) rename tests/unit_tests/{test_arduino8266_component.py => test_arduino_library.py} (89%) diff --git a/esphome/arduino/__init__.py b/esphome/arduino/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/arduino8266/component.py b/esphome/arduino/library.py similarity index 90% rename from esphome/arduino8266/component.py rename to esphome/arduino/library.py index 2e0de9a57d..d673b5fd73 100644 --- a/esphome/arduino8266/component.py +++ b/esphome/arduino/library.py @@ -1,10 +1,12 @@ -"""Arduino ESP8266 backend for the shared PlatformIO library converter. +"""Arduino-core backend for the shared PlatformIO library converter. Turns the libraries registered via ``cg.add_library()`` into build inputs for -the ninja generator. Bare names that exist under the framework's bundled +a native Arduino build. Bare names that exist under the framework's bundled ``libraries/`` directory (ESP8266WiFi, Wire, SPI, ...) are read straight from the framework tree; everything else goes through the shared -resolution/download pipeline in ``esphome.platformio.library``. +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. 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 @@ -40,8 +42,6 @@ from esphome.platformio.library import ( _LOGGER = logging.getLogger(__name__) -ESP8266_PLATFORM = "espressif8266" - @dataclass class ArduinoLibrary: @@ -145,8 +145,15 @@ def _bundled_library(framework_path: Path, name: str) -> ArduinoLibrary: return _library_info(name, lib_dir, {"name": name, **data}) -def resolve_libraries(framework_path: Path) -> list[ArduinoLibrary]: - """Resolve every ``cg.add_library()`` entry into an :class:`ArduinoLibrary`.""" +def resolve_libraries( + framework_path: Path, *, pio_platform: str, board_mcu: str, cache_key: str +) -> list[ArduinoLibrary]: + """Resolve every ``cg.add_library()`` entry into an :class:`ArduinoLibrary`. + + ``pio_platform``/``board_mcu`` filter manifests the way PlatformIO would + for that core (e.g. ``espressif8266``/``esp8266``); ``cache_key`` keys the + shared converter's download cache. + """ bundled: list[ArduinoLibrary] = [] external: list[Library] = [] # PlatformIO's lib_ignore covers framework-bundled libraries too; the @@ -200,7 +207,7 @@ def resolve_libraries(framework_path: Path) -> list[ArduinoLibrary]: ) continue try: - check_library_data(dep, ESP8266_PLATFORM, "arduino") + 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 @@ -212,9 +219,7 @@ def resolve_libraries(framework_path: Path) -> list[ArduinoLibrary]: bundled.append(_bundled_library(framework_path, name)) def _emit(component: ConvertedLibrary) -> None: - apply_extra_script( - component, board_mcu="esp8266", pio_platform=ESP8266_PLATFORM - ) + apply_extra_script(component, board_mcu=board_mcu, pio_platform=pio_platform) converted.append( _library_info( component.get_require_name(), component.source_dir, component.data @@ -226,10 +231,10 @@ def resolve_libraries(framework_path: Path) -> list[ArduinoLibrary]: convert_libraries( external, LibraryBackend( - platform=ESP8266_PLATFORM, + platform=pio_platform, framework="arduino", emit=_emit, - cache_key="arduino8266", + cache_key=cache_key, ), ) diff --git a/esphome/build_gen/build_tool.py b/esphome/build_gen/build_tool.py index 19275a8eaf..ef6b1d4cc2 100644 --- a/esphome/build_gen/build_tool.py +++ b/esphome/build_gen/build_tool.py @@ -25,7 +25,9 @@ def main() -> int: # treats backslashes in response files as escapes, corrupting Windows # paths ("sub\a.o" -> "suba.o"). objects = Path(rspfile).read_text(encoding="utf-8").split() - return subprocess.run([ar, "rc", archive, *objects], check=False).returncode + return subprocess.run( + [ar, "rc", archive, *objects], check=False, close_fds=False + ).returncode if mode == "copy": src, dst = sys.argv[2:4] shutil.copyfile(src, dst) diff --git a/tests/unit_tests/test_arduino8266_component.py b/tests/unit_tests/test_arduino_library.py similarity index 89% rename from tests/unit_tests/test_arduino8266_component.py rename to tests/unit_tests/test_arduino_library.py index edc81be6cb..912038a20c 100644 --- a/tests/unit_tests/test_arduino8266_component.py +++ b/tests/unit_tests/test_arduino_library.py @@ -1,4 +1,4 @@ -"""Tests for esphome.arduino8266.component (library resolution).""" +"""Tests for esphome.arduino.library (Arduino-core library resolution).""" from __future__ import annotations @@ -8,7 +8,7 @@ from unittest.mock import patch import pytest -from esphome.arduino8266 import component +from esphome.arduino import library as component from esphome.const import KEY_CORE, KEY_TARGET_PLATFORM, PLATFORM_ESP8266 from esphome.core import CORE, EsphomeError, Library from esphome.platformio.library import ConvertedLibrary, LibraryBackend @@ -149,7 +149,12 @@ def test_library_info_no_src_dir(tmp_path: Path) -> None: def test_resolve_libraries_bundled(tmp_path: Path) -> None: framework = _make_framework(tmp_path) _add_library("ESP8266WiFi", None) - libs = component.resolve_libraries(framework) + libs = component.resolve_libraries( + framework, + pio_platform="espressif8266", + board_mcu="esp8266", + cache_key="arduino8266", + ) assert [lib.name for lib in libs] == ["ESP8266WiFi"] @@ -159,7 +164,12 @@ def test_resolve_libraries_bare_registry_name_is_external(tmp_path: Path) -> Non framework = _make_framework(tmp_path) _add_library("pngle", None) with patch.object(component, "convert_libraries", return_value=[]) as mock_convert: - component.resolve_libraries(framework) + component.resolve_libraries( + framework, + pio_platform="espressif8266", + board_mcu="esp8266", + cache_key="arduino8266", + ) (libraries, _backend), _ = mock_convert.call_args assert [lib.name for lib in libraries] == ["pngle"] @@ -197,7 +207,12 @@ def test_resolve_libraries_external_and_bundled_deps(tmp_path: Path) -> None: ) with _emitting_converter(converted) as mock_extra: - libs = component.resolve_libraries(framework) + libs = component.resolve_libraries( + framework, + pio_platform="espressif8266", + board_mcu="esp8266", + cache_key="arduino8266", + ) mock_extra.assert_called_once_with( converted, board_mcu="esp8266", pio_platform="espressif8266" @@ -220,7 +235,12 @@ def test_resolve_libraries_bundled_dep_already_present(tmp_path: Path) -> None: ) with _emitting_converter(converted): - libs = component.resolve_libraries(framework) + libs = component.resolve_libraries( + framework, + pio_platform="espressif8266", + board_mcu="esp8266", + cache_key="arduino8266", + ) # Wire appears once (from the explicit registration), not twice assert [lib.name for lib in libs] == ["Wire", "some__External"] @@ -233,7 +253,12 @@ def test_resolve_libraries_versioned_bare_name_is_external(tmp_path: Path) -> No _add_library("pngle", "1.1.0") with patch.object(component, "convert_libraries", return_value=[]) as mock_convert: - component.resolve_libraries(framework) + component.resolve_libraries( + framework, + pio_platform="espressif8266", + board_mcu="esp8266", + cache_key="arduino8266", + ) (libraries, _backend), _ = mock_convert.call_args assert [lib.name for lib in libraries] == ["pngle"] @@ -283,7 +308,12 @@ def test_resolve_libraries_lib_ignore_covers_bundled(tmp_path: Path) -> None: _add_library("ESP8266WiFi", None) _add_library("Wire", None) CORE.platformio_options = {"lib_ignore": ["Wire"]} - libs = component.resolve_libraries(framework) + libs = component.resolve_libraries( + framework, + pio_platform="espressif8266", + board_mcu="esp8266", + cache_key="arduino8266", + ) assert [lib.name for lib in libs] == ["ESP8266WiFi"] @@ -301,7 +331,12 @@ def test_resolve_libraries_lib_ignore_covers_bundled_dependencies( ) with _emitting_converter(converted): - libs = component.resolve_libraries(framework) + libs = component.resolve_libraries( + framework, + pio_platform="espressif8266", + board_mcu="esp8266", + cache_key="arduino8266", + ) assert [lib.name for lib in libs] == ["some__External"]