From debfff005bceba3c9a3c9d810d8170547a35988d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 21 Aug 2026 12:57:36 -0500 Subject: [PATCH] Check the elf2bin inputs at generation, quote cxx flags, add d1_wroom_02 to BOARDS elf2bin.py and eboot.elf join the existence checks so a half-extracted package fails by name before the compile instead of at the second-to-last edge. get_project_cxx_compile_flags() values go through _shell_token like every other flag source. d1_wroom_02 (2 MB, in ESP8266_BOARD_BUILD and buildable under PlatformIO) joins BOARDS so the native board guard stops rejecting it, and the guard comment states what config validation actually checks. --- esphome/build_gen/arduino8266.py | 16 +++++++++++--- esphome/components/esp8266/boards.py | 4 ++++ .../unit_tests/build_gen/test_arduino8266.py | 21 +++++++++++++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/esphome/build_gen/arduino8266.py b/esphome/build_gen/arduino8266.py index 2b3750eb54..c483d91cc6 100644 --- a/esphome/build_gen/arduino8266.py +++ b/esphome/build_gen/arduino8266.py @@ -551,8 +551,8 @@ def write_project(paths: InstalledPaths) -> bool: config = _resolve_build_config(flag_defines) esp8266_data = CORE.data[KEY_ESP8266] board = esp8266_data[KEY_BOARD] - # Config-time validation rejects unsupported boards before this runs; - # guard anyway so a bypassing caller fails by name, not KeyError + # Config validation accepts the board as a bare string, so this is the + # first place an unknown board can fail by name instead of a KeyError if board not in BOARDS or board not in ESP8266_BOARD_BUILD: raise EsphomeError(f"Board '{board}' is not supported by the native toolchain") board_build = ESP8266_BOARD_BUILD[board] @@ -590,6 +590,16 @@ def write_project(paths: InstalledPaths) -> bool: raise EsphomeError( f"{_INCOMPLETE_INSTALL}: missing {required}; {_CLEAN_HINT}" ) + # The elf2bin edge runs after the full compile and link; a + # half-extracted package must fail here, not an hour of wall-clock later + for required_file in ( + framework / "tools" / "elf2bin.py", + framework / "bootloaders" / "eboot" / "eboot.elf", + ): + if not required_file.is_file(): + raise EsphomeError( + f"{_INCOMPLETE_INSTALL}: missing {required_file}; {_CLEAN_HINT}" + ) for lib in libraries: include_dirs += lib.include_dirs @@ -611,7 +621,7 @@ def write_project(paths: InstalledPaths) -> bool: ["-fno-rtti", f"-std={cpp_standard}"] + ["-fexceptions" if config.exceptions else "-fno-exceptions"] + common - + get_project_cxx_compile_flags() + + [_shell_token(f) for f in get_project_cxx_compile_flags()] ) # PlatformIO's ASPPCOM carries defines and includes but not CCFLAGS, # so only -D/-I user flags reach assembly there; match it. The tokens diff --git a/esphome/components/esp8266/boards.py b/esphome/components/esp8266/boards.py index 71681d99a3..0d52e7551d 100644 --- a/esphome/components/esp8266/boards.py +++ b/esphome/components/esp8266/boards.py @@ -199,6 +199,10 @@ BOARDS = { "name": "WeMos D1 mini Pro", "flash_size": FLASH_SIZE_16_MB, }, + "d1_wroom_02": { + "name": "WeMos D1 ESP-WROOM-02", + "flash_size": FLASH_SIZE_2_MB, + }, "d1": { "name": "WEMOS D1 R1", "flash_size": FLASH_SIZE_4_MB, diff --git a/tests/unit_tests/build_gen/test_arduino8266.py b/tests/unit_tests/build_gen/test_arduino8266.py index 64bfc5e3bd..bca1f9ab1b 100644 --- a/tests/unit_tests/build_gen/test_arduino8266.py +++ b/tests/unit_tests/build_gen/test_arduino8266.py @@ -185,6 +185,10 @@ def _make_framework(tmp_path: Path) -> InstalledPaths: for sub in ("include", "ld", "lwip2/include", "lib"): (framework / "tools" / "sdk" / sub).mkdir(parents=True) (framework / "libraries").mkdir() + (framework / "tools" / "elf2bin.py").write_text("") + eboot = framework / "bootloaders" / "eboot" + eboot.mkdir(parents=True) + (eboot / "eboot.elf").write_text("") toolchain = tmp_path / "toolchain" (toolchain / "bin").mkdir(parents=True) (toolchain / "include").mkdir() @@ -722,6 +726,23 @@ def test_write_project_returns_changed(tmp_path: Path) -> None: assert arduino8266.write_project(paths) is False +def test_write_project_missing_elf2bin_raises(tmp_path: Path) -> None: + """A half-extracted package must fail by name at generation, not after + the full compile at the elf2bin edge.""" + paths = _make_framework(tmp_path) + (paths.framework / "tools" / "elf2bin.py").unlink() + _set_flags() + src = CORE.relative_src_path() + (src / "main.cpp").parent.mkdir(parents=True, exist_ok=True) + (src / "main.cpp").write_text("") + with ( + patch.object(arduino8266, "generate_ld_scripts"), + patch("esphome.arduino.library.resolve_libraries", return_value=[]), + pytest.raises(EsphomeError, match="elf2bin"), + ): + arduino8266.write_project(paths) + + def test_write_project_missing_src_dir_raises(tmp_path: Path) -> None: """A missing generated source tree is its own error, not an install one.""" paths = _make_framework(tmp_path)