Route native dispatch through a platform hook, hoist the pio-options warner, adopt shared toolchain factories

This commit is contained in:
J. Nick Koston
2026-08-20 15:53:58 -05:00
parent b3a0856afd
commit ed48f36fa8
5 changed files with 71 additions and 57 deletions
+6 -7
View File
@@ -1920,18 +1920,17 @@ def command_update_all(args: ArgsProtocol) -> int | None:
def _native_toolchain_module():
"""The native build backend module for the resolved toolchain, if any.
Platform toolchain validation rejects values a platform cannot serve, so
using_toolchain_arduino by itself implies the native ESP8266 build.
Platform-owned toolchains resolve through the target platform's
``native_toolchain_module`` hook (the same per-platform module seam
``compile_program`` uses), so shared dispatch never names a backend.
"""
if CORE.using_toolchain_esp_idf:
from esphome.espidf import toolchain
return toolchain
if CORE.using_toolchain_arduino:
from esphome.arduino8266 import toolchain
return toolchain
return None
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
def command_idedata(args: ArgsProtocol, config: ConfigType) -> int:
+7 -25
View File
@@ -7,6 +7,7 @@ from pathlib import Path
import subprocess
from esphome.arduino8266 import framework
from esphome.build_helpers.pio_options import warn_ignored_platformio_options
from esphome.const import (
CONF_COMPILE_PROCESS_LIMIT,
CONF_ESPHOME,
@@ -27,23 +28,6 @@ _MAX_RAM_SIZE = 81920
_CONSUMED_PIO_OPTIONS = frozenset({"lib_ignore", "upload_speed"})
def _warn_ignored_platformio_options() -> None:
"""Warn for component-added platformio options the native build drops.
YAML ``esphome: platformio_options:`` keys are warned about during code
generation and never reach ``CORE.platformio_options`` under the native
toolchain, so anything left here came from ``cg.add_platformio_option()``
calls (e.g. an external component) and would be silently ignored.
"""
for key in sorted(CORE.platformio_options or {}):
if key not in _CONSUMED_PIO_OPTIONS:
_LOGGER.warning(
"platformio_options->%s is ignored when building with the "
"native 'arduino' toolchain",
key,
)
_RAM_SECTIONS = (".data", ".rodata", ".bss")
_FLASH_SECTIONS = (".irom0.text", ".text", ".text1", ".data", ".rodata")
@@ -81,21 +65,21 @@ def get_readelf_path() -> Path:
def run_compile(config: ConfigType, verbose: bool) -> int:
from esphome.build_gen import arduino8266 as build_gen
_warn_ignored_platformio_options()
warn_ignored_platformio_options(_CONSUMED_PIO_OPTIONS, "arduino")
paths = framework.check_and_install(CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION])
ninja_changed = build_gen.write_project(paths)
build_dir = get_build_dir()
env = framework.get_build_env(paths["toolchain_path"])
env = framework.get_build_env(paths.toolchain)
# The compile database is a pure function of build.ninja (no compilation
# involved), so regenerate it before the build: a failed build can then
# never leave a stale database behind. Skip the ninja spawn plus MBs of
# text on unchanged builds.
if ninja_changed or not (build_dir / "compile_commands.json").is_file():
_write_compile_commands(paths["ninja_path"], build_dir, env)
_write_compile_commands(paths.ninja, build_dir, env)
cmd = [str(paths["ninja_path"]), "-C", str(build_dir)]
cmd = [str(paths.ninja), "-C", str(build_dir)]
if verbose:
cmd.append("-v")
if jobs := config[CONF_ESPHOME].get(CONF_COMPILE_PROCESS_LIMIT):
@@ -186,11 +170,9 @@ def _print_size_summary(build_dir: Path) -> None:
try:
sections[parts[0]] = int(parts[1])
except ValueError:
# An unparsed RAM/Flash section trips the missing-sections
# guard below, so no total is built on a dropped value
_LOGGER.warning("Unparsable size output for section %s", parts[0])
if parts[0] in _RAM_SECTIONS or parts[0] in _FLASH_SECTIONS:
# A confident total built on a dropped section would feed
# a wrong number to CI's memory-impact metric
return
if missing := set(_RAM_SECTIONS + _FLASH_SECTIONS) - set(sections):
# A defaulted 0 would print a confidently wrong total for CI's metric
_LOGGER.warning(
+28
View File
@@ -0,0 +1,28 @@
"""Warn about platformio options a native build backend drops."""
from __future__ import annotations
import logging
from esphome.core import CORE
_LOGGER = logging.getLogger(__name__)
def warn_ignored_platformio_options(consumed: frozenset[str], toolchain: str) -> None:
"""Warn for component-added platformio options the native build drops.
YAML ``esphome: platformio_options:`` keys are warned about during code
generation and never reach ``CORE.platformio_options`` under a native
toolchain, so anything left here came from ``cg.add_platformio_option()``
calls (e.g. an external component) and would be silently ignored.
``consumed`` names the keys the backend honors.
"""
for key in sorted(CORE.platformio_options or {}):
if key not in consumed:
_LOGGER.warning(
"platformio_options->%s is ignored when building with the "
"native '%s' toolchain",
key,
toolchain,
)
+21 -18
View File
@@ -109,18 +109,9 @@ def set_core_data(config):
return config
def _validate_toolchain(value: str) -> Toolchain:
return Toolchain(
cv.one_of(Toolchain.PLATFORMIO, Toolchain.ARDUINO, lower=True)(value)
)
def _resolve_toolchain(config: ConfigType) -> ConfigType:
# Resolve toolchain: CLI (already on CORE.toolchain) > YAML > default.
if CORE.toolchain is None:
CORE.toolchain = config.get(CONF_TOOLCHAIN, Toolchain.PLATFORMIO)
cv.check_supported_toolchain("ESP8266", (Toolchain.PLATFORMIO, Toolchain.ARDUINO))
return config
_TOOLCHAINS = (Toolchain.PLATFORMIO, Toolchain.ARDUINO)
_validate_toolchain = cv.toolchain_enum(_TOOLCHAINS)
_resolve_toolchain = cv.resolve_toolchain("ESP8266", _TOOLCHAINS, Toolchain.PLATFORMIO)
def _validate_native_toolchain(config: ConfigType) -> ConfigType:
@@ -321,6 +312,18 @@ CONFIG_SCHEMA = cv.All(
)
def native_toolchain_module():
"""The native build backend for the resolved toolchain, if any.
Hook for ``__main__``'s shared dispatch (idedata, analyze_memory).
"""
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.
@@ -627,12 +630,12 @@ def _decode_pc(config, addr):
addr2line = native_toolchain.get_addr2line_path()
elf = native_toolchain.get_elf_path()
missing = addr2line if not addr2line.is_file() else elf
if not missing.is_file():
_warn_decode_problem(
str(missing), "Cannot decode crash addresses: %s missing", missing
)
return
for path in (addr2line, elf):
if not path.is_file():
_warn_decode_problem(
str(path), "Cannot decode crash addresses: %s missing", path
)
return
addr2line, elf = str(addr2line), str(elf)
else:
from esphome.platformio import toolchain
@@ -8,6 +8,7 @@ from unittest.mock import MagicMock, patch
import pytest
from esphome.arduino8266 import framework, toolchain
from esphome.build_helpers.pio_options import warn_ignored_platformio_options
import esphome.config_validation as cv
from esphome.const import (
CONF_COMPILE_PROCESS_LIMIT,
@@ -39,12 +40,12 @@ def _setup_core(tmp_path: Path) -> None:
CORE.data[KEY_CORE] = {KEY_FRAMEWORK_VERSION: cv.Version(3, 1, 2)}
def _paths(tmp_path: Path) -> dict[str, Path]:
return {
"framework_path": tmp_path / "framework",
"toolchain_path": tmp_path / "toolchain",
"ninja_path": tmp_path / "ninja",
}
def _paths(tmp_path: Path) -> framework.InstalledPaths:
return framework.InstalledPaths(
framework=tmp_path / "framework",
toolchain=tmp_path / "toolchain",
ninja=tmp_path / "ninja",
)
def test_path_getters(tmp_path: Path) -> None:
@@ -331,7 +332,8 @@ def test_warn_ignored_platformio_options(caplog: pytest.LogCaptureFixture) -> No
"lib_ignore": ["Updater"],
"upload_speed": "460800",
}
toolchain._warn_ignored_platformio_options()
warn_ignored_platformio_options(toolchain._CONSUMED_PIO_OPTIONS, "arduino")
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