diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 201c69a8c0..120e4d8ccd 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -1071,20 +1071,11 @@ def _check_esp_idf_versions(config: ConfigType) -> ConfigType: return config -def _validate_toolchain(value) -> Toolchain: - return Toolchain( - cv.one_of(Toolchain.PLATFORMIO, Toolchain.ESP_IDF, lower=True)(value) - ) - - -def _resolve_toolchain(value: ConfigType) -> ConfigType: - # Resolve toolchain: CLI (already on CORE.toolchain) > YAML > default. - # Runs before _detect_variant so downstream validators can rely on - # 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) - cv.check_supported_toolchain("ESP32", (Toolchain.PLATFORMIO, Toolchain.ESP_IDF)) - return value +_TOOLCHAINS = (Toolchain.PLATFORMIO, Toolchain.ESP_IDF) +_validate_toolchain = cv.toolchain_enum(_TOOLCHAINS) +# Runs before _detect_variant so downstream validators can rely on +# CORE.toolchain instead of re-resolving it from the config dict. +_resolve_toolchain = cv.resolve_toolchain("ESP32", _TOOLCHAINS, Toolchain.ESP_IDF) def _check_versions(config: ConfigType) -> ConfigType: diff --git a/esphome/components/nrf52/__init__.py b/esphome/components/nrf52/__init__.py index fe0c1a12de..aeeaba0c11 100644 --- a/esphome/components/nrf52/__init__.py +++ b/esphome/components/nrf52/__init__.py @@ -125,11 +125,8 @@ def set_core_data(config: ConfigType) -> ConfigType: return config -def _resolve_toolchain(config: ConfigType) -> ConfigType: - if CORE.toolchain is None: - CORE.toolchain = config.get(CONF_TOOLCHAIN, Toolchain.SDK_NRF) - cv.check_supported_toolchain("nRF52", (Toolchain.PLATFORMIO, Toolchain.SDK_NRF)) - return config +_TOOLCHAINS = (Toolchain.PLATFORMIO, Toolchain.SDK_NRF) +_resolve_toolchain = cv.resolve_toolchain("nRF52", _TOOLCHAINS, Toolchain.SDK_NRF) def set_framework(config: ConfigType) -> ConfigType: @@ -171,10 +168,7 @@ BOOTLOADERS = [ ] -def _validate_toolchain(value) -> Toolchain: - return Toolchain( - cv.one_of(Toolchain.PLATFORMIO, Toolchain.SDK_NRF, lower=True)(value) - ) +_validate_toolchain = cv.toolchain_enum(_TOOLCHAINS) def _detect_bootloader(config: ConfigType) -> ConfigType: diff --git a/esphome/config_validation.py b/esphome/config_validation.py index df0c152b13..58b89e836f 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -53,6 +53,7 @@ from esphome.const import ( CONF_SETUP_PRIORITY, CONF_STATE_TOPIC, CONF_SUBSCRIBE_QOS, + CONF_TOOLCHAIN, CONF_TOPIC, CONF_TYPE, CONF_TYPE_ID, @@ -2555,20 +2556,43 @@ def check_supported_toolchain( ) +def toolchain_enum(supported: tuple[Toolchain, ...]): + """Schema validator for a platform's ``toolchain`` config key.""" + + def validator(value) -> Toolchain: + return Toolchain(one_of(*supported, lower=True)(value)) + + return validator + + +def resolve_toolchain( + platform_name: str, supported: tuple[Toolchain, ...], default: Toolchain +): + """Resolve ``CORE.toolchain`` (CLI > YAML > default) and reject one the + platform cannot serve. + + Add to the platform's validation chain before anything that reads + ``CORE.toolchain``. + """ + + def validator(config: ConfigType) -> ConfigType: + if CORE.toolchain is None: + CORE.toolchain = config.get(CONF_TOOLCHAIN, default) + check_supported_toolchain(platform_name, supported) + return config + + return validator + + 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: ConfigType) -> ConfigType: - if CORE.toolchain is None: - CORE.toolchain = Toolchain.PLATFORMIO - check_supported_toolchain(platform_name, (Toolchain.PLATFORMIO,)) - return config - - return validator + return resolve_toolchain( + platform_name, (Toolchain.PLATFORMIO,), Toolchain.PLATFORMIO + ) def require_framework_version( diff --git a/esphome/const.py b/esphome/const.py index 6e9378ec9a..6f83f0c937 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -25,6 +25,12 @@ class Toolchain(StrEnum): ARDUINO = "arduino" +# Toolchains that drive their build natively and never read platformio.ini. +# SDK_NRF is absent on purpose: the zephyr backend keeps consuming +# platformio_options. +NATIVE_TOOLCHAINS = frozenset({Toolchain.ESP_IDF, Toolchain.ARDUINO}) + + class Platform(StrEnum): """Platform identifiers for ESPHome.""" diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index afe57f29b4..a20a60e322 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -21,6 +21,7 @@ from esphome.const import ( KEY_CORE, KEY_TARGET_FRAMEWORK, KEY_TARGET_PLATFORM, + NATIVE_TOOLCHAINS, PLATFORM_BK72XX, PLATFORM_ESP32, PLATFORM_ESP8266, @@ -992,6 +993,12 @@ class EsphomeCore: """ return self.toolchain == Toolchain.ARDUINO + @property + def using_native_toolchain(self): + """Whether the selected toolchain builds natively, without reading + ``platformio.ini`` (see ``NATIVE_TOOLCHAINS`` in ``esphome.const``).""" + return self.toolchain in NATIVE_TOOLCHAINS + @property def using_zephyr(self): return self.target_framework == "zephyr" diff --git a/esphome/core/config.py b/esphome/core/config.py index 350e4fd557..9be3d611b4 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -557,9 +557,7 @@ 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: - # Every platform's toolchain validation rejects values it cannot serve, - # so using_toolchain_arduino by itself implies the native ESP8266 build. - if CORE.using_toolchain_esp_idf or CORE.using_toolchain_arduino: + if CORE.using_native_toolchain: # 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.