From ff7161d7ebaeb66c87ab4f814b6ae982180cb829 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 20 Aug 2026 05:21:41 -0500 Subject: [PATCH] Surface silently dropped inputs and anchor the platform defaults to shared constants --- esphome/arduino8266/component.py | 21 ++++--- esphome/arduino8266/framework.py | 11 +++- esphome/build_gen/arduino8266.py | 2 +- esphome/components/esp8266/__init__.py | 21 ++++--- .../esp8266/test_toolchain_validation.py | 56 +++++++++---------- 5 files changed, 64 insertions(+), 47 deletions(-) diff --git a/esphome/arduino8266/component.py b/esphome/arduino8266/component.py index 3bd96be0ef..6855b345e6 100644 --- a/esphome/arduino8266/component.py +++ b/esphome/arduino8266/component.py @@ -160,13 +160,20 @@ def resolve_libraries(framework_path: Path) -> list[ArduinoLibrary]: # it cannot be resolved from the registry. for dep in normalize_dependencies(component.data.get("dependencies")): name = dep.get("name") - if ( - not name - or dep.get("owner") - or "version" in dep - or name in bundled_names - or not (framework_path / "libraries" / name).is_dir() - ): + if not name or dep.get("owner") or "version" in dep: + continue + if name in bundled_names: + continue + if not (framework_path / "libraries" / name).is_dir(): + # The shared converter skips version-less deps too, so this + # is the only place the drop can be made visible before the + # missing sources surface as link errors. + _LOGGER.warning( + "Dependency %s of library %s is not bundled with the " + "framework and has no version to resolve; skipping", + name, + component.name, + ) continue if is_lib_ignored(name, lib_ignore): continue diff --git a/esphome/arduino8266/framework.py b/esphome/arduino8266/framework.py index 73197ae72c..6b9086c853 100644 --- a/esphome/arduino8266/framework.py +++ b/esphome/arduino8266/framework.py @@ -74,12 +74,16 @@ def get_arduino8266_tools_path() -> Path: return path.resolve() +# 3.1.1 rather than 3.1.0: the registry has no package for 3.1.0 +MIN_FRAMEWORK_VERSION = "3.1.1" + + def framework_package_version(ver: cv.Version) -> str: """Map an Arduino core version (e.g. 3.1.2) to its package version. Same encoding as the PlatformIO package registry uses for core 3.x releases (3.1.2 -> 3.30102.0). The native toolchain only supports core - >= 3.1.0, so the 1.x/2.x encodings never apply here. + >= MIN_FRAMEWORK_VERSION, so the 1.x/2.x encodings never apply here. """ return f"3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" @@ -171,6 +175,11 @@ def _install_package( archive = _downloads_path() / f"{name}-{version}" _LOGGER.info("Downloading %s %s ...", name, version) if mirrors: + _LOGGER.warning( + "Downloading %s from a mirror override; checksum verification " + "is skipped for mirrors", + name, + ) download_from_mirrors( mirrors, {"VERSION": version, "SYSTEM": _pio_system()}, archive ) diff --git a/esphome/build_gen/arduino8266.py b/esphome/build_gen/arduino8266.py index 3eb2d32b84..80c0730aa0 100644 --- a/esphome/build_gen/arduino8266.py +++ b/esphome/build_gen/arduino8266.py @@ -511,7 +511,7 @@ def write_project(paths: dict[str, Path]) -> bool: asflags = [f for f in asflags if f not in unflags] link_flags = [f for f in _LINKFLAGS if f not in unflags] - if esp8266_data.get(KEY_SCANF_FLOAT): + if esp8266_data[KEY_SCANF_FLOAT]: link_flags += ["-u", "_scanf_float"] link_flags += project_link_flags link_flags += [flag for lib in libraries for flag in lib.link_flags] diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 608a0e3aa0..48a9802aba 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -129,16 +129,19 @@ def _validate_native_toolchain(config: ConfigType) -> ConfigType: """Constraints of the native (non-PlatformIO) Arduino toolchain.""" if not CORE.using_toolchain_arduino: return config + from esphome.arduino8266.framework import MIN_FRAMEWORK_VERSION + conf = config[CONF_FRAMEWORK] version = cv.Version.parse(conf[CONF_VERSION]) - # 3.1.1 rather than 3.1.0: the registry has no package for 3.1.0 - if version < cv.Version(3, 1, 1): + if version < cv.Version.parse(MIN_FRAMEWORK_VERSION): raise cv.Invalid( - "'toolchain: arduino' requires framework version 3.1.1 or newer" + "'toolchain: arduino' requires framework version " + f"{MIN_FRAMEWORK_VERSION} or newer" ) - if conf[CONF_PLATFORM_VERSION] != _parse_platform_version( - str(ARDUINO_4_PLATFORM_VERSION) - ): + # platform_version is a PlatformIO concept; drop it (as esp32's native + # toolchain does), warning when a custom pin is discarded. The floor + # above guarantees the schema-derived default is the ARDUINO_4 spec. + if conf.pop(CONF_PLATFORM_VERSION, None) != _ARDUINO_4_PLATFORM_SPEC: _LOGGER.warning( "'platform_version' is ignored by 'toolchain: arduino'; the native " "toolchain downloads the framework and compiler directly" @@ -243,7 +246,7 @@ def _arduino_check_versions(value): platform_version = value.get(CONF_PLATFORM_VERSION) if platform_version is None: if version >= cv.Version(3, 1, 0): - platform_version = _parse_platform_version(str(ARDUINO_4_PLATFORM_VERSION)) + platform_version = _ARDUINO_4_PLATFORM_SPEC elif version >= cv.Version(3, 0, 0): platform_version = _parse_platform_version(str(ARDUINO_3_PLATFORM_VERSION)) elif version >= cv.Version(2, 5, 0): @@ -270,6 +273,10 @@ def _parse_platform_version(value): return value +# The platform_version derived for every core >= 3.1.0 config +_ARDUINO_4_PLATFORM_SPEC = _parse_platform_version(str(ARDUINO_4_PLATFORM_VERSION)) + + ARDUINO_FRAMEWORK_SCHEMA = cv.All( cv.Schema( { diff --git a/tests/unit_tests/components/esp8266/test_toolchain_validation.py b/tests/unit_tests/components/esp8266/test_toolchain_validation.py index 09f3939081..4ae96f527e 100644 --- a/tests/unit_tests/components/esp8266/test_toolchain_validation.py +++ b/tests/unit_tests/components/esp8266/test_toolchain_validation.py @@ -2,14 +2,10 @@ from __future__ import annotations -from collections.abc import Generator - import pytest from esphome.components.esp8266 import ( - ARDUINO_4_PLATFORM_VERSION, - _format_framework_arduino_version, - _parse_platform_version, + ARDUINO_FRAMEWORK_SCHEMA, _validate_native_toolchain, ) import esphome.config_validation as cv @@ -26,26 +22,17 @@ from esphome.types import ConfigType @pytest.fixture(autouse=True) -def _arduino_toolchain() -> Generator[None]: +def _arduino_toolchain() -> None: + # The suite-wide reset_core fixture clears CORE.toolchain after each test CORE.toolchain = Toolchain.ARDUINO - yield - CORE.toolchain = None -def _config( - version: str = "3.1.2", - source: str | None = None, - platform_version: str | None = None, - board: str = "nodemcuv2", -) -> ConfigType: +def _config(board: str = "nodemcuv2", **framework: str) -> ConfigType: + framework.setdefault(CONF_VERSION, "3.1.2") + # The real schema fills the source/platform_version defaults, so these + # tests validate against what config validation actually emits return { - CONF_FRAMEWORK: { - CONF_VERSION: version, - CONF_SOURCE: source - or _format_framework_arduino_version(cv.Version.parse(version)), - CONF_PLATFORM_VERSION: platform_version - or _parse_platform_version(str(ARDUINO_4_PLATFORM_VERSION)), - }, + CONF_FRAMEWORK: ARDUINO_FRAMEWORK_SCHEMA(framework), CONF_BOARD: board, } @@ -57,35 +44,42 @@ def test_valid_config_passes() -> None: def test_platformio_toolchain_skips_checks() -> None: CORE.toolchain = Toolchain.PLATFORMIO - config = _config(version="2.7.4", board="not_a_board") + config = _config(board="not_a_board", **{CONF_VERSION: "2.7.4"}) assert _validate_native_toolchain(config) is config -def test_version_floor_is_3_1_1() -> None: +def test_version_below_floor_rejected() -> None: # 3.1.0 has no registry package, so the native floor is 3.1.1 with pytest.raises(cv.Invalid, match="3.1.1 or newer"): - _validate_native_toolchain(_config(version="3.1.0")) - _validate_native_toolchain(_config(version="3.1.1")) + _validate_native_toolchain(_config(**{CONF_VERSION: "3.1.0"})) -def test_custom_platform_version_warns(caplog: pytest.LogCaptureFixture) -> None: - _validate_native_toolchain( - _config(platform_version="platformio/espressif8266@4.0.1") - ) +def test_version_at_floor_accepted() -> None: + _validate_native_toolchain(_config(**{CONF_VERSION: "3.1.1"})) + + +def test_custom_platform_version_warns_and_is_dropped( + caplog: pytest.LogCaptureFixture, +) -> None: + config = _config(**{CONF_PLATFORM_VERSION: "platformio/espressif8266@4.0.1"}) + _validate_native_toolchain(config) assert "'platform_version' is ignored" in caplog.text + assert CONF_PLATFORM_VERSION not in config[CONF_FRAMEWORK] def test_default_platform_version_does_not_warn( caplog: pytest.LogCaptureFixture, ) -> None: - _validate_native_toolchain(_config()) + config = _config() + _validate_native_toolchain(config) assert "'platform_version' is ignored" not in caplog.text + assert CONF_PLATFORM_VERSION not in config[CONF_FRAMEWORK] def test_custom_source_rejected() -> None: with pytest.raises(cv.Invalid, match="custom framework source"): _validate_native_toolchain( - _config(source="https://github.com/esp8266/Arduino.git") + _config(**{CONF_SOURCE: "https://github.com/esp8266/Arduino.git"}) )