diff --git a/esphome/build_gen/arduino8266.py b/esphome/build_gen/arduino8266.py index 330834603a..f37cf30830 100644 --- a/esphome/build_gen/arduino8266.py +++ b/esphome/build_gen/arduino8266.py @@ -72,7 +72,12 @@ _CORE_EXCLUDE_WAVEFORM = { # Values that land unquoted on generated command lines are shape-checked # against these before use +_MMU_VALUE_RE = re.compile(r"(?:0[xX][0-9a-fA-F]+|\d+)[uUlL]*") _MMU_HEX_VALUE_RE = re.compile(r"0[xX][0-9a-fA-F]+[uUlL]*") +# Only these land in the preprocessed script's ``len =`` fields, which +# build_surgery's segment parser reads back as hex; the other MMU_* macros +# (MMU_EXTERNAL_HEAP=128) are consumed by mmu_iram.h and may be decimal +_MMU_SEGMENT_SIZE_NAMES = ("MMU_IRAM_SIZE", "MMU_ICACHE_SIZE") _BOARD_NAME_RE = re.compile(r"[\w.-]+") _F_CPU_RE = re.compile(r"\d+L?") _FLASH_LD_NAME_RE = re.compile(r"[\w.-]+\.ld") @@ -378,14 +383,21 @@ def _resolve_build_config(defines: dict[str, str]) -> _BuildConfig: # 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. + # bare or non-numeric value would corrupt it and fail far away + # in ld. The two segment sizes must additionally be hex: + # build_surgery's segment parser cannot read decimal back. value = body.partition("=")[2] - if not _MMU_HEX_VALUE_RE.fullmatch(value): + rule = ( + _MMU_HEX_VALUE_RE if name in _MMU_SEGMENT_SIZE_NAMES else _MMU_VALUE_RE + ) + if not rule.fullmatch(value): + shape = ( + "a hex literal (e.g. 0x8000)" + if name in _MMU_SEGMENT_SIZE_NAMES + else "a numeric literal" + ) raise EsphomeError( - f"{name} must be a hex literal (e.g. 0x8000), got " - f"{value or '(no value)'}" + f"{name} must be {shape}, got {value or '(no value)'}" ) # Sorted so build.ninja and the linker-script stamp stay # byte-stable across runs (the flag set has no deterministic diff --git a/tests/unit_tests/build_gen/test_arduino8266.py b/tests/unit_tests/build_gen/test_arduino8266.py index c5d3bf9747..94a0cb50db 100644 --- a/tests/unit_tests/build_gen/test_arduino8266.py +++ b/tests/unit_tests/build_gen/test_arduino8266.py @@ -1176,7 +1176,7 @@ def test_mmu_custom_valueless_switch_accepted_and_others_validated() -> None: "-DMMU_IRAM_HEAP", ) assert "MMU_IRAM_HEAP" in config.mmu_defines - with pytest.raises(EsphomeError, match="MMU_SEC_HEAP_SIZE must be a hex"): + with pytest.raises(EsphomeError, match="MMU_SEC_HEAP_SIZE must be a numeric"): _resolve( "-DPIO_FRAMEWORK_ARDUINO_MMU_CUSTOM", "-DMMU_IRAM_SIZE=0x8000", @@ -1185,6 +1185,19 @@ def test_mmu_custom_valueless_switch_accepted_and_others_validated() -> None: ) +def test_mmu_custom_accepts_decimal_non_segment_values() -> None: + """MMU_EXTERNAL_HEAP=128 (the module's own EXTERNAL_128K shape) is a + mmu_iram.h count, not a segment length; decimal is legal there while + the two segment sizes stay hex-only for the surgery parser.""" + config = _resolve( + "-DPIO_FRAMEWORK_ARDUINO_MMU_CUSTOM", + "-DMMU_IRAM_SIZE=0x8000", + "-DMMU_ICACHE_SIZE=0x8000", + "-DMMU_EXTERNAL_HEAP=128", + ) + assert "MMU_EXTERNAL_HEAP=128" in config.mmu_defines + + 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."""