Unify the MMU guards: hex-only sizes, every valued MMU_* validated, no-knob refuses all

This commit is contained in:
J. Nick Koston
2026-08-22 21:29:31 -05:00
parent 586bc114fa
commit 81afaca560
2 changed files with 54 additions and 16 deletions
+15 -11
View File
@@ -289,15 +289,18 @@ def _resolve_build_config(defines: dict[str, str]) -> _BuildConfig:
"PIO_FRAMEWORK_ARDUINO_MMU_CUSTOM requires MMU_IRAM_SIZE and "
"MMU_ICACHE_SIZE build flags"
)
for size_name in ("MMU_IRAM_SIZE", "MMU_ICACHE_SIZE"):
# These reach the linker-script preprocessor; a bare or
# non-numeric value would corrupt the segment lengths and fail
# far away in ld ("48K" and a valueless flag both preprocess
# wrong; ul suffixes survive preprocessing, see build_surgery)
value = defines[size_name].partition("=")[2]
if not re.fullmatch(r"(?:0[xX][0-9a-fA-F]+|\d+)[uUlL]*", value):
for name, body in defines.items():
if not name.startswith("MMU_") or "=" not in body:
# Valueless flags (MMU_IRAM_HEAP) are legitimate switches
continue
# Every valued MMU_* reaches the linker-script preprocessor; a
# bare or non-numeric value would corrupt the segment lengths
# and fail far away in ld. Hex only: build_surgery's segment
# parser (and upstream's spellings) cannot read decimal.
value = body.partition("=")[2]
if not re.fullmatch(r"0[xX][0-9a-fA-F]+[uUlL]*", value):
raise EsphomeError(
f"{size_name} must be a numeric literal, got "
f"{name} must be a hex literal (e.g. 0x8000), got "
f"{value or '(no value)'}"
)
# Sorted so build.ninja and the linker-script stamp stay
@@ -305,11 +308,12 @@ def _resolve_build_config(defines: dict[str, str]) -> _BuildConfig:
# iteration order).
mmu = sorted(body for name, body in defines.items() if name.startswith("MMU_"))
else:
if "MMU_IRAM_SIZE" in defines or "MMU_ICACHE_SIZE" in defines:
if raw := sorted(n for n in defines if n.startswith("MMU_")):
# Unlike PlatformIO (whose defaults win the compile line), user
# MMU_* here would win the compile but not the linker script; refuse.
# MMU_* here would win the compile but not the linker script;
# refuse them all, like the knob branch above.
raise EsphomeError(
"Custom MMU_IRAM_SIZE/MMU_ICACHE_SIZE build flags require "
f"Raw {', '.join(raw)} build flags require "
"-DPIO_FRAMEWORK_ARDUINO_MMU_CUSTOM"
)
mmu = list(_MMU_DEFAULT)
+39 -5
View File
@@ -794,7 +794,7 @@ def test_generate_ld_scripts_unreadable_note_still_warns(
assert "could not be read" in caplog.text
@pytest.mark.parametrize("value", ["0x8000", "0xC000ul", "32768", "48UL"])
@pytest.mark.parametrize("value", ["0x8000", "0xC000ul", "0x10UL"])
def test_mmu_custom_numeric_sizes_accepted(value: str) -> None:
config = _resolve(
"-DPIO_FRAMEWORK_ARDUINO_MMU_CUSTOM",
@@ -804,11 +804,19 @@ def test_mmu_custom_numeric_sizes_accepted(value: str) -> None:
assert f"MMU_IRAM_SIZE={value}" in config.mmu_defines
@pytest.mark.parametrize("flag", ["-DMMU_IRAM_SIZE=48K", "-DMMU_IRAM_SIZE"])
@pytest.mark.parametrize(
"flag",
[
"-DMMU_IRAM_SIZE=48K",
# Decimal passes preprocessing but build_surgery's segment parser
# only reads hex, so testing-mode surgery would fail misleadingly
"-DMMU_IRAM_SIZE=32768",
],
)
def test_mmu_custom_malformed_size_raises(flag: str) -> None:
"""A bare or non-numeric size would corrupt the preprocessed segment
lengths and fail far away in ld; refuse by name."""
with pytest.raises(EsphomeError, match="MMU_IRAM_SIZE must be a numeric"):
"""A non-hex size would corrupt the preprocessed segment lengths (or
defeat the testing-mode surgery); refuse by name."""
with pytest.raises(EsphomeError, match="MMU_IRAM_SIZE must be a hex"):
_resolve(
"-DPIO_FRAMEWORK_ARDUINO_MMU_CUSTOM",
flag,
@@ -816,6 +824,32 @@ def test_mmu_custom_malformed_size_raises(flag: str) -> None:
)
def test_mmu_custom_valueless_switch_accepted_and_others_validated() -> None:
"""Valueless MMU switches (MMU_IRAM_HEAP) pass; every valued MMU_* is
hex-validated, not just the two required sizes."""
config = _resolve(
"-DPIO_FRAMEWORK_ARDUINO_MMU_CUSTOM",
"-DMMU_IRAM_SIZE=0x8000",
"-DMMU_ICACHE_SIZE=0x8000",
"-DMMU_IRAM_HEAP",
)
assert "MMU_IRAM_HEAP" in config.mmu_defines
with pytest.raises(EsphomeError, match="MMU_SEC_HEAP_SIZE must be a hex"):
_resolve(
"-DPIO_FRAMEWORK_ARDUINO_MMU_CUSTOM",
"-DMMU_IRAM_SIZE=0x8000",
"-DMMU_ICACHE_SIZE=0x8000",
"-DMMU_SEC_HEAP_SIZE=48K",
)
def test_mmu_no_knob_rejects_any_raw_mmu_flag() -> None:
"""The no-knob branch refuses every raw MMU_*, like the knob branch; a
lone switch would win the compile line but not the linker script."""
with pytest.raises(EsphomeError, match="Raw MMU_IRAM_HEAP"):
_resolve("-DMMU_IRAM_HEAP")
def test_raw_nonosdk_define_raises() -> None:
"""A raw NONOSDK* define would split the compile line from the linked
SDK libraries, like the lwIP knob overrides."""