diff --git a/esphome/__main__.py b/esphome/__main__.py index 082c6bd7e3..4d80a2ae98 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -813,9 +813,10 @@ def write_cpp_file() -> int: from esphome.build_gen import espidf espidf.write_project() - elif CORE.using_toolchain_arduino: - # The ninja project is generated at compile time by - # esphome.arduino8266.toolchain (it needs the downloaded framework). + elif CORE.using_native_toolchain: + # Native builds generate their project at compile time (the ESP8266 + # ninja build needs the downloaded framework); nothing to write here, + # and never a platformio.ini (must agree with _add_platformio_options) pass else: from esphome.build_gen import platformio @@ -1937,7 +1938,15 @@ def _native_toolchain_module(): return toolchain module = importlib.import_module("esphome.components." + CORE.target_platform) get_native = getattr(module, "native_toolchain_module", None) - return get_native() if get_native is not None else None + native = get_native() if get_native is not None else None + if native is None and CORE.using_native_toolchain: + # A missing/renamed hook must fail, not silently degrade the native + # build's tooling to the PlatformIO path + raise EsphomeError( + f"Platform {CORE.target_platform} resolved toolchain " + f"'{CORE.toolchain.value}' but provides no native toolchain module" + ) + return native def command_idedata(args: ArgsProtocol, config: ConfigType) -> int: @@ -2000,6 +2009,12 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int: idedata = None native_toolchain = _native_toolchain_module() + if native_toolchain is None and not CORE.using_toolchain_platformio: + _LOGGER.error( + "analyze-memory is not supported with the '%s' toolchain", + CORE.toolchain.value if CORE.toolchain else "unresolved", + ) + return 1 if native_toolchain is not None: objdump_path = str(native_toolchain.get_objdump_path()) readelf_path = str(native_toolchain.get_readelf_path()) diff --git a/esphome/arduino8266/toolchain.py b/esphome/arduino8266/toolchain.py index 0cf20d47dc..4d736c247f 100644 --- a/esphome/arduino8266/toolchain.py +++ b/esphome/arduino8266/toolchain.py @@ -23,9 +23,11 @@ _LOGGER = logging.getLogger(__name__) # ESP8266 user RAM (matches upload.maximum_ram_size in every board manifest) _MAX_RAM_SIZE = 81920 -# platformio_options keys the native build consumes (lib_ignore) or that are -# read from the raw config elsewhere (upload_speed, at upload time) -_CONSUMED_PIO_OPTIONS = frozenset({"lib_ignore", "upload_speed"}) +# platformio_options keys the native build consumes. YAML-set upload_speed +# never reaches CORE.platformio_options under the native toolchain (it is +# read from the raw config at upload time), so anything here came from a +# component and genuinely is dropped; warn for it. +_CONSUMED_PIO_OPTIONS = frozenset({"lib_ignore"}) _RAM_SECTIONS = (".data", ".rodata", ".bss") diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index f4072262b2..fe6474ca18 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -536,7 +536,8 @@ async def finalize_serial_config() -> None: # Called by __main__.compile_program; returning False falls through to the # PlatformIO toolchain. def run_compile(args, config: ConfigType) -> bool: - if CORE.using_toolchain_platformio: + # Positive check: the native backend only runs when explicitly resolved + if not CORE.using_toolchain_arduino: return False from esphome.arduino8266 import toolchain @@ -547,9 +548,10 @@ def run_compile(args, config: ConfigType) -> bool: # Called by writer.py def copy_files() -> None: - if not CORE.using_toolchain_platformio: - # The extra scripts are PlatformIO/SCons-only; the native toolchain - # applies their logic in the build generator instead. + # Positive check, matching run_compile: only the arduino native backend + # skips the PlatformIO/SCons extra scripts (their logic lives in the + # build generator there) + if CORE.using_toolchain_arduino: return dir = Path(__file__).parent for script in ( diff --git a/tests/unit_tests/test_arduino8266_toolchain.py b/tests/unit_tests/test_arduino8266_toolchain.py index 38ab284068..55fad4f3da 100644 --- a/tests/unit_tests/test_arduino8266_toolchain.py +++ b/tests/unit_tests/test_arduino8266_toolchain.py @@ -336,7 +336,9 @@ def test_warn_ignored_platformio_options(caplog: pytest.LogCaptureFixture) -> No assert "platformio_options->board_build.ldscript is ignored" in caplog.text assert "native 'arduino' toolchain" in caplog.text assert "lib_ignore" not in caplog.text - assert "upload_speed" not in caplog.text + # Component-added upload_speed never gets read under the native + # toolchain, so it must warn + assert "upload_speed" in caplog.text def test_run_compile_idedata_error_does_not_fail_build(