Fail by name on a corrupt bundled manifest; name the recording side effect

A truncated bundled library.json now raises the module's named error
with the clean-all hint instead of a raw JSONDecodeError. The renamed
_reconcile_versionless_skips advertises that it records into
backend.provided_requests. Two real-converter tests gain the
registry-guard patch their siblings use so a regression fails offline.
This commit is contained in:
J. Nick Koston
2026-08-25 22:52:39 -05:00
parent 112868414e
commit 3fa4d478e5
3 changed files with 34 additions and 7 deletions
+7 -1
View File
@@ -257,7 +257,13 @@ def _bundled_library(framework_path: Path, name: str) -> ArduinoLibrary:
lib_dir = framework_path / "libraries" / name
manifest_json = lib_dir / "library.json"
if manifest_json.is_file():
data = parse_library_json(manifest_json)
try:
data = parse_library_json(manifest_json)
except ValueError as err: # JSONDecodeError
raise EsphomeError(
f"Bundled library {name} has a corrupt library.json ({err}); "
"the framework install may be incomplete (run 'esphome clean-all')"
) from err
elif (manifest := lib_dir / "library.properties").is_file():
data = parse_library_properties(manifest)
else:
+6 -4
View File
@@ -924,13 +924,15 @@ def is_lib_ignored(name: str | None, lib_ignore: set[str]) -> bool:
)
def _warn_unsatisfied_versionless(
def _reconcile_versionless_skips(
skipped_versionless: list[tuple[Any, Any, str]],
components: dict[str, ConvertedLibrary],
backend: LibraryBackend,
) -> None:
"""Warn for version-less deps nothing satisfied; a silent drop
surfaces as link errors far from the cause."""
"""Warn for version-less deps nothing satisfied, and record the
backend-provided ones in ``backend.provided_requests`` for its
post-emit reconciliation; a silent drop surfaces as link errors far
from the cause."""
resolved_manifest_names = {c.data.get("name") for c in components.values()}
# A treeless backend can never supply a bundled name; noise for it
log = _LOGGER.warning if backend.provides is not None else _LOGGER.debug
@@ -1355,6 +1357,6 @@ def convert_libraries(
for component in components.values():
backend.emit(component)
_warn_unsatisfied_versionless(skipped_versionless, components, backend)
_reconcile_versionless_skips(skipped_versionless, components, backend)
return [components[key] for key in top_level if key in components]
+21 -2
View File
@@ -900,6 +900,15 @@ def test_bundled_library_non_dict_manifest_skips_probes_and_raises(
component._bundled_library(framework, "Wire")
def test_bundled_corrupt_library_json_fails_by_name(tmp_path: Path) -> None:
"""A truncated bundled library.json fails with the library name and the
clean-all hint, not a raw JSONDecodeError."""
framework = _make_framework(tmp_path)
(framework / "libraries" / "Wire" / "library.json").write_text("{truncated")
with pytest.raises(EsphomeError, match="Wire has a corrupt library.json"):
component._bundled_library(framework, "Wire")
def test_dict_shorthand_dependency_skips_registry_through_real_converter(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
@@ -931,7 +940,12 @@ def test_versionless_provides_skip_is_reconciled_through_real_converter(
framework = _make_framework(tmp_path)
_local_lib(tmp_path, ["Wire"])
monkeypatch.setenv("ESPHOME_DATA_DIR", str(tmp_path / ".esphome"))
libs = _resolve(framework)
with patch.object(
pio_library,
"_resolve_registry_version",
side_effect=AssertionError("registry touched"),
):
libs = _resolve(framework)
assert "Wire" in [lib.name for lib in libs]
@@ -943,7 +957,12 @@ def test_platform_filtered_bundled_candidate_does_not_break_reconciliation(
framework = _make_framework(tmp_path)
_local_lib(tmp_path, [{"name": "Wire", "platforms": ["espressif32"]}])
monkeypatch.setenv("ESPHOME_DATA_DIR", str(tmp_path / ".esphome"))
libs = _resolve(framework)
with patch.object(
pio_library,
"_resolve_registry_version",
side_effect=AssertionError("registry touched"),
):
libs = _resolve(framework)
assert "Wire" not in [lib.name for lib in libs]