Merge branch 'esp8266-native-build-spec' into esp8266-native-ninja-emission

This commit is contained in:
J. Nick Koston
2026-08-22 22:58:48 -05:00
9 changed files with 184 additions and 28 deletions
+24
View File
@@ -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:
+20
View File
@@ -940,6 +940,26 @@ def test_prefetch_all_entries_unverifiable_is_a_noop(tmp_path: Path) -> None:
download.assert_not_called()
def test_prefetch_dedupes_entries_by_dest(tmp_path: Path) -> None:
"""Two entries resolving to one dest would interleave writes into the
same .part file; only the first downloads."""
entries = json.loads(_PREFETCH_JSON)
dup = dict(entries[0]) | {"name": "cmake-alias@3.30.2"}
entries.append(dup)
with (
patch(
"esphome.espidf.framework.run_command",
return_value=(True, json.dumps(entries), ""),
),
patch("esphome.espidf.framework.download_with_resume") as download,
patch("esphome.espidf.framework.get_system_python_path", return_value="python"),
patch("esphome.espidf.framework.BatchDownloadProgress"),
):
_prefetch_idf_tool_archives(tmp_path, "esp32", ["required"], None)
dests = [call[0][1].name for call in download.call_args_list]
assert dests.count("cmake-3.30.2.tar.gz") == 1
def test_prefetch_downloads_each_archive_with_resume(tmp_path: Path) -> None:
with (
patch(
@@ -169,6 +169,30 @@ def test_apply_extra_script_callable_target_and_str_flags(tmp_path) -> None:
assert c.data["build"]["flags"] == ["-DBASE=1", "-lesp8266"]
def test_captured_nonstring_buckets_warn_and_skip(tmp_path, caplog) -> None:
"""Non-string LIBS/LINKFLAGS/CPPFLAGS/LIBPATH entries (legal SCons
nodes) are skipped by name instead of stringified into garbage flags."""
(tmp_path / "src").mkdir()
script = tmp_path / "extra.py"
script.write_text(
"env.Append(LIBS=['m', 42], LINKFLAGS=['-Wl,-x', {'no': 1}], "
"CPPFLAGS=['-Os', 3.5], LIBPATH=['libs', 7])\n"
)
(tmp_path / "libs").mkdir()
c = IDFComponent("owner/name", "1.0", source=URLSource("http://dummy"))
c.path = tmp_path
c.data = {"build": {"extraScript": "extra.py"}}
apply_extra_script(c, board_mcu=lambda: "esp8266", pio_platform="espressif8266")
flags = c.data["build"]["flags"]
assert "-lm" in flags and "-Wl,-x" in flags and "-Os" in flags
assert not any("42" in f or "no" in f or "3.5" in f for f in flags)
assert "Ignoring unsupported LIBS entry 42" in caplog.text
assert "Ignoring unsupported LIBPATH entry 7" in caplog.text
def test_captured_dict_cppdefines_warn_and_skip(tmp_path, caplog) -> None:
"""A dict CPPDEFINES entry (legal SCons) must warn and skip; formatting
it blind would hand the compiler -D{'FOO': '1'} garbage."""
@@ -259,6 +259,37 @@ def _patch_download_with_manifests(monkeypatch, tmp_path, manifests, *, properti
_patch_registry_resolve(monkeypatch)
def test_wave_requirement_growth_defers_the_superseded_download(tmp_path, monkeypatch):
"""A's manifest constrains B while B sits in the same wave: B's
drain-time resolution is superseded, so its download defers to the
next wave instead of fetching a version that is immediately replaced."""
download_names: list[str] = []
manifests = {
"esphome/A": {
"name": "A",
"build": {},
"dependencies": {"esphome/B": ">=1.0"},
},
"esphome/B": {"name": "B", "build": {}},
}
def fake_download(self, force=False, salt="", namespace="", progress=None):
download_names.append(self.name)
self.path = tmp_path / self.get_require_name()
self.path.mkdir(parents=True, exist_ok=True)
(self.path / "library.json").write_text(json.dumps(manifests[self.name]))
monkeypatch.setattr(ConvertedLibrary, "download", fake_download)
_patch_registry_resolve(monkeypatch)
top = convert_libraries(
[Library("esphome/A", "1.0.0", None), Library("esphome/B", None, None)],
_backend(),
)
assert sorted(c.name for c in top) == ["esphome/A", "esphome/B"]
# B downloads exactly once, after its requirement set stabilized
assert download_names.count("esphome/B") == 1
def test_convert_libraries_parses_library_properties(tmp_path, monkeypatch):
# A manifest provided as library.properties (Arduino style) instead of
# library.json must still be parsed and converted.
@@ -848,6 +879,7 @@ def test_walk_warns_for_properties_only_depends(
{"esphome/A": "name=A\nversion=1.0\ndepends=Wire, SPI\n"},
properties=("esphome/A",),
)
caplog.set_level("INFO")
convert_libraries([Library("esphome/A", "1.0.0", None)], _backend())
assert "declares dependencies via library.properties" in caplog.text