mirror of
https://github.com/esphome/esphome.git
synced 2026-08-24 07:06:20 +00:00
Reconcile the provides() promise after resolving; name the double-warning invariant's pinning test
This commit is contained in:
+36
-14
@@ -317,6 +317,22 @@ def _external_short_name(name: str) -> str:
|
||||
return short.partition("#")[0].removesuffix(".git")
|
||||
|
||||
|
||||
def _warn_unfulfilled_provides(
|
||||
provided_requests: list[str], satisfied: 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 would surface only as undefined symbols at link."""
|
||||
for name in provided_requests:
|
||||
if name not in satisfied:
|
||||
_LOGGER.warning(
|
||||
"provides() skipped dependency %s but nothing added it; "
|
||||
"the build is missing a library",
|
||||
name,
|
||||
)
|
||||
|
||||
|
||||
def resolve_libraries(
|
||||
framework_path: Path, *, pio_platform: str, board_mcu: str, cache_key: str
|
||||
) -> list[ArduinoLibrary]:
|
||||
@@ -430,8 +446,11 @@ def resolve_libraries(
|
||||
continue
|
||||
try:
|
||||
# framework=None: the walk already ran dependency_is_usable
|
||||
# on this entry (and warned for any non-platform cause);
|
||||
# re-checking with a framework would warn twice
|
||||
# on this entry and warned for any non-platform cause, so
|
||||
# debug here is what keeps one manifest fault from warning
|
||||
# twice. That invariant is pinned by
|
||||
# test_nonplatform_rejection_warns_once_through_real_converter,
|
||||
# which fails if the walk stops evaluating these deps.
|
||||
check_library_data(dep, pio_platform, None)
|
||||
except InvalidLibrary as err:
|
||||
_LOGGER.debug("Skip bundled candidate %s: %s", name, err)
|
||||
@@ -458,19 +477,17 @@ def resolve_libraries(
|
||||
)
|
||||
_add_bundled_dependencies(component)
|
||||
|
||||
backend = LibraryBackend(
|
||||
platform=pio_platform,
|
||||
framework="arduino",
|
||||
emit=_emit,
|
||||
cache_key=cache_key,
|
||||
# The walk must not resolve bundled names from the registry;
|
||||
# _add_bundled_dependencies adds them after emit
|
||||
provides=_provided,
|
||||
)
|
||||
if external:
|
||||
convert_libraries(
|
||||
external,
|
||||
LibraryBackend(
|
||||
platform=pio_platform,
|
||||
framework="arduino",
|
||||
emit=_emit,
|
||||
cache_key=cache_key,
|
||||
# The walk must not resolve bundled names from the registry;
|
||||
# _add_bundled_dependencies adds them after emit
|
||||
provides=_provided,
|
||||
),
|
||||
)
|
||||
convert_libraries(external, backend)
|
||||
for name in pending_bundled:
|
||||
if name in converted_manifest_names:
|
||||
# Manifest-name evidence: the converted library is this library,
|
||||
@@ -486,4 +503,9 @@ def resolve_libraries(
|
||||
bundled_names.add(name)
|
||||
bundled.append(_bundled_library(framework_path, name))
|
||||
|
||||
_warn_unfulfilled_provides(
|
||||
backend.provided_requests,
|
||||
bundled_names | converted_manifest_names | external_short_names,
|
||||
)
|
||||
|
||||
return bundled + converted
|
||||
|
||||
@@ -351,8 +351,10 @@ class LibraryBackend:
|
||||
cache_key: str
|
||||
# Owner-less dependency names this returns True for are skipped by the
|
||||
# walk; the backend supplies them outside the registry (e.g. core-bundled
|
||||
# libraries).
|
||||
# libraries). The walk records every skipped name in provided_requests
|
||||
# so the backend can reconcile its promise after resolving.
|
||||
provides: Callable[[str], bool] | None = None
|
||||
provided_requests: list[str] = field(default_factory=list)
|
||||
|
||||
|
||||
def ensure_list[T](obj: T | list[T]) -> list[T]:
|
||||
@@ -1303,6 +1305,7 @@ def convert_libraries(
|
||||
)
|
||||
else:
|
||||
_LOGGER.debug("Skip backend-provided dependency %s", dep_name)
|
||||
backend.provided_requests.append(dep_name)
|
||||
continue
|
||||
dep_key = add_spec(dep_name, dep_version, dep_url)
|
||||
node.edges.add(dep_key)
|
||||
|
||||
@@ -716,6 +716,30 @@ def test_bundled_dependency_dict_shorthand_prefers_bundled(tmp_path: Path) -> No
|
||||
assert "Wire" in [lib.name for lib in libs]
|
||||
|
||||
|
||||
def test_unfulfilled_provides_promise_warns(
|
||||
tmp_path: Path, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""The walk records every provides()-skipped dependency; one nothing
|
||||
added must warn instead of surfacing as link errors, while satisfied
|
||||
ones stay silent."""
|
||||
component._warn_unfulfilled_provides(["Wire", "Hash"], {"Hash"})
|
||||
assert "provides() skipped dependency Wire but nothing added it" in caplog.text
|
||||
assert "Hash" not in caplog.text
|
||||
|
||||
|
||||
def test_fulfilled_provides_promise_is_silent_end_to_end(
|
||||
tmp_path: Path, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""The normal path: the walk records the skip and the backend adds the
|
||||
bundled copy, so the reconciliation stays quiet."""
|
||||
framework = _make_framework(tmp_path)
|
||||
converted = _webserver(tmp_path, {"build": {}, "dependencies": {"Wire": "*"}})
|
||||
with _emitting_converter(converted):
|
||||
libs = _resolve(framework)
|
||||
assert "Wire" in [lib.name for lib in libs]
|
||||
assert "provides() skipped dependency" not in caplog.text
|
||||
|
||||
|
||||
def test_bundled_dependency_platform_rejection_is_debug(
|
||||
tmp_path: Path, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
|
||||
Reference in New Issue
Block a user