mirror of
https://github.com/esphome/esphome.git
synced 2026-10-02 01:10:22 +00:00
[esp8266] Derive the native image header flash size from the linker script (#19961)
This commit is contained in:
@@ -1065,10 +1065,10 @@ def write_project(paths: InstalledPaths, ccache: str | None) -> bool:
|
||||
" rspfile_content = $in_newline",
|
||||
" description = LINK $out",
|
||||
"rule elf2bin",
|
||||
# --flash_size deliberately stays board-derived, as under
|
||||
# PlatformIO (which reads upload.maximum_size, not the ldscript).
|
||||
# --flash_size follows PlatformIO: ldscript filename first, then
|
||||
# upload.maximum_size, so the header always matches the layout.
|
||||
# -W: the framework's own elf2bin.py trips SyntaxWarning on 3.12+.
|
||||
f" command = $python -W ignore::SyntaxWarning {_q(framework / 'tools' / 'elf2bin.py')} --eboot {_q(framework / 'bootloaders' / 'eboot' / 'eboot.elf')} --app $in --flash_mode {flash_mode} --flash_freq {_FLASH_FREQ_MHZ} --flash_size {_flash_size_str(BOARDS[board][KEY_FLASH_SIZE])} --path {_q(toolchain_bin)} --out $out",
|
||||
f" command = $python -W ignore::SyntaxWarning {_q(framework / 'tools' / 'elf2bin.py')} --eboot {_q(framework / 'bootloaders' / 'eboot' / 'eboot.elf')} --app $in --flash_mode {flash_mode} --flash_freq {_FLASH_FREQ_MHZ} --flash_size {_elf2bin_flash_size(board, flash_ld_name)} --path {_q(toolchain_bin)} --out $out",
|
||||
" description = BIN $out",
|
||||
"rule copy",
|
||||
" command = $python $buildtool copy $in $out",
|
||||
@@ -1189,3 +1189,28 @@ def _flash_size_str(flash_size: int) -> str:
|
||||
"""Flash size argument for elf2bin (e.g. ``4M``, ``512K``)."""
|
||||
mb = 1024 * 1024
|
||||
return f"{flash_size // mb}M" if flash_size >= mb else f"{flash_size // 1024}K"
|
||||
|
||||
|
||||
# Same pattern PlatformIO's _get_flash_size applies to the ldscript path
|
||||
_LD_FLASH_SIZE_RE = re.compile(r"\.flash\.(\d+[mk]).*\.ld")
|
||||
# The framework elf2bin.py's --flash_size choices
|
||||
_ELF2BIN_FLASH_SIZES = frozenset({"256K", "512K", "1M", "2M", "4M", "8M", "16M"})
|
||||
|
||||
|
||||
def _elf2bin_flash_size(board: str, flash_ld_name: str) -> str:
|
||||
"""Image-header flash size as PlatformIO derives it: ldscript filename,
|
||||
else board_upload.maximum_size, else the board table. The SDK clamps the
|
||||
chip to the header size at boot, so a header smaller than the linked
|
||||
layout breaks OTA writes on the running device."""
|
||||
if match := _LD_FLASH_SIZE_RE.search(flash_ld_name):
|
||||
token = match.group(1)
|
||||
base = 1024 if token[-1] == "k" else 1024 * 1024
|
||||
return _flash_size_str(int(token[:-1]) * base)
|
||||
if max_size := _pio_option("board_upload.maximum_size", ""):
|
||||
if (
|
||||
not max_size.isdigit()
|
||||
or (size := _flash_size_str(int(max_size))) not in _ELF2BIN_FLASH_SIZES
|
||||
):
|
||||
raise EsphomeError(f"Invalid board_upload.maximum_size value {max_size!r}")
|
||||
return size
|
||||
return _flash_size_str(BOARDS[board][KEY_FLASH_SIZE])
|
||||
|
||||
+10
-4
@@ -549,7 +549,9 @@ def _add_library_str(lib: str) -> None:
|
||||
# platformio_options keys the native ESP8266 Arduino generator (a later PR
|
||||
# in this chain) will honor; its ignored-option warning will consume the same
|
||||
# list so the two cannot drift
|
||||
NATIVE_ARDUINO_PIO_OPTIONS = frozenset({"board_build.f_cpu", "board_build.ldscript"})
|
||||
NATIVE_ARDUINO_PIO_OPTIONS = frozenset(
|
||||
{"board_build.f_cpu", "board_build.ldscript", "board_upload.maximum_size"}
|
||||
)
|
||||
# The full set that survives into CORE.platformio_options under the native
|
||||
# arduino toolchain: lib_ignore is the only specially-translated key below
|
||||
# that is stored rather than translated away. Consumed by the esp8266 native
|
||||
@@ -609,9 +611,13 @@ async def _add_platformio_options(pio_options: dict[str, str | list[str]]) -> No
|
||||
# through to the ignored-option warning). Other native
|
||||
# toolchains have no equivalent and fall through too.
|
||||
cg.add_platformio_option(key, vals[-1])
|
||||
elif key != "upload_speed":
|
||||
# upload_speed needs no handling: it is read from the raw
|
||||
# config at upload time (upload_using_esptool)
|
||||
elif key != "upload_speed" and not (
|
||||
key == "board_upload.flash_size" and CORE.using_toolchain_arduino
|
||||
):
|
||||
# upload_speed is read from the raw config at upload time.
|
||||
# board_upload.flash_size is dropped silently on arduino:
|
||||
# PlatformIO's esp8266 builder never reads it either, and
|
||||
# published configs (Athom) commonly set it.
|
||||
_LOGGER.warning(
|
||||
"esphome->platformio_options->%s is ignored when building with "
|
||||
"the native '%s' toolchain",
|
||||
|
||||
@@ -21,12 +21,18 @@ from esphome.arduino8266.framework import InstalledPaths, toolchain_tool
|
||||
from esphome.build_gen import arduino8266
|
||||
from esphome.build_gen.arduino8266 import (
|
||||
_defines_flags,
|
||||
_elf2bin_flash_size,
|
||||
_flag_defines,
|
||||
_flash_size_str,
|
||||
_resolve_build_config,
|
||||
get_flash_ld_path,
|
||||
)
|
||||
from esphome.components.esp8266.boards import BOARDS, ESP8266_BOARD_BUILD
|
||||
from esphome.components.esp8266.boards import (
|
||||
BOARDS,
|
||||
ESP8266_BOARD_BUILD,
|
||||
KEY_FLASH_SIZE,
|
||||
board_ld_script,
|
||||
)
|
||||
from esphome.components.esp8266.build_surgery import RATETABLE_RULE
|
||||
from esphome.components.esp8266.const import KEY_BOARD, KEY_ESP8266, KEY_SCANF_FLOAT
|
||||
import esphome.config_validation as cv
|
||||
@@ -747,6 +753,54 @@ def test_flash_size_str() -> None:
|
||||
assert _flash_size_str(512 * 1024) == "512K"
|
||||
|
||||
|
||||
def test_elf2bin_flash_size() -> None:
|
||||
"""The image-header size follows the ldscript filename like PlatformIO,
|
||||
falling back to board_upload.maximum_size and then the board table."""
|
||||
assert _elf2bin_flash_size("esp8285", "eagle.flash.2m.ld") == "2M"
|
||||
assert _elf2bin_flash_size("esp01", "eagle.flash.512k.ld") == "512K"
|
||||
assert _elf2bin_flash_size("nodemcuv2", "eagle.flash.4m1m.ld") == "4M"
|
||||
# The testing-mode prefix still matches (search, not match)
|
||||
assert _elf2bin_flash_size("esp8285", "testing_eagle.flash.2m.ld") == "2M"
|
||||
# Custom ldscript name: board_upload.maximum_size wins over the board
|
||||
CORE.platformio_options["board_upload.maximum_size"] = "2097152"
|
||||
assert _elf2bin_flash_size("esp8285", "custom.ld") == "2M"
|
||||
del CORE.platformio_options["board_upload.maximum_size"]
|
||||
assert _elf2bin_flash_size("esp8285", "custom.ld") == "1M"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("bad", ["2MB", "3145728", "-1"])
|
||||
def test_elf2bin_flash_size_rejects_bad_maximum_size(bad: str) -> None:
|
||||
"""A non-numeric or unsupported board_upload.maximum_size fails by name
|
||||
instead of a ValueError or a late elf2bin choices error."""
|
||||
CORE.platformio_options["board_upload.maximum_size"] = bad
|
||||
with pytest.raises(EsphomeError, match="board_upload.maximum_size"):
|
||||
_elf2bin_flash_size("esp8285", "custom.ld")
|
||||
|
||||
|
||||
def test_elf2bin_flash_size_default_matches_board_table() -> None:
|
||||
"""Without an ldscript override, every board's own ldscript parses to
|
||||
the board-table size, so the emitted --flash_size is unchanged."""
|
||||
for board, entry in BOARDS.items():
|
||||
assert _elf2bin_flash_size(board, board_ld_script(entry)) == _flash_size_str(
|
||||
entry[KEY_FLASH_SIZE]
|
||||
), board
|
||||
|
||||
|
||||
def test_write_project_flash_size_follows_ldscript_override(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""An ldscript overriding the board's flash size drives the image header
|
||||
too (the Athom shape: esp8285 with eagle.flash.2m.ld). A 1M header over
|
||||
a 2M layout clamps the chip below the OTA scratch area and bricks OTA."""
|
||||
paths = _make_framework(tmp_path)
|
||||
(paths.framework / "variants" / "esp8285").mkdir()
|
||||
CORE.data[KEY_ESP8266][KEY_BOARD] = "esp8285"
|
||||
CORE.platformio_options["board_build.ldscript"] = "eagle.flash.2m.ld"
|
||||
content = _write_ninja(paths)
|
||||
assert "--flash_size 2M" in content
|
||||
assert "eagle.flash.2m.ld" in content
|
||||
|
||||
|
||||
def test_write_project_testing_mode(tmp_path: Path) -> None:
|
||||
paths = _make_framework(tmp_path)
|
||||
CORE.testing_mode = True
|
||||
|
||||
@@ -1341,6 +1341,8 @@ async def test_add_platformio_options_native_idf(
|
||||
"lib_ignore": "libsodium",
|
||||
"upload_speed": "115200",
|
||||
"board_build.f_flash": "80000000L",
|
||||
# Silently dropped on arduino only; warns here
|
||||
"board_upload.flash_size": "2MB",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1351,6 +1353,9 @@ async def test_add_platformio_options_native_idf(
|
||||
# nothing else lands in platformio_options on the native toolchain.
|
||||
assert CORE.platformio_options == {"lib_ignore": ["libsodium"]}
|
||||
assert "esphome->platformio_options->board_build.f_flash is ignored" in caplog.text
|
||||
assert (
|
||||
"esphome->platformio_options->board_upload.flash_size is ignored" in caplog.text
|
||||
)
|
||||
assert "upload_speed" not in caplog.text
|
||||
# build_flags has a first-class esphome equivalent, so it is deprecated.
|
||||
# lib_deps/lib_ignore are kept as valid platformio_options (no warning).
|
||||
@@ -1464,13 +1469,21 @@ async def test_add_platformio_options_native_arduino(
|
||||
"board_build.ldscript": ["eagle.flash.2m.ld", "eagle.flash.4m2m.ld"],
|
||||
"board_build.filesystem": "littlefs",
|
||||
"upload_speed": "115200",
|
||||
# The Athom shape: maximum_size is the elf2bin fallback,
|
||||
# flash_size is dropped silently (PlatformIO never reads it)
|
||||
"board_upload.maximum_size": "2097152",
|
||||
"board_upload.flash_size": "2MB",
|
||||
}
|
||||
)
|
||||
|
||||
assert CORE.platformio_options["board_build.f_cpu"] == "160000000L"
|
||||
assert CORE.platformio_options["board_build.ldscript"] == "eagle.flash.4m2m.ld"
|
||||
assert CORE.platformio_options["board_upload.maximum_size"] == "2097152"
|
||||
assert "board_upload.flash_size" not in CORE.platformio_options
|
||||
assert "board_build.f_cpu is ignored" not in caplog.text
|
||||
assert "board_build.ldscript is ignored" not in caplog.text
|
||||
assert "board_upload.maximum_size is ignored" not in caplog.text
|
||||
assert "board_upload.flash_size is ignored" not in caplog.text
|
||||
assert (
|
||||
"esphome->platformio_options->board_build.filesystem is ignored" in caplog.text
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user