mirror of
https://github.com/esphome/esphome.git
synced 2026-08-24 15:16:20 +00:00
Patch exactly the named segments and share the segment-line pattern
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user