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

This commit is contained in:
J. Nick Koston
2026-08-25 22:23:06 -05:00
3 changed files with 22 additions and 7 deletions
+11 -3
View File
@@ -324,14 +324,16 @@ def _external_short_name(name: str) -> str:
def _check_unfulfilled_provides(
provided_requests: list[str], satisfied: set[str]
provided_requests: list[str], satisfied: set[str], still_requested: set[str]
) -> None:
"""Reconcile the provides() promise: every dependency the walk skipped
on the backend's word must have been added from the framework tree (or
knowingly satisfied by a converted/external library). An unfulfilled
promise can only surface as undefined symbols at link, so it fails
here by name like the other can-never-link checks in this module."""
if missing := sorted(set(provided_requests) - satisfied):
here by name like the other can-never-link checks in this module. The
walk records across re-resolutions, so a name no final manifest still
requests is stale state, never a failure."""
if missing := sorted((set(provided_requests) & still_requested) - satisfied):
raise EsphomeError(
"provides() skipped these dependencies but nothing added them: "
f"{', '.join(missing)}; the build is missing libraries"
@@ -391,6 +393,9 @@ def resolve_libraries(
# Bundled candidates skipped on purpose (platform filter); the
# provides() reconciliation must count them as satisfied
knowingly_skipped: set[str] = set()
# Dependency names of the manifests actually emitted; a walk recording
# for a since-re-resolved manifest must not fail the reconciliation
final_dep_names: set[str] = set()
# Ordered set of bundled dependency names to add once conversion is done
pending_bundled: dict[str, None] = {}
# Deps matching a separately-requested external are already in the build
@@ -407,6 +412,8 @@ def resolve_libraries(
component.data.get("dependencies"), component.name
):
name = dep.get("name")
if isinstance(name, str):
final_dep_names.add(name)
if isinstance(name, str) and "/" in name:
owner, _, pkg = name.partition("/")
if _is_safe_library_name(owner) and _is_safe_library_name(pkg):
@@ -527,6 +534,7 @@ def resolve_libraries(
| converted_manifest_names
| external_short_names
| knowingly_skipped,
final_dep_names,
)
return bundled + converted
+4 -2
View File
@@ -944,9 +944,11 @@ def _warn_unsatisfied_versionless(
if dep_name in resolved_manifest_names:
# Name-only evidence: any resolved component with this manifest
# name counts, not just ones the requester can reach, so a
# coincidental name collision must stay visible
# coincidental name collision must stay visible where the user
# could pin it; treeless backends cannot act on it
warned.add(dep_name)
_LOGGER.warning(
log = _LOGGER.warning if backend.provides is not None else _LOGGER.debug
log(
"Version-less dependency %s of %s assumed satisfied by a "
"resolved library's manifest name only",
dep_name,
+7 -2
View File
@@ -721,10 +721,15 @@ def test_unfulfilled_provides_promise_raises(tmp_path: Path) -> None:
undefined symbols at link, so it fails here by name, deduplicated;
satisfied ones pass silently."""
with pytest.raises(EsphomeError, match="Wire") as err:
component._check_unfulfilled_provides(["Wire", "Wire", "Hash"], {"Hash"})
component._check_unfulfilled_provides(
["Wire", "Wire", "Hash"], {"Hash"}, {"Wire", "Hash"}
)
assert str(err.value).count("Wire") == 1
assert "Hash" not in str(err.value)
component._check_unfulfilled_provides(["Hash"], {"Hash"})
component._check_unfulfilled_provides(["Hash"], {"Hash"}, {"Hash"})
# A recording for a since-re-resolved manifest is stale walk state,
# never a failure: no final manifest still requests Wire
component._check_unfulfilled_provides(["Wire"], set(), set())
def test_extra_script_link_flags_reach_the_library(tmp_path: Path) -> None: