From dad9a1175b7f426809b9a29425dbbffb04dd62b8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 20 Aug 2026 06:52:54 -0500 Subject: [PATCH] Reject unsupported toolchains on the PlatformIO-only platforms and warn on empty srcFilter matches --- esphome/arduino8266/component.py | 7 ++++++ esphome/components/host/__init__.py | 1 + esphome/components/libretiny/__init__.py | 1 + esphome/components/rp2/__init__.py | 1 + esphome/config_validation.py | 22 +++++++++++++++++++ .../unit_tests/test_arduino8266_component.py | 21 ++++++++++++++++++ tests/unit_tests/test_config_validation.py | 16 ++++++++++++++ 7 files changed, 69 insertions(+) diff --git a/esphome/arduino8266/component.py b/esphome/arduino8266/component.py index 111e407dbc..94d008374c 100644 --- a/esphome/arduino8266/component.py +++ b/esphome/arduino8266/component.py @@ -123,6 +123,13 @@ def _library_info(name: str, read_path: Path, data: dict) -> ArduinoLibrary: for f in collect_filtered_files(read_path / src_dir, src_filter) if (path := Path(f)).suffix in SRC_FILE_EXTENSIONS ) + if not lib.sources and ("srcFilter" in build or "srcDir" in build): + # A default probe finding nothing is a header-only library; a + # declared filter matching nothing is a manifest/tree problem. + _LOGGER.warning( + "Library %s declares srcFilter/srcDir but no source files matched", + name, + ) return lib diff --git a/esphome/components/host/__init__.py b/esphome/components/host/__init__.py index b6a3b8b615..6f0fcb9d52 100644 --- a/esphome/components/host/__init__.py +++ b/esphome/components/host/__init__.py @@ -37,6 +37,7 @@ CONFIG_SCHEMA = cv.All( } ), set_core_data, + cv.require_platformio_toolchain("host"), ) diff --git a/esphome/components/libretiny/__init__.py b/esphome/components/libretiny/__init__.py index c56cc48055..f83593269e 100644 --- a/esphome/components/libretiny/__init__.py +++ b/esphome/components/libretiny/__init__.py @@ -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: diff --git a/esphome/components/rp2/__init__.py b/esphome/components/rp2/__init__.py index 60fcd4f8b0..1b2399f27b 100644 --- a/esphome/components/rp2/__init__.py +++ b/esphome/components/rp2/__init__.py @@ -312,6 +312,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"), ) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index f455c7b8bf..c3ee760761 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -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, diff --git a/tests/unit_tests/test_arduino8266_component.py b/tests/unit_tests/test_arduino8266_component.py index 0165ee54af..0a271215dc 100644 --- a/tests/unit_tests/test_arduino8266_component.py +++ b/tests/unit_tests/test_arduino8266_component.py @@ -96,6 +96,27 @@ def test_library_info_missing_link_dir_warns( assert lib.link_dirs == [(read_path / "missing_blobs").resolve()] +def test_library_info_declared_filter_matches_nothing_warns( + tmp_path: Path, caplog: pytest.LogCaptureFixture +) -> None: + read_path = tmp_path / "lib" + (read_path / "src").mkdir(parents=True) + data = {"build": {"srcFilter": ["+"]}} + lib = component._library_info("x", read_path, data) + assert not lib.sources + assert "declares srcFilter/srcDir but no source files matched" in caplog.text + + +def test_library_info_header_only_does_not_warn( + tmp_path: Path, caplog: pytest.LogCaptureFixture +) -> None: + read_path = tmp_path / "lib" + (read_path / "src").mkdir(parents=True) + lib = component._library_info("x", read_path, {}) + assert not lib.sources + assert "no source files matched" not in caplog.text + + def test_library_info_no_src_dir(tmp_path: Path) -> None: read_path = tmp_path / "empty" read_path.mkdir() diff --git a/tests/unit_tests/test_config_validation.py b/tests/unit_tests/test_config_validation.py index 971c4e462d..a7ddb931b3 100644 --- a/tests/unit_tests/test_config_validation.py +++ b/tests/unit_tests/test_config_validation.py @@ -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)