mirror of
https://github.com/esphome/esphome.git
synced 2026-09-29 07:50:21 +00:00
Reconcile provides() promises against the final manifests only
The walk records across re-resolutions, so a dependency a re-resolved manifest no longer declares would have failed the build by name. The reconciliation now intersects the recordings with the dependency names of the manifests actually emitted, keeping the raise for real broken promises. The manifest-name warning also gains the treeless-backend gate its sibling got, since those backends cannot act on it either.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user