diff --git a/esphome/platformio/extra_script.py b/esphome/platformio/extra_script.py index 726c3e04bb..ef75421671 100644 --- a/esphome/platformio/extra_script.py +++ b/esphome/platformio/extra_script.py @@ -35,7 +35,7 @@ import os from pathlib import Path from typing import TYPE_CHECKING -from esphome.platformio.library import ensure_list +from esphome.core import EsphomeError if TYPE_CHECKING: from esphome.platformio.library import ConvertedLibrary @@ -89,9 +89,17 @@ def apply_extra_script( extra_flags = captured_as_build_flags(result, library_dir=source_path) if not extra_flags: return - flags = ensure_list(component.data.setdefault("build", {}).setdefault("flags", [])) - flags.extend(extra_flags) - component.data["build"]["flags"] = flags + flags = component.data.setdefault("build", {}).setdefault("flags", []) + if isinstance(flags, str): + flags = [flags] + elif not isinstance(flags, list): + # A null/dict value coerced through a list wrapper would inject a + # non-string into the compiler command line; fail naming the library + raise EsphomeError( + f"Library {component.name} has a malformed build.flags " + f"({type(flags).__name__}); expected a string or list" + ) + component.data["build"]["flags"] = [*flags, *extra_flags] # Keys we know how to translate back into ESPHome's build-flag pipeline. diff --git a/tests/unit_tests/build_helpers/test_idedata.py b/tests/unit_tests/build_helpers/test_idedata.py index 6c44192439..51f5b2384c 100644 --- a/tests/unit_tests/build_helpers/test_idedata.py +++ b/tests/unit_tests/build_helpers/test_idedata.py @@ -451,3 +451,17 @@ def test_load_or_build_idedata_never_caches_bad_compiler(tmp_path: Path) -> None ) assert data["cxx_path"] == "/usr/bin/python3" assert not cache.exists() + + +def test_load_or_build_idedata_cache_hit_skips_rebuild(tmp_path: Path) -> None: + """A valid cache newer than the compile DB is served without re-parsing.""" + compile_commands = _write_compile_commands(tmp_path) + cache = tmp_path / "c.json" + cache.write_text(json.dumps({"cc_path": "/tools/gcc", "cached": True})) + os.utime(cache, (compile_commands.stat().st_mtime + 5,) * 2) + with patch.object(idedata, "idedata_from_build") as mock_build: + data = idedata.load_or_build_idedata( + compile_commands, tmp_path / "f.elf", cache + ) + mock_build.assert_not_called() + assert data["cached"] is True