Log a manifest-name match and ignore a dependency on the requester itself

A match found only through a library's manifest name is logged at info,
so a chance name collision can be pinned to the right package. A
manifest listing its own name as a dependency no longer fetches a
second copy of itself.
This commit is contained in:
J. Nick Koston
2026-10-21 12:52:08 -10:00
parent a1d730704e
commit 0d9402cf82
2 changed files with 56 additions and 5 deletions
+23
View File
@@ -1002,10 +1002,28 @@ def _satisfying_key(
for key, component in candidates:
manifest_name = component.data.get("name")
if isinstance(manifest_name, str) and manifest_name.lower() == wanted:
# Visible, so a chance name collision can be pinned to the right package
_LOGGER.info(
"Dependency %s of %s satisfied by %s through its manifest name",
name,
requester,
key,
)
return key
return None
def _own_names(
key: str, nodes: dict[str, _LibNode], components: dict[str, ConvertedLibrary]
) -> set[str]:
"""The lower-case key, package and manifest names a resolved library goes by."""
names = {key.lower(), (nodes[key].pkgname or key).lower()}
manifest_name = components[key].data.get("name")
if isinstance(manifest_name, str):
names.add(manifest_name.lower())
return names
def _fetch_source(
component: ConvertedLibrary,
salt: str,
@@ -1248,6 +1266,11 @@ def convert_libraries(
for requester, dependencies in pending_versionless.items():
for dependency in dependencies:
name, owner = dependency["name"], dependency.get("owner")
if not owner and name.lower() in _own_names(
requester, nodes, components
):
_LOGGER.debug("Dependency %s of %s names itself", name, requester)
continue
# A library in the graph beats a bundled copy, as on the Arduino backend
if (
match := _satisfying_key(name, owner, requester, nodes, components)
+33 -5
View File
@@ -1261,7 +1261,7 @@ def test_versionless_url_ish_dependency_name_warns_cleanly(
def test_versionless_dependency_matching_resolved_manifest_name_is_wired(
tmp_path, monkeypatch
tmp_path, monkeypatch, caplog: pytest.LogCaptureFixture
) -> None:
"""A bare name no package name matches is satisfied by a listed component's
manifest name (a fork published under another package name)."""
@@ -1273,12 +1273,40 @@ def test_versionless_dependency_matching_resolved_manifest_name_is_wired(
"esphome/B-fork": {"name": "B"},
},
)
top = convert_libraries(
[Library("esphome/A", None, None), Library("esphome/B-fork", None, None)],
_backend(),
)
with caplog.at_level(logging.INFO):
top = convert_libraries(
[Library("esphome/A", None, None), Library("esphome/B-fork", None, None)],
_backend(),
)
assert [d.name for d in top[0].dependencies] == ["esphome/B-fork"]
assert asked == [("esphome", "A"), ("esphome", "B-fork")]
assert (
"Dependency B of esphome/A satisfied by esphome/B-fork through its manifest name"
in caplog.text
)
def test_dependency_naming_the_requester_itself_is_ignored(
tmp_path, monkeypatch
) -> None:
"""A manifest that lists its own name as a dependency neither fetches a
second copy of itself nor gains a self edge."""
asked = _patch_download_with_manifests(
monkeypatch,
tmp_path,
{
"esphome/A": "name=A\ndepends=a, esphome/A\n",
# No manifest name: the package name still identifies it
"esphome/C": {"dependencies": [{"name": "C"}]},
},
properties=("esphome/A",),
)
top = convert_libraries(
[Library("esphome/A", "1.0.0", None), Library("esphome/C", "1.0.0", None)],
_backend(),
)
assert [c.dependencies for c in top] == [[], []]
assert asked == [("esphome", "A"), ("esphome", "C")]
def test_versionless_dependency_settles_after_the_graph(