mirror of
https://github.com/esphome/esphome.git
synced 2026-08-22 22:26:21 +00:00
Merge branch 'esp8266-native-library-backend' into esp8266-native-build-spec
This commit is contained in:
+21
-16
@@ -73,6 +73,17 @@ class ArduinoLibrary:
|
|||||||
link_flags: list[str] = field(default_factory=list)
|
link_flags: list[str] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_safe_library_name(name: object) -> bool:
|
||||||
|
"""Whether a name may be joined under the framework's libraries dir."""
|
||||||
|
return (
|
||||||
|
isinstance(name, str)
|
||||||
|
and bool(name)
|
||||||
|
and "/" not in name
|
||||||
|
and "\\" not in name
|
||||||
|
and name not in (".", "..")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _manifest_build(name: str, data: object) -> dict:
|
def _manifest_build(name: str, data: object) -> dict:
|
||||||
"""The manifest's ``build`` section, validated by name.
|
"""The manifest's ``build`` section, validated by name.
|
||||||
|
|
||||||
@@ -227,12 +238,11 @@ def _bundled_library(framework_path: Path, name: str) -> ArduinoLibrary:
|
|||||||
for d in lib.include_dirs
|
for d in lib.include_dirs
|
||||||
for p in d.rglob("*")
|
for p in d.rglob("*")
|
||||||
):
|
):
|
||||||
# An empty or half-extracted bundled directory would otherwise
|
# An empty or half-extracted bundled directory can never link; a
|
||||||
# become a silent no-op that surfaces as undefined symbols at link
|
# warning would scroll away and resurface as undefined symbols
|
||||||
_LOGGER.warning(
|
raise EsphomeError(
|
||||||
"Bundled library %s has no sources or headers; the framework "
|
f"Bundled library {name} has no sources or headers; the "
|
||||||
"install may be incomplete (run 'esphome clean-all')",
|
"framework install may be incomplete (run 'esphome clean-all')"
|
||||||
name,
|
|
||||||
)
|
)
|
||||||
return lib
|
return lib
|
||||||
|
|
||||||
@@ -266,8 +276,7 @@ def resolve_libraries(
|
|||||||
if (
|
if (
|
||||||
not library.repository
|
not library.repository
|
||||||
and not library.version
|
and not library.version
|
||||||
and library.name
|
and _is_safe_library_name(library.name)
|
||||||
and "/" not in library.name
|
|
||||||
and (framework_path / "libraries" / library.name).is_dir()
|
and (framework_path / "libraries" / library.name).is_dir()
|
||||||
):
|
):
|
||||||
# A bundled library's own manifest dependencies are not walked.
|
# A bundled library's own manifest dependencies are not walked.
|
||||||
@@ -304,13 +313,7 @@ def resolve_libraries(
|
|||||||
component.data.get("dependencies"), component.name
|
component.data.get("dependencies"), component.name
|
||||||
):
|
):
|
||||||
name = dep.get("name")
|
name = dep.get("name")
|
||||||
if (
|
if not _is_safe_library_name(name):
|
||||||
not name
|
|
||||||
or not isinstance(name, str)
|
|
||||||
or "/" in name
|
|
||||||
or "\\" in name
|
|
||||||
or name in (".", "..")
|
|
||||||
):
|
|
||||||
# The name becomes a path component under the framework
|
# The name becomes a path component under the framework
|
||||||
# tree; never join a traversal or a non-string
|
# tree; never join a traversal or a non-string
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
@@ -421,7 +424,9 @@ def resolve_libraries(
|
|||||||
# The shared converter skips version-less deps too, so this is the only
|
# The shared converter skips version-less deps too, so this is the only
|
||||||
# place a genuine drop can be made visible before the missing sources
|
# place a genuine drop can be made visible before the missing sources
|
||||||
# surface as link errors; names the walk resolved anyway stay quiet.
|
# surface as link errors; names the walk resolved anyway stay quiet.
|
||||||
resolved_short_names = {c.name.split("__")[-1] for c in converted}
|
# Split once from the left: strip the sanitized owner prefix only, so a
|
||||||
|
# library whose own name carries "__" still matches
|
||||||
|
resolved_short_names = {c.name.split("__", 1)[-1] for c in converted}
|
||||||
for name, requester in pending_drops:
|
for name, requester in pending_drops:
|
||||||
if name in bundled_names or name in resolved_short_names:
|
if name in bundled_names or name in resolved_short_names:
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -991,20 +991,35 @@ def convert_libraries(
|
|||||||
component.data.get("dependencies"), component.name
|
component.data.get("dependencies"), component.name
|
||||||
):
|
):
|
||||||
if "name" not in dependency or "version" not in dependency:
|
if "name" not in dependency or "version" not in dependency:
|
||||||
if backend.provides is None:
|
dep_name = dependency.get("name")
|
||||||
|
if (
|
||||||
|
isinstance(dep_name, str)
|
||||||
|
and _node_key(dep_name, None, None)[0] in nodes
|
||||||
|
):
|
||||||
|
# Already requested top-level: present in the build, not
|
||||||
|
# a drop (a false warning teaches users to ignore the
|
||||||
|
# real one)
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Version-less dependency %r of %s is requested top-level",
|
||||||
|
dep_name,
|
||||||
|
component.name,
|
||||||
|
)
|
||||||
|
elif backend.provides is None:
|
||||||
# No backend tree can supply it and the registry cannot
|
# No backend tree can supply it and the registry cannot
|
||||||
# resolve it: a real drop, not a routine skip
|
# resolve it: a real drop, not a routine skip
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"Dependency %r of %s has no version to resolve; skipping",
|
"Dependency %r of %s has no version to resolve; skipping",
|
||||||
dependency.get("name"),
|
dep_name,
|
||||||
component.name,
|
component.name,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# The backend (e.g. the arduino bundled tree) picks
|
# A provides backend owns the post-emit reporting (the
|
||||||
# these up after emit
|
# arduino backend defers drops and suppresses names the
|
||||||
|
# walk resolved, which this layer cannot know yet), so
|
||||||
|
# warning here would duplicate or false-positive
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Skip version-less dependency %r of %s",
|
"Skip version-less dependency %r of %s",
|
||||||
dependency.get("name"),
|
dep_name,
|
||||||
component.name,
|
component.name,
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -907,8 +907,7 @@ def test_pinned_bundled_dependency_substitution_warns(
|
|||||||
'{"name": "LocalLib", "version": "1.0.0", "dependencies": {"Wire": "^2.0.0"}}'
|
'{"name": "LocalLib", "version": "1.0.0", "dependencies": {"Wire": "^2.0.0"}}'
|
||||||
)
|
)
|
||||||
_add_library(local_lib.as_uri(), None)
|
_add_library(local_lib.as_uri(), None)
|
||||||
CORE.config_path = tmp_path / "test.yaml"
|
monkeypatch.setenv("ESPHOME_DATA_DIR", str(tmp_path / ".esphome"))
|
||||||
CORE.config_path.write_text("")
|
|
||||||
with patch.object(
|
with patch.object(
|
||||||
pio_library,
|
pio_library,
|
||||||
"_resolve_registry_version",
|
"_resolve_registry_version",
|
||||||
@@ -955,18 +954,20 @@ def test_transitively_resolved_dependency_does_not_warn(
|
|||||||
def test_empty_bundled_library_warns(
|
def test_empty_bundled_library_warns(
|
||||||
tmp_path: Path, caplog: pytest.LogCaptureFixture
|
tmp_path: Path, caplog: pytest.LogCaptureFixture
|
||||||
) -> None:
|
) -> None:
|
||||||
"""A bundled directory with no sources or headers is a broken install,
|
"""A bundled directory with no sources or headers is a broken install
|
||||||
not a silent no-op archive."""
|
that can never link; fail by name instead of warning into it."""
|
||||||
framework = _make_framework(tmp_path)
|
framework = _make_framework(tmp_path)
|
||||||
(framework / "libraries" / "Empty").mkdir()
|
(framework / "libraries" / "Empty").mkdir()
|
||||||
_add_library("Empty", None)
|
_add_library("Empty", None)
|
||||||
|
with pytest.raises(
|
||||||
|
EsphomeError, match="Bundled library Empty has no sources or headers"
|
||||||
|
):
|
||||||
component.resolve_libraries(
|
component.resolve_libraries(
|
||||||
framework,
|
framework,
|
||||||
pio_platform="espressif8266",
|
pio_platform="espressif8266",
|
||||||
board_mcu="esp8266",
|
board_mcu="esp8266",
|
||||||
cache_key="arduino8266",
|
cache_key="arduino8266",
|
||||||
)
|
)
|
||||||
assert "Bundled library Empty has no sources or headers" in caplog.text
|
|
||||||
|
|
||||||
|
|
||||||
def test_versionless_dependency_with_provider_stays_quiet(
|
def test_versionless_dependency_with_provider_stays_quiet(
|
||||||
|
|||||||
@@ -645,3 +645,23 @@ def test_walk_warns_for_nonplatform_invalid_library(
|
|||||||
monkeypatch.setattr(lib, "check_library_data", flaky)
|
monkeypatch.setattr(lib, "check_library_data", flaky)
|
||||||
convert_libraries([Library("esphome/A", None, None)], _backend())
|
convert_libraries([Library("esphome/A", None, None)], _backend())
|
||||||
assert "Skipping dependency B of esphome/A: manifest is corrupt" in caplog.text
|
assert "Skipping dependency B of esphome/A: manifest is corrupt" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
def test_versionless_dependency_requested_top_level_stays_quiet(
|
||||||
|
tmp_path, monkeypatch, caplog: pytest.LogCaptureFixture
|
||||||
|
) -> None:
|
||||||
|
"""A version-less dependency the config also requests top-level is in
|
||||||
|
the build; no drop warning even without a provides backend."""
|
||||||
|
_patch_download_with_manifests(
|
||||||
|
monkeypatch,
|
||||||
|
tmp_path,
|
||||||
|
{
|
||||||
|
"esphome/A": {"name": "A", "dependencies": [{"name": "Hash"}]},
|
||||||
|
"Hash": {"name": "Hash"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
convert_libraries(
|
||||||
|
[Library("esphome/A", None, None), Library("Hash", None, None)],
|
||||||
|
_backend(),
|
||||||
|
)
|
||||||
|
assert "has no version to resolve" not in caplog.text
|
||||||
|
|||||||
Reference in New Issue
Block a user