Merge branch 'esp8266-native-framework-installer' into esp8266-native-library-backend

# Conflicts:
#	tests/unit_tests/test_platformio_library.py
This commit is contained in:
J. Nick Koston
2026-08-20 17:12:49 -05:00
6 changed files with 78 additions and 11 deletions
+7 -4
View File
@@ -73,15 +73,16 @@ def print_summary(size_json: Path, partitions_csv: Path | None) -> None:
"""Print PlatformIO-shaped RAM and Flash one-liners.
Failures are non-fatal: the build has already succeeded, we just couldn't
summarize. Logs the cause at debug level.
summarize. Logs the cause at warning level, so a missing RAM/Flash line
(which CI's memory-impact extraction greps for) is diagnosable.
"""
if not size_json.is_file():
_LOGGER.debug("Skipping size summary: %s not found", size_json)
_LOGGER.warning("Skipping size summary: %s not found", size_json)
return
try:
data = json.loads(size_json.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as e:
_LOGGER.debug("Skipping size summary: %s", e)
_LOGGER.warning("Skipping size summary: %s", e)
return
memory_types = data.get("memory_types", {})
@@ -90,6 +91,8 @@ def print_summary(size_json: Path, partitions_csv: Path | None) -> None:
ram_total = ram_region.get("size")
if ram_total and ram_used is not None:
print_size_line("RAM", ram_used, ram_total)
else:
_LOGGER.warning("Skipping RAM summary: no DRAM/DIRAM region in %s", size_json)
image_size = data.get("image_size")
if image_size is None or partitions_csv is None:
@@ -97,6 +100,6 @@ def print_summary(size_json: Path, partitions_csv: Path | None) -> None:
try:
app_size = _find_app_partition_size(partitions_csv)
except ValueError as e:
_LOGGER.debug("Skipping Flash summary: %s", e)
_LOGGER.warning("Skipping Flash summary: %s", e)
return
print_size_line("Flash", image_size, app_size)
+12 -5
View File
@@ -173,9 +173,9 @@ def run_extra_script(
process CWD so relative-path lookups (``join``, ``realpath``,
``open``) resolve against the library tree.
On any exception inside the script we log at debug level and return
an empty result — extra-scripts are best-effort, and an unsupported
script shouldn't block the build.
On any exception inside the script we warn and return whatever the
script captured before failing — extra-scripts are best-effort, and an
unsupported script shouldn't block the build.
"""
env = _FakeSConsEnv(
board_mcu=board_mcu,
@@ -196,8 +196,15 @@ def run_extra_script(
},
)
except Exception as e: # noqa: BLE001 # pylint: disable=broad-exception-caught
_LOGGER.warning("PIO extra-script %s raised %s; skipping", script_path, e)
return ExtraScriptResult()
# Keep what the script captured before failing: dropping flags it
# already appended would fail later at link time, far from the cause
_LOGGER.warning(
"PIO extra-script %s (in %s) raised %s; keeping the partial capture",
script_path,
library_dir.name,
e,
)
return env.result
finally:
os.chdir(old_cwd)
return env.result
+3 -1
View File
@@ -564,7 +564,9 @@ def split_flag_entry(entry: str, owner: str) -> list[str]:
"""``shlex.split`` with a clean error naming the offending flags entry."""
try:
return shlex.split(entry)
except ValueError as err:
except (ValueError, AttributeError, TypeError) as err:
# AttributeError/TypeError: a dict or number from a third-party
# manifest; name the entry instead of an opaque shlex traceback
raise EsphomeError(f"Malformed build flag {entry!r} in {owner}: {err}") from err
@@ -224,7 +224,7 @@ def test_apply_extra_script_swallows_script_errors(tmp_path, caplog) -> None:
c.data = {"build": {"extraScript": "extra.py"}}
apply_extra_script(c, board_mcu=lambda: "esp8266", pio_platform="espressif8266")
assert "flags" not in c.data["build"]
assert "skipping" in caplog.text
assert "keeping the partial capture" in caplog.text
def test_apply_extra_script_pio_platform(tmp_path) -> None:
@@ -250,3 +250,16 @@ def test_apply_extra_script_missing_script_logged(tmp_path, caplog) -> None:
c.data = {"build": {"extraScript": "nope.py"}}
apply_extra_script(c, board_mcu=lambda: "esp8266", pio_platform="espressif8266")
assert "not found" in caplog.text
def test_run_extra_script_keeps_partial_capture(tmp_path, caplog) -> None:
"""Flags appended before a script fails are kept, not dropped."""
from esphome.platformio.extra_script import run_extra_script
script = tmp_path / "extra.py"
script.write_text("env.Append(LIBS=['algobsec'])\nraise RuntimeError('boom')\n")
result = run_extra_script(
script, library_dir=tmp_path, board_mcu="esp32", pio_platform="espressif32"
)
assert result.libs == ["algobsec"]
assert "keeping the partial capture" in caplog.text
@@ -564,3 +564,16 @@ def test_lex_build_flags_dangling_flag_does_not_cross_entries(
assert lex_build_flags(["-Wall -I", "-DFOO=1"], "lib x") == ["-Wall", "-DFOO=1"]
assert "Ignoring trailing '-I'" in caplog.text
def test_split_flag_entry_non_string_is_clean( # type: ignore[no-untyped-def]
) -> None:
"""A dict or number from a third-party manifest fails naming the entry,
not with an opaque shlex traceback."""
from esphome.core import EsphomeError
from esphome.platformio.library import split_flag_entry
with pytest.raises(EsphomeError, match="Malformed build flag"):
split_flag_entry({"esp32": ["-DX"]}, "lib x")
with pytest.raises(EsphomeError, match="Malformed build flag 5"):
split_flag_entry(5, "lib x")
+29
View File
@@ -149,3 +149,32 @@ def test_print_summary_flash_line(
out = capsys.readouterr().out
assert "RAM: [===== ] 50.0% (used 100 bytes from 200 bytes)" in out
assert "Flash: [ ] 0.0% (used 500 bytes from 1048576 bytes)" in out
def test_print_summary_missing_ram_region_warns(
tmp_path: Path, caplog: pytest.LogCaptureFixture
) -> None:
"""A missing RAM line is diagnosable, not a silently absent CI metric."""
size_json = _write_size_json(tmp_path, {"memory_types": {}, "image_size": 100})
print_summary(size_json, partitions_csv=None)
assert "Skipping RAM summary" in caplog.text
def test_print_summary_bad_partitions_warns(
tmp_path: Path, caplog: pytest.LogCaptureFixture
) -> None:
"""An unparseable partition table skips the Flash line with a warning."""
size_json = _write_size_json(tmp_path, _esp32_size_data())
partitions = tmp_path / "partitions.csv"
partitions.write_text("not,a,valid,partition,table\n")
print_summary(size_json, partitions_csv=partitions)
assert "Skipping Flash summary" in caplog.text
def test_print_summary_corrupt_json_warns(
tmp_path: Path, caplog: pytest.LogCaptureFixture
) -> None:
size_json = tmp_path / "size.json"
size_json.write_text("{not json")
print_summary(size_json, partitions_csv=None)
assert "Skipping size summary" in caplog.text