Merge branch 'esp8266-arduino-toolchain' into esp8266-native-ci

This commit is contained in:
J. Nick Koston
2026-09-26 11:24:31 +01:00
5 changed files with 54 additions and 42 deletions
+4 -25
View File
@@ -16,6 +16,7 @@ from typing import TYPE_CHECKING, Protocol
# cause them to be loaded before external components are processed, resulting
# in the built-in version being used instead of the external component one.
from esphome import const, platform_hooks
from esphome.build_helpers.native import native_backend
from esphome.const import (
ALLOWED_NAME_CHARS,
ARGUMENT_HELP_DEVICE,
@@ -971,7 +972,7 @@ def upload_using_esptool(
if file is not None:
flash_images = [FlashImage(path=file, offset="0x0")]
elif (native := _native_toolchain_module()) is not None:
elif (native := native_backend()) is not None:
# Every native backend supplies its own 0x0 flash image (bootloader
# and partitions included where the target needs them)
image = native.get_factory_firmware_path()
@@ -1966,32 +1967,10 @@ def command_update_all(args: ArgsProtocol) -> int | None:
return run_multiple_configs(files, build_command)
# Native build backend per (target platform, toolchain); keyed here so
# the serial upload/logs fast path never imports the platform package
_NATIVE_TOOLCHAIN_MODULES = {
("esp32", Toolchain.ESP_IDF): "esphome.espidf.toolchain",
("esp8266", Toolchain.ARDUINO): "esphome.arduino8266.toolchain",
}
def _native_toolchain_module():
"""The native build backend module for the resolved toolchain."""
if not CORE.using_native_toolchain:
return None
key = (CORE.target_platform, CORE.toolchain)
if (module_path := _NATIVE_TOOLCHAIN_MODULES.get(key)) is None:
# Degrading to the PlatformIO path would build with the wrong backend
raise EsphomeError(
f"Toolchain '{CORE.toolchain.value}' has no native build backend "
f"module for platform {CORE.target_platform}"
)
return importlib.import_module(module_path)
def command_idedata(args: ArgsProtocol, config: ConfigType) -> int:
import json
native_toolchain = _native_toolchain_module()
native_toolchain = native_backend()
if native_toolchain is not None:
# Native toolchains derive idedata from the build's
@@ -2036,7 +2015,7 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int:
from esphome.analyze_memory.ram_strings import RamStringsAnalyzer
# Refuse an unsupported toolchain before paying for a full compile
native_toolchain = _native_toolchain_module()
native_toolchain = native_backend()
if native_toolchain is None and not CORE.using_toolchain_platformio:
_LOGGER.error(
"analyze-memory is not supported with the '%s' toolchain on %s; "
+34
View File
@@ -0,0 +1,34 @@
"""Resolution of the native (non-PlatformIO) build backend for a config.
Kept deliberately light: the serial upload and logs fast path resolves a
backend for its artifact paths alone, so importing this must not pull in a
platform component package or the backend itself.
"""
from __future__ import annotations
import importlib
from types import ModuleType
from esphome.const import Toolchain
from esphome.core import CORE, EsphomeError
# Native build backend per (target platform, toolchain)
NATIVE_TOOLCHAIN_MODULES = {
("esp32", Toolchain.ESP_IDF): "esphome.espidf.toolchain",
("esp8266", Toolchain.ARDUINO): "esphome.arduino8266.toolchain",
}
def native_backend() -> ModuleType | None:
"""The native build backend module for the resolved toolchain."""
if not CORE.using_native_toolchain:
return None
key = (CORE.target_platform, CORE.toolchain)
if (module_path := NATIVE_TOOLCHAIN_MODULES.get(key)) is None:
# Degrading to the PlatformIO path would build with the wrong backend
raise EsphomeError(
f"Toolchain '{CORE.toolchain.value}' has no native build backend "
f"module for platform {CORE.target_platform}"
)
return importlib.import_module(module_path)
+3 -11
View File
@@ -5,6 +5,7 @@ import re
import subprocess
from typing import Any
from esphome.build_helpers.native import native_backend
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import (
@@ -310,15 +311,6 @@ CONFIG_SCHEMA = cv.All(
)
def native_toolchain_module():
"""The native build backend for the resolved toolchain, if any."""
if not CORE.using_toolchain_arduino:
return None
from esphome.arduino8266 import toolchain
return toolchain
def check_rosetta() -> None:
"""Fail fast when the x86_64 ESP8266 toolchain cannot run on this Mac.
@@ -516,7 +508,7 @@ async def finalize_serial_config() -> None:
# PlatformIO toolchain.
def run_compile(args, config: ConfigType) -> bool:
# Positive check: the native backend only runs when explicitly resolved
toolchain = native_toolchain_module()
toolchain = native_backend()
if toolchain is None:
return False
if toolchain.run_compile(config, CORE.verbose) != 0:
@@ -588,7 +580,7 @@ ESP8266_EXCEPTION_CODES = {
def _resolve_decode_tools(config: ConfigType) -> tuple[str, str] | None:
"""``(addr2line, elf)`` for this build, or None after warning why."""
if (native_toolchain := native_toolchain_module()) is not None:
if (native_toolchain := native_backend()) is not None:
addr2line = native_toolchain.get_addr2line_path()
elf = native_toolchain.get_elf_path()
for path in (addr2line, elf):
@@ -23,6 +23,9 @@ from esphome.const import (
CONF_SOURCE,
CONF_TOOLCHAIN,
CONF_VERSION,
KEY_CORE,
KEY_TARGET_PLATFORM,
PLATFORM_ESP8266,
Toolchain,
)
from esphome.core import CORE, EsphomeError
@@ -31,9 +34,11 @@ from esphome.types import ConfigType
@pytest.fixture(autouse=True)
def _arduino_toolchain() -> Generator[None]:
# The suite-wide reset_core fixture clears CORE.toolchain after each test
# The decode-tool cache lives in CORE.data, which reset_core clears
# The suite-wide reset_core fixture clears both after each test; the
# shared backend resolver reads the platform as well as the toolchain,
# and the decode-tool cache lives in CORE.data
CORE.toolchain = Toolchain.ARDUINO
CORE.data.setdefault(KEY_CORE, {})[KEY_TARGET_PLATFORM] = PLATFORM_ESP8266
yield
+6 -4
View File
@@ -25,7 +25,6 @@ from esphome.__main__ import (
Purpose,
_get_configured_xtal_freq,
_make_crystal_freq_callback,
_native_toolchain_module,
_redact_with_legacy_fallback,
_resolve_network_devices,
_should_subscribe_states,
@@ -7676,21 +7675,24 @@ def test_native_toolchain_table_serves_every_native_toolchain() -> None:
"""Every member of NATIVE_TOOLCHAINS has a backend entry; a gap would
surface as a targeted EsphomeError on the one affected config, and this
pin keeps the table from drifting when a toolchain is added."""
from esphome.build_helpers.native import NATIVE_TOOLCHAIN_MODULES
from esphome.const import NATIVE_TOOLCHAINS
assert {tc for _, tc in main._NATIVE_TOOLCHAIN_MODULES} == set(NATIVE_TOOLCHAINS)
assert {tc for _, tc in NATIVE_TOOLCHAIN_MODULES} == set(NATIVE_TOOLCHAINS)
def test_native_toolchain_module_missing_backend_raises(tmp_path: Path) -> None:
"""A native toolchain missing from the backend table is a bug and must
fail, not silently degrade to the PlatformIO path."""
from esphome.build_helpers import native
setup_core(platform=PLATFORM_ESP8266, tmp_path=tmp_path, name="test_device")
CORE.toolchain = Toolchain.ARDUINO
with (
patch.dict(main._NATIVE_TOOLCHAIN_MODULES, clear=True),
patch.dict(native.NATIVE_TOOLCHAIN_MODULES, clear=True),
pytest.raises(EsphomeError, match="no native build backend"),
):
_native_toolchain_module()
native.native_backend()
def test_command_analyze_memory_unsupported_toolchain(