diff --git a/esphome/compiled_config.py b/esphome/compiled_config.py index be03eea965..066209b184 100644 --- a/esphome/compiled_config.py +++ b/esphome/compiled_config.py @@ -100,6 +100,22 @@ def _refresh_sidecar() -> bool: ) return False if old is not None and old.can_apply_to_core(): + if ( + old.toolchain is not None + and CORE.toolchain is not None + and old.toolchain != CORE.toolchain.value + ): + # The config was validated under a different toolchain than + # the compile's, and platforms normalize toolchain-sensitive + # keys (e.g. the esp32 board name) differently; caching it + # would disagree with the sidecar until the next compile + _LOGGER.debug( + "Not caching: config validated with toolchain %r but the " + "last compile used %r", + CORE.toolchain.value, + old.toolchain, + ) + return False # Compile-written; nothing to refresh. return True if CORE.build_path is not None and CORE.build_path.exists(): diff --git a/tests/unit_tests/test_compiled_config.py b/tests/unit_tests/test_compiled_config.py index 77690a6897..62d043f480 100644 --- a/tests/unit_tests/test_compiled_config.py +++ b/tests/unit_tests/test_compiled_config.py @@ -68,6 +68,7 @@ def _write_storage( esp_platform: str | None = "ESP32", core_platform: str | None = "esp32", build_path: str | None = "/build/lite_test", + toolchain: str | None = None, ) -> None: """Write a vanilla StorageJSON sidecar for the cache tests.""" storage_path.parent.mkdir(parents=True, exist_ok=True) @@ -88,6 +89,7 @@ def _write_storage( "no_mdns": False, "framework": "arduino", "core_platform": core_platform, + "toolchain": toolchain, } storage_path.write_text(json.dumps(data), encoding="utf-8") @@ -629,6 +631,36 @@ def test_save_compiled_config_and_sidecar_builds_real_sidecar(tmp_path: Path) -> assert load_compiled_config(yaml_path) is not None +@pytest.mark.parametrize( + ("sidecar_toolchain", "saved"), + [ + ("esp-idf", False), + ("platformio", True), + (None, True), # legacy sidecar without the field: guard is inert + ], +) +def test_save_compiled_config_and_sidecar_toolchain_mismatch( + tmp_path: Path, sidecar_toolchain: str | None, saved: bool +) -> None: + """A config validated under a different toolchain than the compile's + must not overwrite the cache: platforms normalize toolchain-sensitive + keys differently and the sidecar keeps the compile's toolchain.""" + yaml_path = _bare_yaml(tmp_path) + _prime_core(tmp_path) + CORE.config = {CONF_ESPHOME: {CONF_NAME: "lite_test"}} + CORE.toolchain = Toolchain.PLATFORMIO + _write_storage( + tmp_path / ".esphome" / "storage" / "lite_test.yaml.json", + toolchain=sidecar_toolchain, + ) + + save_compiled_config_and_sidecar(CORE.config) + + cache = tmp_path / ".esphome" / "storage" / "lite_test.yaml.validated.json" + assert cache.exists() is saved + assert (load_compiled_config(yaml_path) is not None) is saved + + @pytest.mark.parametrize("command", ["upload", "logs"]) def test_run_esphome_upload_and_logs_refresh_cache_on_fallback( tmp_path: Path, command: str