Merge branch 'esp8266-native-ninja-emission' into esp8266-arduino-toolchain

This commit is contained in:
J. Nick Koston
2026-08-20 13:50:10 -05:00
7 changed files with 49 additions and 68 deletions
+2 -2
View File
@@ -436,7 +436,7 @@ def generate_ld_scripts(
content = build_surgery.relocate_ratetable(result.stdout)
if CORE.testing_mode:
content = build_surgery.apply_testing_memory_patches(
content, require=("iram1_0_seg",)
content, ("iram1_0_seg",)
)
write_file_if_changed(output, content)
stamp.write_text(stamp_content, encoding="utf-8")
@@ -449,7 +449,7 @@ def generate_ld_scripts(
ld_dir / f"testing_{flash_ld_name}",
build_surgery.apply_testing_memory_patches(
flash_ld.read_text(encoding="utf-8"),
require=("dram0_0_seg", "irom0_0_seg"),
("dram0_0_seg", "irom0_0_seg"),
),
)
+25 -31
View File
@@ -5,6 +5,7 @@ These mirror the PlatformIO extra scripts in this directory
inside SCons and must stay self-contained. The native build generator applies
the same patches to the linker scripts it generates, so the logic lives here
as plain functions. Keep both in sync when changing either.
``segment_length`` is native-toolchain-only and has no script twin.
"""
from __future__ import annotations
@@ -45,48 +46,41 @@ def relocate_ratetable(content: str) -> str:
)
_TESTING_SEGMENT_SIZES = (
("iram1_0_seg", TESTING_IRAM_SIZE),
("dram0_0_seg", TESTING_DRAM_SIZE),
("irom0_0_seg", TESTING_FLASH_SIZE),
)
_TESTING_SEGMENT_SIZES = {
"iram1_0_seg": TESTING_IRAM_SIZE,
"dram0_0_seg": TESTING_DRAM_SIZE,
"irom0_0_seg": TESTING_FLASH_SIZE,
}
def _patch_segment_size(content: str, segment_name: str, new_size: str) -> str:
pattern = (
def _segment_line_re(segment_name: str) -> re.Pattern[str]:
"""The MEMORY line for one segment: ``<seg> : org = 0x..., len = 0x...``."""
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]+"
r"(0x[0-9a-fA-F]+)"
)
return re.sub(pattern, rf"\g<1>{new_size}", content)
def apply_testing_memory_patches(content: str, require: Collection[str]) -> str:
"""Enlarge IRAM/DRAM/flash segments so grouped CI test builds can link.
def apply_testing_memory_patches(content: str, segments: Collection[str]) -> str:
"""Enlarge the named memory segments so grouped CI test builds can link.
``require`` names the segments this file must define; a silently
unpatched segment would keep the real memory limits and fail grouped
builds far from the cause. The segments are split across the two linker
scripts (iram1_0_seg in the generated common one, dram0_0_seg and
irom0_0_seg in the flash one), so each caller requires only its own.
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.
"""
missing = set(require)
for segment, size in _TESTING_SEGMENT_SIZES:
patched = _patch_segment_size(content, segment, size)
if patched != content:
missing.discard(segment)
content = patched
if missing:
raise RuntimeError(
f"Testing-mode memory patch failed: segment(s) {', '.join(sorted(missing))} "
"not found (has the Arduino core linker script changed?)"
for segment in segments:
content, count = _segment_line_re(segment).subn(
rf"\g<1>{_TESTING_SEGMENT_SIZES[segment]}", content
)
if count == 0:
raise RuntimeError(
f"Testing-mode memory patch failed: segment {segment} "
"not found (has the Arduino core linker script changed?)"
)
return content
def segment_length(content: str, segment_name: str) -> int | None:
"""Read a memory segment's length from linker script content."""
match = re.search(
rf"{segment_name}\s*:.+len\s*=\s*(0x[\da-fA-F]+)",
content,
)
return int(match.group(1), 16) if match else None
match = _segment_line_re(segment_name).search(content)
return int(match.group(2), 16) if match else None
View File
@@ -25,7 +25,7 @@ from esphome.build_gen.arduino8266 import (
_resolve_build_config,
get_flash_ld_path,
)
from esphome.components.esp8266.boards import BOARDS, ESP8266_BOARD_BUILD
from esphome.components.esp8266.boards import ESP8266_BOARD_BUILD
from esphome.components.esp8266.build_surgery import RATETABLE_RULE
from esphome.components.esp8266.const import (
KEY_BOARD,
@@ -59,11 +59,6 @@ def _set_flags(*flags: str) -> None:
CORE.build_flags = set(flags)
def test_board_build_covers_every_board() -> None:
"""Every supported board must have variant/define metadata."""
assert set(BOARDS) <= set(ESP8266_BOARD_BUILD)
def test_rule_map_covers_all_source_suffixes() -> None:
"""Every suffix a library manifest can select must map to a ninja rule."""
from esphome.platformio.library import SRC_FILE_EXTENSIONS
@@ -4,10 +4,12 @@ from __future__ import annotations
import pytest
from esphome.components.esp8266.boards import BOARDS, ESP8266_BOARD_BUILD
from esphome.components.esp8266.build_surgery import (
RATETABLE_RULE,
apply_testing_memory_patches,
relocate_ratetable,
segment_length,
)
_COMMON_LD_SNIPPET = """\
@@ -49,39 +51,29 @@ def test_relocate_ratetable_requires_anchor() -> None:
def test_testing_memory_patches_enlarge_segments() -> None:
patched = apply_testing_memory_patches(_FLASH_LD_SNIPPET, require=())
assert (
"iram1_0_seg : org = 0x40100000, len = 0x200000"
in patched
)
assert (
"dram0_0_seg : org = 0x3FFE8000, len = 0x200000"
in patched
)
assert (
"irom0_0_seg : org = 0x40201010, len = 0x2000000"
in patched
patched = apply_testing_memory_patches(
_FLASH_LD_SNIPPET, ("iram1_0_seg", "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_segment_length() -> None:
from esphome.components.esp8266.build_surgery import segment_length
assert segment_length(_FLASH_LD_SNIPPET, "irom0_0_seg") == 0xFEFF0
assert segment_length(_FLASH_LD_SNIPPET, "missing_seg") is None
def test_testing_memory_patches_require() -> None:
"""A required segment the patch could not find raises instead of
silently keeping the real memory limits."""
patched = apply_testing_memory_patches(
_FLASH_LD_SNIPPET, require=("dram0_0_seg", "irom0_0_seg")
)
assert "0x2000000" in patched
with pytest.raises(RuntimeError, match="dram0_0_seg, irom0_0_seg"):
apply_testing_memory_patches(
"MEMORY { }", require=("dram0_0_seg", "irom0_0_seg")
)
# Segments a file does not require are patched opportunistically only
# With nothing required, unmatched content passes through unchanged
assert apply_testing_memory_patches("MEMORY { }", require=()) == "MEMORY { }"
def test_testing_memory_patches_missing_segment_raises() -> None:
"""A named segment the patch could not find raises instead of silently
keeping the real memory limits."""
with pytest.raises(RuntimeError, match="dram0_0_seg"):
apply_testing_memory_patches("MEMORY { }", ("dram0_0_seg",))
def test_board_build_covers_every_board() -> None:
"""Every supported board has native build metadata (the table may carry
extras that BOARDS does not expose)."""
assert set(BOARDS) <= set(ESP8266_BOARD_BUILD)
+1 -1
View File
@@ -6,9 +6,9 @@ from unittest.mock import patch
from hypothesis import given
import pytest
from strategies import mac_addr_strings
from esphome import const, core
from tests.unit_tests.strategies import mac_addr_strings
class TestHexInt: