Merge branch 'esp8266-native-library-backend' into esp8266-native-build-spec

This commit is contained in:
J. Nick Koston
2026-08-21 20:38:32 -05:00
3 changed files with 88 additions and 4 deletions
+18 -4
View File
@@ -71,13 +71,22 @@ class ArduinoLibrary:
link_flags: list[str] = field(default_factory=list)
def _library_info(name: str, read_path: Path, data: dict) -> ArduinoLibrary:
"""Resolve one library's sources, include dirs, and flags (PIO semantics)."""
def _manifest_build(name: str, data: object) -> dict:
"""The manifest's ``build`` section, validated by name.
A bare json.load imposes no shape; a malformed manifest must name the
library instead of an AttributeError deep in a traceback (and must do so
before apply_extra_script dereferences the same section).
"""
build = data.get("build", {}) if isinstance(data, dict) else None
if not isinstance(build, dict):
# A bare json.load imposes no shape; name the library instead of an
# AttributeError deep in a traceback
raise EsphomeError(f"Library {name} has a malformed manifest")
return build
def _library_info(name: str, read_path: Path, data: dict) -> ArduinoLibrary:
"""Resolve one library's sources, include dirs, and flags (PIO semantics)."""
build = _manifest_build(name, data)
# PIO's source-dir resolution: manifest srcDir, else src/Src, else the root
if "srcDir" in build:
@@ -326,6 +335,7 @@ def resolve_libraries(
bundled.append(_bundled_library(framework_path, name))
def _emit(component: ConvertedLibrary) -> None:
_manifest_build(component.get_require_name(), component.data)
apply_extra_script(
component, board_mcu=lambda: board_mcu, pio_platform=pio_platform
)
@@ -344,6 +354,10 @@ def resolve_libraries(
framework="arduino",
emit=_emit,
cache_key=cache_key,
# The graph walk must not resolve a bundled name from the
# registry ({"Wire": "*"} in a manifest); the bundled copy
# is added by _add_bundled_dependencies after emit
provides=lambda name: (framework_path / "libraries" / name).is_dir(),
),
)
if len(resolved) < len(external):
+14
View File
@@ -300,6 +300,11 @@ class LibraryBackend:
framework: str
emit: Callable[["ConvertedLibrary"], None]
cache_key: str
# When set, an owner-less manifest dependency this returns True for is
# skipped by the graph walk: the backend provides it outside the
# registry (e.g. a library bundled with the Arduino core), mirroring
# PlatformIO's process_dependencies preference for bundled builders.
provides: Callable[[str], bool] | None = None
def ensure_list[T](obj: T | list[T]) -> list[T]:
@@ -973,6 +978,15 @@ def convert_libraries(
if is_lib_ignored(dep_name, lib_ignore):
_LOGGER.debug("Skip ignored dependency %s", dep_name)
continue
if (
backend.provides is not None
and not dependency.get("owner")
and backend.provides(dep_name)
):
# The backend adds it from its own tree; resolving it here
# would fetch a same-named registry package instead
_LOGGER.debug("Skip backend-provided dependency %s", dep_name)
continue
# The version field may actually be a URL (git/archive dependency).
dep_version = dependency["version"]
dep_url = _url_or_none(dep_version)
+56
View File
@@ -757,3 +757,59 @@ def test_bundled_library_non_dict_manifest_skips_probes_and_raises(
(wire / "library.json").write_text('["not", "a", "manifest"]')
with pytest.raises(EsphomeError, match="Library Wire has a malformed manifest"):
component._bundled_library(framework, "Wire")
def test_dict_shorthand_dependency_skips_registry_through_real_converter(
tmp_path: Path,
) -> None:
"""{"Wire": "*"} in a real manifest must never reach the registry: the
graph walk skips backend-provided names and the bundled copy is added
after emit (no converter mock; a registry touch fails the test)."""
import esphome.platformio.library as pio_library
framework = _make_framework(tmp_path)
local_lib = tmp_path / "locallib"
(local_lib / "src").mkdir(parents=True)
(local_lib / "src" / "local.cpp").write_text("")
(local_lib / "library.json").write_text(
'{"name": "LocalLib", "version": "1.0.0", "dependencies": {"Wire": "*"}}'
)
_add_library(f"file://{local_lib}", None)
# The real converter writes its component cache under the config dir
CORE.config_path = tmp_path / "test.yaml"
CORE.config_path.write_text("")
with patch.object(
pio_library,
"_resolve_registry_version",
side_effect=AssertionError("registry touched"),
):
libs = component.resolve_libraries(
framework,
pio_platform="espressif8266",
board_mcu="esp8266",
cache_key="arduino8266",
)
names = [lib.name for lib in libs]
assert "Wire" in names
assert any("locallib" in n.lower() for n in names)
def test_emit_validates_manifest_before_extra_script(tmp_path: Path) -> None:
"""A malformed build section fails by name before apply_extra_script
dereferences it."""
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": "src"})
with (
_emitting_converter(converted) as mock_extra,
pytest.raises(EsphomeError, match="has a malformed manifest"),
):
component.resolve_libraries(
framework,
pio_platform="espressif8266",
board_mcu="esp8266",
cache_key="arduino8266",
)
mock_extra.assert_not_called()