From a50c4db1878b0bc0a66113f35f5fc86b68fcbf8c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 20 Aug 2026 16:46:48 -0500 Subject: [PATCH] Type the toolchain factories, guard the legacy libretiny stub, tighten the gate comments --- esphome/components/libretiny/__init__.py | 2 +- esphome/config_validation.py | 18 ++++++++++++------ esphome/core/config.py | 9 ++++++--- tests/unit_tests/test_config_validation.py | 2 ++ 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/esphome/components/libretiny/__init__.py b/esphome/components/libretiny/__init__.py index 6ba0b6e834..50dc787799 100644 --- a/esphome/components/libretiny/__init__.py +++ b/esphome/components/libretiny/__init__.py @@ -300,7 +300,7 @@ FRAMEWORK_SCHEMA = cv.All( _check_debug_order, ) -CONFIG_SCHEMA = cv.All(_notify_old_style) +CONFIG_SCHEMA = cv.All(_notify_old_style, cv.require_platformio_toolchain("LibreTiny")) BASE_SCHEMA = cv.Schema( { diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 58b89e836f..907e59bac7 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -2547,19 +2547,23 @@ def check_supported_toolchain( different backend. """ toolchain = CORE.toolchain - if toolchain is None or toolchain not in supported: + if toolchain is None: + # A caller ran the check before resolving; an ordering bug, not a + # user error + raise Invalid(f"Toolchain was not resolved before {platform_name} validation") + if toolchain not in supported: names = ", ".join(f"'{tc.value}'" for tc in supported) raise Invalid( f"Unsupported toolchain " - f"'{toolchain.value if toolchain else 'unresolved'}' for " + f"'{toolchain.value}' for " f"{platform_name}. Supported: {names}." ) -def toolchain_enum(supported: tuple[Toolchain, ...]): +def toolchain_enum(supported: tuple[Toolchain, ...]) -> Callable[[str], Toolchain]: """Schema validator for a platform's ``toolchain`` config key.""" - def validator(value) -> Toolchain: + def validator(value: str) -> Toolchain: return Toolchain(one_of(*supported, lower=True)(value)) return validator @@ -2567,7 +2571,7 @@ def toolchain_enum(supported: tuple[Toolchain, ...]): def resolve_toolchain( platform_name: str, supported: tuple[Toolchain, ...], default: Toolchain -): +) -> Callable[[ConfigType], ConfigType]: """Resolve ``CORE.toolchain`` (CLI > YAML > default) and reject one the platform cannot serve. @@ -2584,7 +2588,9 @@ def resolve_toolchain( return validator -def require_platformio_toolchain(platform_name: str): +def require_platformio_toolchain( + platform_name: str, +) -> Callable[[ConfigType], ConfigType]: """Reject a CLI-selected toolchain other than PlatformIO. For platforms with only the PlatformIO backend; without this a diff --git a/esphome/core/config.py b/esphome/core/config.py index 9be3d611b4..12f55283ad 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -560,7 +560,10 @@ async def _add_platformio_options(pio_options: dict[str, str | list[str]]) -> No 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. + # otherwise be silently ignored. __main__'s write_cpp_file and + # compile_program dispatch must agree with this gate: a toolchain + # treated as native here must not fall through to the PlatformIO + # project writer there. for key, val in pio_options.items(): vals = [val] if isinstance(val, str) else val if key == CONF_BUILD_FLAGS: @@ -581,8 +584,8 @@ async def _add_platformio_options(pio_options: dict[str, str | list[str]]) -> No _add_library_str(lib) elif key == "lib_ignore": # Read by the shared library conversion (lib_ignore_set in - # platformio/library.py) on both native backends; filters - # top-level libraries and discovered dependencies + # platformio/library.py); filters top-level libraries and + # discovered dependencies cg.add_platformio_option(key, vals) elif key != "upload_speed": # upload_speed needs no handling: it is read from the raw diff --git a/tests/unit_tests/test_config_validation.py b/tests/unit_tests/test_config_validation.py index 19106f4e1b..f7a05fbc47 100644 --- a/tests/unit_tests/test_config_validation.py +++ b/tests/unit_tests/test_config_validation.py @@ -3189,6 +3189,8 @@ def test_require_platformio_toolchain() -> None: ("host", {}), ("rp2", {"board": "rpipicow"}), ("bk72xx", {"board": "generic-bk7231n-qfn32-tuya"}), + # The legacy stub platform must reject too, not just the chip families + ("libretiny", {}), ], ) def test_every_platformio_only_platform_rejects_arduino_toolchain(