Add the arduino toolchain enum and validate --toolchain on every platform

This commit is contained in:
J. Nick Koston
2026-08-20 12:56:34 -05:00
parent 72da2b1ce2
commit 04ad9524f5
14 changed files with 126 additions and 5 deletions
+5
View File
@@ -1083,6 +1083,11 @@ def _resolve_toolchain(value: ConfigType) -> ConfigType:
# CORE.toolchain instead of re-resolving it from the config dict.
if CORE.toolchain is None:
CORE.toolchain = value.get(CONF_TOOLCHAIN, Toolchain.ESP_IDF)
if CORE.toolchain not in (Toolchain.PLATFORMIO, Toolchain.ESP_IDF):
raise cv.Invalid(
f"Unsupported toolchain '{CORE.toolchain.value}' for ESP32. "
"Supported toolchains are 'platformio' and 'esp-idf'."
)
return value
+3
View File
@@ -244,6 +244,9 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_ENABLE_SCANF_FLOAT): cv.boolean,
}
),
# Until the native toolchain lands, PlatformIO is the only backend;
# reject a --toolchain this platform cannot serve yet.
cv.require_platformio_toolchain("ESP8266"),
set_core_data,
)
+1
View File
@@ -37,6 +37,7 @@ CONFIG_SCHEMA = cv.All(
}
),
set_core_data,
cv.require_platformio_toolchain("host"),
)
+1
View File
@@ -315,6 +315,7 @@ BASE_SCHEMA = cv.Schema(
BASE_SCHEMA.add_extra(_detect_variant)
BASE_SCHEMA.add_extra(_update_core_data)
BASE_SCHEMA.add_extra(cv.require_platformio_toolchain("LibreTiny"))
def _configure_lwip(config: dict) -> None:
+5
View File
@@ -128,6 +128,11 @@ def set_core_data(config: ConfigType) -> ConfigType:
def _resolve_toolchain(config: ConfigType) -> ConfigType:
if CORE.toolchain is None:
CORE.toolchain = config.get(CONF_TOOLCHAIN, Toolchain.SDK_NRF)
if CORE.toolchain not in (Toolchain.PLATFORMIO, Toolchain.SDK_NRF):
raise cv.Invalid(
f"Unsupported toolchain '{CORE.toolchain.value}' for nRF52. "
"Supported toolchains are 'platformio' and 'sdk-nrf'."
)
return config
+1
View File
@@ -313,6 +313,7 @@ CONFIG_SCHEMA = cv.All(
cv.has_at_least_one_key(CONF_BOARD, CONF_VARIANT),
_detect_variant,
set_core_data,
cv.require_platformio_toolchain("RP2"),
)
+22
View File
@@ -2532,6 +2532,28 @@ def platformio_version_constraint(value):
return constraints
def require_platformio_toolchain(platform_name: str):
"""Reject a CLI-selected toolchain other than PlatformIO.
For platforms with only the PlatformIO backend; without this a
``--toolchain`` they cannot serve would silently build with PlatformIO.
"""
def validator(config):
from esphome.const import Toolchain
if CORE.toolchain is None:
CORE.toolchain = Toolchain.PLATFORMIO
if CORE.toolchain != Toolchain.PLATFORMIO:
raise Invalid(
f"Unsupported toolchain '{CORE.toolchain.value}' for "
f"{platform_name}. The only supported toolchain is 'platformio'."
)
return config
return validator
def require_framework_version(
*,
max_version=False,
+2
View File
@@ -21,6 +21,8 @@ class Toolchain(StrEnum):
PLATFORMIO = "platformio"
ESP_IDF = "esp-idf"
SDK_NRF = "sdk-nrf"
# ESP8266: the Arduino core built directly (no PlatformIO)
ARDUINO = "arduino"
class Platform(StrEnum):
+4
View File
@@ -982,6 +982,10 @@ class EsphomeCore:
def using_toolchain_sdk_nrf(self):
return self.toolchain == Toolchain.SDK_NRF
@property
def using_toolchain_arduino(self):
return self.toolchain == Toolchain.ARDUINO
@property
def using_zephyr(self):
return self.target_framework == "zephyr"
+8 -5
View File
@@ -557,10 +557,12 @@ def _add_library_str(lib: str) -> None:
@coroutine_with_priority(CoroPriority.FINAL)
async def _add_platformio_options(pio_options: dict[str, str | list[str]]) -> None:
if CORE.using_toolchain_esp_idf:
# The native ESP-IDF build doesn't read platformio.ini; honor the
# options with a native equivalent and warn about the rest, which
# would otherwise be silently ignored.
if CORE.using_toolchain_esp_idf or (
CORE.using_toolchain_arduino and CORE.is_esp8266
):
# The native builds don't read platformio.ini; honor the options
# with a native equivalent and warn about the rest, which would
# otherwise be silently ignored.
for key, val in pio_options.items():
vals = [val] if isinstance(val, str) else val
if key == CONF_BUILD_FLAGS:
@@ -588,8 +590,9 @@ async def _add_platformio_options(pio_options: dict[str, str | list[str]]) -> No
# config at upload time (upload_using_esptool)
_LOGGER.warning(
"esphome->platformio_options->%s is ignored when building with "
"the native ESP-IDF toolchain",
"the native '%s' toolchain",
key,
CORE.toolchain.value,
)
return
# Add includes at the very end, so that they override everything
+14
View File
@@ -131,6 +131,20 @@ def test_esp32_rejects_unsupported_toolchains(
CONFIG_SCHEMA({"variant": VARIANT_ESP32, "toolchain": config_toolchain})
def test_esp32_rejects_unsupported_cli_toolchain(
set_core_config: SetCoreConfigCallable,
) -> None:
"""A --toolchain the platform cannot serve fails instead of silently
building with PlatformIO (the CLI path bypasses the YAML validator)."""
set_core_config(PlatformFramework.ESP32_IDF)
from esphome.components.esp32 import CONFIG_SCHEMA
CORE.toolchain = Toolchain.ARDUINO
with pytest.raises(cv.Invalid, match="Unsupported toolchain 'arduino'"):
CONFIG_SCHEMA({"variant": VARIANT_ESP32})
@pytest.mark.parametrize(
("config", "error_match"),
[
+33
View File
@@ -1389,3 +1389,36 @@ def test_esphome_build_internals_are_yaml_only() -> None:
assert markers[field].visibility is cv.Visibility.ADVANCED, field
# A regular device-config field stays on the main form.
assert markers[CONF_NAME_ADD_MAC_SUFFIX].visibility is None
@pytest.mark.asyncio
async def test_add_platformio_options_native_arduino(
caplog: pytest.LogCaptureFixture,
) -> None:
"""The native ESP8266 Arduino toolchain warns about ignored options the
same way the native IDF toolchain does."""
CORE.toolchain = Toolchain.ARDUINO
CORE.data[KEY_CORE] = {
KEY_TARGET_PLATFORM: "esp8266",
KEY_TARGET_FRAMEWORK: "arduino",
}
await config._add_platformio_options(
{
"board_build.f_cpu": "160000000L",
"upload_speed": "115200",
}
)
assert "esphome->platformio_options->board_build.f_cpu is ignored" in caplog.text
assert "'arduino' toolchain" in caplog.text
assert "upload_speed" not in caplog.text
def test_esp8266_rejects_unsupported_cli_toolchain() -> None:
"""Until the native backend lands, ESP8266 serves only PlatformIO."""
from esphome.components.esp8266 import CONFIG_SCHEMA
CORE.toolchain = Toolchain.ARDUINO
with pytest.raises(cv.Invalid, match="Unsupported toolchain 'arduino'"):
CONFIG_SCHEMA({"board": "nodemcuv2"})
@@ -3165,3 +3165,19 @@ def test_file__remapped_path_is_directory_raises(setup_core: Path) -> None:
with pytest.raises(Invalid, match="is not a file"):
cv.file_("/original/config/headers")
def test_require_platformio_toolchain() -> None:
"""Platforms with only the PlatformIO backend reject other toolchains."""
from esphome.const import Toolchain
from esphome.core import CORE
validator = cv.require_platformio_toolchain("RP2")
CORE.toolchain = None
config: dict = {}
assert validator(config) is config
assert CORE.toolchain == Toolchain.PLATFORMIO
CORE.toolchain = Toolchain.ARDUINO
with pytest.raises(Invalid, match="Unsupported toolchain 'arduino' for RP2"):
validator(config)
+11
View File
@@ -619,3 +619,14 @@ def test_needs_venv_rebuild_on_dangling_interpreter_symlink(tmp_path: Path) -> N
assert not python.exists()
assert _needs_venv_rebuild(python, sentinel, "abc123")
def test_resolve_toolchain_rejects_unsupported() -> None:
"""A --toolchain nRF52 cannot serve fails instead of degrading silently."""
from esphome.components.nrf52 import _resolve_toolchain
import esphome.config_validation as cv
from esphome.const import Toolchain
CORE.toolchain = Toolchain.ARDUINO
with pytest.raises(cv.Invalid, match="Unsupported toolchain 'arduino'"):
_resolve_toolchain({})