diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 77efc91bef..6969ce9def 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -1138,13 +1138,14 @@ class EsphomeCore: def add_platformio_option( self, key: str, value: str | list[str], *, replace: bool = False ) -> None: - """Set a platformio.ini option; list values append to an existing list - unless ``replace`` is True, which overwrites any existing value.""" + """Set a platformio.ini option; values append to an existing list + (a string as one element) unless ``replace`` is True, which + overwrites any existing value.""" new_val = value old_val = self.platformio_options.get(key) if not replace and isinstance(old_val, list): - assert isinstance(value, list) - new_val = old_val + value + # A user platformio_options string must merge, not assert + new_val = old_val + ([value] if isinstance(value, str) else value) self.platformio_options[key] = new_val def _get_variable_generator(self, id): diff --git a/script/determine-jobs.py b/script/determine-jobs.py index 4c87fc828a..776236fa9e 100755 --- a/script/determine-jobs.py +++ b/script/determine-jobs.py @@ -498,11 +498,7 @@ ESP32_PLATFORMIO_TEST_COMPONENTS = frozenset( # drives every PlatformIO build). The esp32 platform component is already in # ESP32_PLATFORMIO_TEST_COMPONENTS, so its changes are covered by the normal # component-narrowing path. -ESP32_PLATFORMIO_TRIGGER_PATH_PREFIXES = ( - "esphome/platformio/", - # The pch wiring the strict smoke jobs police lives here - "esphome/build_helpers/", -) +ESP32_PLATFORMIO_TRIGGER_PATH_PREFIXES = ("esphome/platformio/",) # Standalone files that, when changed, trigger the PlatformIO compile test: # - esphome/build_gen/platformio.py -- the PlatformIO build generator @@ -519,6 +515,11 @@ _SMOKE_HARNESS_TRIGGER_FILES = frozenset( ESP32_PLATFORMIO_TRIGGER_FILES = _SMOKE_HARNESS_TRIGGER_FILES | { "esphome/build_gen/platformio.py", + # The pch machinery the strict smoke job polices, and the modules it + # imports; the rest of build_helpers/ does not affect PlatformIO builds + "esphome/build_helpers/pch.py", + "esphome/build_helpers/ccache.py", + "esphome/build_helpers/idedata.py", } diff --git a/tests/script/test_determine_jobs.py b/tests/script/test_determine_jobs.py index 21a0f30d78..adf638e17b 100644 --- a/tests/script/test_determine_jobs.py +++ b/tests/script/test_determine_jobs.py @@ -1003,7 +1003,10 @@ _ESP32_PLATFORMIO_FULL_LIST_FILES = [ # PlatformIO subsystem (path-prefix trigger) + build generator ["esphome/platformio/runner.py"], ["esphome/platformio/toolchain.py"], + # The pch modules are standalone triggers, not the whole build_helpers/ ["esphome/build_helpers/pch.py"], + ["esphome/build_helpers/ccache.py"], + ["esphome/build_helpers/idedata.py"], ["esphome/build_gen/platformio.py"], # Workflow / harness files ["script/test_build_components.py"], @@ -1062,6 +1065,9 @@ def test_esp32_platformio_components_to_test_returns_full_list_on_infrastructure # Non-PlatformIO files in esphome/build_gen/ do NOT trigger the # full list -- only esphome/build_gen/platformio.py is a trigger. (["esphome/build_gen/espidf.py"], [], []), + # build_helpers modules the pch does not import are not triggers. + (["esphome/build_helpers/size_summary.py"], [], []), + (["esphome/build_helpers/ninja.py"], [], []), # Docs / unrelated files -> empty. (["README.md"], [], []), ([], [], []), diff --git a/tests/unit_tests/test_core.py b/tests/unit_tests/test_core.py index 0c96f8c8c9..3cf8d126d4 100644 --- a/tests/unit_tests/test_core.py +++ b/tests/unit_tests/test_core.py @@ -568,6 +568,16 @@ class TestEsphomeCore: target.config_path = Path("foo/config") return target + def test_add_platformio_option_merges_string_into_list(self, target) -> None: + """A user platformio_options string lands after a component's list + (FINAL priority) and must merge as one element, not assert.""" + target.add_platformio_option("extra_scripts", ["post:pch.py"]) + target.add_platformio_option("extra_scripts", "pre:mine.py") + assert target.platformio_options["extra_scripts"] == [ + "post:pch.py", + "pre:mine.py", + ] + def test_reset(self, target): """Call reset on target and compare to new instance""" other = core.EsphomeCore().__dict__