Resolve bare registry libraries at latest version like PlatformIO; tighten review cleanups

This commit is contained in:
J. Nick Koston
2026-08-20 03:07:07 -05:00
parent 702ef56a88
commit 9cf7ff94ac
7 changed files with 47 additions and 35 deletions
+4 -7
View File
@@ -18,7 +18,7 @@ import logging
from pathlib import Path
import shlex
from esphome.core import CORE, EsphomeError, Library
from esphome.core import CORE, Library
from esphome.espidf.extra_script import apply_extra_script
from esphome.platformio.library import (
DEFAULT_BUILD_INCLUDE_DIR,
@@ -140,12 +140,9 @@ def resolve_libraries(framework_path: Path) -> list[ArduinoLibrary]:
elif (framework_path / "libraries" / library.name).is_dir():
bundled.append(_bundled_library(framework_path, library.name))
else:
# PlatformIO fails on an unresolvable lib_deps entry too; building
# without it would only surface as unrelated include/link errors.
raise EsphomeError(
f"Library {library.name} is not bundled with the Arduino "
"framework and has no version or repository to download it from"
)
# A bare registry name; resolved at the latest version, matching
# PlatformIO (a typo fails loudly as a registry lookup error).
external.append(library)
converted: list[ArduinoLibrary] = []
bundled_names = {lib.name for lib in bundled}
+4 -5
View File
@@ -36,6 +36,7 @@ from esphome.framework_helpers import (
str_to_lst_of_str,
)
from esphome.helpers import get_bool_env, get_str_env
from esphome.platformio.library import ensure_list
_LOGGER = logging.getLogger(__name__)
@@ -135,11 +136,9 @@ def _registry_download(
if ver.get("name") != version:
continue
for file in ver.get("files", []):
systems = file.get("system") or "*"
# A bare string would make ``in`` a substring test
if isinstance(systems, str) and systems != "*":
systems = [systems]
if systems == "*" or system in systems:
# ensure_list: a bare string would make ``in`` a substring test
systems = ensure_list(file.get("system") or "*")
if "*" in systems or system in systems:
return (
file["download_url"],
(file.get("checksum") or {}).get("sha256"),
+5 -7
View File
@@ -144,13 +144,11 @@ def _print_size_summary(build_dir: Path, toolchain_path: Path) -> None:
try:
sections[parts[0]] = int(parts[1])
except ValueError:
# A confident total built on a dropped section would feed a
# wrong number to CI's memory-impact metric; skip the summary.
_LOGGER.warning(
"Unparsable size output for section %s; skipping the size summary",
parts[0],
)
return
_LOGGER.warning("Unparsable size output for section %s", parts[0])
if parts[0] in _RAM_SECTIONS or parts[0] in _FLASH_SECTIONS:
# A confident total built on a dropped section would feed
# a wrong number to CI's memory-impact metric
return
ram = sum(sections.get(s, 0) for s in _RAM_SECTIONS)
flash = sum(sections.get(s, 0) for s in _FLASH_SECTIONS)
print(f"RAM: {format_bar(ram, _MAX_RAM_SIZE)}")
+3 -1
View File
@@ -69,7 +69,9 @@ _CORE_EXCLUDE_WAVEFORM = {
"core_esp8266_waveform_phase.cpp",
}
# From platformio-build.py, in its order of precedence (first is the default).
# From platformio-build.py. The first entry is the default; with multiple SDK
# knobs set (a pathological config) ties break by table order, since
# upstream's tie-break depends on define order and is not reproducible here.
_NONOSDK_VERSIONS = (
("SDK22x_190703", "NONOSDK22x_190703"),
("SDK221", "NONOSDK221"),
@@ -252,11 +252,10 @@ def test_write_project_link_line_and_exclusions(tmp_path: Path) -> None:
# -L/-l from esphome build_flags reach the link line, not the compiles
assert '-L"/opt/blobs"' in content
assert "-luser_blob" in content
assert "cflags" not in [
line.split(" = ")[0].strip()
for line in content.splitlines()
if "user_blob" in line
]
for line in content.splitlines():
if line.split(" = ")[0] in ("cflags", "cxxflags", "asflags"):
assert "user_blob" not in line
assert "/opt/blobs" not in line
# System libraries with the selected lwIP variant, in the builder's order
assert (
"-lhal -lphy -lpp -lnet80211 -llwip2-1460 -lwpa -lcrypto -lmain -lwps "
@@ -524,7 +523,8 @@ def test_write_project_missing_framework_dir_raises(tmp_path: Path) -> None:
def test_build_config_nonosdk_precedence() -> None:
"""With two SDK knobs set, the first table entry wins (documented order)."""
"""With two SDK knobs set (a pathological config), ties break
deterministically by table order."""
_set_flags(
"-DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK305",
"-DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK221",
@@ -100,14 +100,15 @@ def test_resolve_libraries_bundled(tmp_path: Path) -> None:
assert [lib.name for lib in libs] == ["ESP8266WiFi"]
def test_resolve_libraries_unknown_bare_name_raises(tmp_path: Path) -> None:
"""An unresolvable library fails the build by name, as PlatformIO does."""
from esphome.core import EsphomeError
def test_resolve_libraries_bare_registry_name_is_external(tmp_path: Path) -> None:
"""A bare name that is not bundled resolves from the registry at the
latest version, matching PlatformIO and the documented libraries: key."""
framework = _make_framework(tmp_path)
_add_library("Typoo", None)
with pytest.raises(EsphomeError, match="Typoo"):
_add_library("pngle", None)
with patch.object(component, "convert_libraries", return_value=[]) as mock_convert:
component.resolve_libraries(framework)
(libraries, _backend), _ = mock_convert.call_args
assert [lib.name for lib in libraries] == ["pngle"]
def _converted(name: str, source_dir: Path, data: dict) -> ConvertedLibrary:
+18 -3
View File
@@ -223,13 +223,14 @@ def test_run_compile_skips_compdb_when_ninja_unchanged(tmp_path: Path) -> None:
run(regenerate_expected=False)
def test_print_size_summary_unparsable_section_skips_summary(
def test_print_size_summary_unparsable_section(
tmp_path: Path,
capsys: pytest.CaptureFixture[str],
caplog: pytest.LogCaptureFixture,
) -> None:
"""A section that fails to parse must not produce a confident wrong total."""
bad = _SIZE_OUTPUT + ".broken abc 0\n"
"""A totals-relevant section that fails to parse must not produce a
confident wrong number; an irrelevant one only warns."""
bad = _SIZE_OUTPUT.replace(".bss 26504", ".bss abc")
with patch.object(
toolchain.subprocess,
"run",
@@ -238,3 +239,17 @@ def test_print_size_summary_unparsable_section_skips_summary(
toolchain._print_size_summary(tmp_path, tmp_path / "toolchain")
assert capsys.readouterr().out == ""
assert "Unparsable size output" in caplog.text
caplog.clear()
harmless = _SIZE_OUTPUT + ".broken abc 0\n"
with (
patch.object(
toolchain.subprocess,
"run",
return_value=MagicMock(returncode=0, stdout=harmless),
),
patch.object(toolchain, "_parse_app_size", return_value=1044464),
):
toolchain._print_size_summary(tmp_path, tmp_path / "toolchain")
assert "RAM:" in capsys.readouterr().out
assert "Unparsable size output" in caplog.text