diff --git a/esphome/components/esp8266/boards.py b/esphome/components/esp8266/boards.py index e5a9628e90..2646682766 100644 --- a/esphome/components/esp8266/boards.py +++ b/esphome/components/esp8266/boards.py @@ -362,9 +362,10 @@ BOARDS = { } """ -ESP8266_BOARD_BUILD generate with: +ESP8266_BOARD_BUILD generate with (v4.2.1 is the platform version the +native toolchain mirrors; regenerate against the tag when bumping it): -git clone https://github.com/platformio/platform-espressif8266 +git clone -b v4.2.1 https://github.com/platformio/platform-espressif8266 python3 - <<'EOF' import json, glob, os for f in sorted(glob.glob("platform-espressif8266/boards/*.json")): diff --git a/esphome/components/esp8266/build_surgery.py b/esphome/components/esp8266/build_surgery.py index c0034f7bad..47f9042f08 100644 --- a/esphome/components/esp8266/build_surgery.py +++ b/esphome/components/esp8266/build_surgery.py @@ -58,21 +58,33 @@ _TESTING_SEGMENT_SIZES = { def _segment_line_re(segment_name: str) -> re.Pattern[str]: - """The MEMORY line for one segment: `` : org = 0x..., len = 0x...``.""" + """The MEMORY line for one segment: `` : org = 0x..., len = 0x...``. + + Anchored to the start of the line so a name never matches inside a + longer one (``ram0_0_seg`` must not read ``dram0_0_seg``). The size + group stops at the hex digits, leaving any ``ul`` suffix (from the + preprocessed ``MMU_IRAM_SIZE``) in place. + """ return re.compile( - rf"({segment_name}\s*:\s*org\s*=\s*0x[0-9a-fA-F]+\s*,\s*len\s*=\s*)" - r"(0x[0-9a-fA-F]+)" + rf"(^[ \t]*{re.escape(segment_name)}" + r"\s*:\s*org\s*=\s*0x[0-9a-fA-F]+\s*,\s*len\s*=\s*)" + r"(0x[0-9a-fA-F]+)", + re.MULTILINE, ) def apply_testing_memory_patches(content: str, segments: Collection[str]) -> str: """Enlarge the named memory segments so grouped CI test builds can link. - Each caller passes the segments its linker script defines; a segment - that fails to match raises, since a silently kept real memory limit - would fail grouped builds far from the cause. + Each caller passes the segments its linker script defines: the + generated common ld carries ``iram1_0_seg``; the flash ld carries + ``dram0_0_seg`` and ``irom0_0_seg``. A segment that fails to match + raises, since a silently kept real memory limit would fail grouped + builds far from the cause. """ for segment in segments: + if segment not in _TESTING_SEGMENT_SIZES: + raise RuntimeError(f"Unknown testing-mode segment {segment!r}") content, count = _segment_line_re(segment).subn( rf"\g<1>{_TESTING_SEGMENT_SIZES[segment]}", content ) diff --git a/tests/unit_tests/components/esp8266/test_build_surgery.py b/tests/unit_tests/components/esp8266/test_build_surgery.py index 64e36f1591..e62a2270fb 100644 --- a/tests/unit_tests/components/esp8266/test_build_surgery.py +++ b/tests/unit_tests/components/esp8266/test_build_surgery.py @@ -24,16 +24,26 @@ _COMMON_LD_SNIPPET = """\ } >dram0_0_seg :dram0_0_phdr """ +# Shaped like the real SDK flash ld scripts: no iram1_0_seg (that lives in +# the generated common ld only) _FLASH_LD_SNIPPET = """\ MEMORY { dport0_0_seg : org = 0x3FF00000, len = 0x10 dram0_0_seg : org = 0x3FFE8000, len = 0x14000 - iram1_0_seg : org = 0x40100000, len = 0x8000 irom0_0_seg : org = 0x40201010, len = 0xfeff0 } """ +# Shaped like the preprocessed common ld: MMU_IRAM_SIZE expands with a ul +# suffix the patcher must leave in place +_COMMON_LD_MEMORY_SNIPPET = """\ +MEMORY +{ + iram1_0_seg : org = 0x40100000, len = 0x8000ul +} +""" + def test_relocate_ratetable_inserts_after_data_start() -> None: patched = relocate_ratetable(_COMMON_LD_SNIPPET) @@ -52,15 +62,32 @@ def test_relocate_ratetable_requires_anchor() -> None: def test_testing_memory_patches_enlarge_segments() -> None: patched = apply_testing_memory_patches( - _FLASH_LD_SNIPPET, ("iram1_0_seg", "dram0_0_seg", "irom0_0_seg") + _FLASH_LD_SNIPPET, ("dram0_0_seg", "irom0_0_seg") ) - assert segment_length(patched, "iram1_0_seg") == 0x200000 assert segment_length(patched, "dram0_0_seg") == 0x200000 assert segment_length(patched, "irom0_0_seg") == 0x2000000 # Untouched segments keep their sizes assert segment_length(patched, "dport0_0_seg") == 0x10 +def test_testing_memory_patches_keep_ul_suffix() -> None: + """The common ld's preprocessed sizes carry a ul suffix; the patch must + replace only the hex digits, as testing_mode.py.script does.""" + patched = apply_testing_memory_patches(_COMMON_LD_MEMORY_SNIPPET, ("iram1_0_seg",)) + assert "len = 0x200000ul" in patched + assert segment_length(patched, "iram1_0_seg") == 0x200000 + + +def test_segment_length_requires_whole_name() -> None: + """A name must match its own line, never inside a longer segment name.""" + assert segment_length(_FLASH_LD_SNIPPET, "ram0_0_seg") is None + + +def test_testing_memory_patches_unknown_segment_raises() -> None: + with pytest.raises(RuntimeError, match="Unknown testing-mode segment"): + apply_testing_memory_patches("MEMORY { }", ("bogus_seg",)) + + def test_segment_length() -> None: assert segment_length(_FLASH_LD_SNIPPET, "irom0_0_seg") == 0xFEFF0 assert segment_length(_FLASH_LD_SNIPPET, "missing_seg") is None