mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 13:45:15 +00:00
[build] Skip target-platform deps when populating host unit-test config (#17039)
This commit is contained in:
@@ -70,12 +70,15 @@ def populate_dependency_config(
|
|||||||
|
|
||||||
* ``domain.platform`` form (e.g. ``sensor.gpio``) appends
|
* ``domain.platform`` form (e.g. ``sensor.gpio``) appends
|
||||||
``{platform: <name>}`` to ``config[domain]``, creating the list if needed.
|
``{platform: <name>}`` to ``config[domain]``, creating the list if needed.
|
||||||
* Bare components are looked up via ``get_component_fn``. Platform
|
* Bare components are looked up via ``get_component_fn``. Target-platform
|
||||||
components (``IS_PLATFORM_COMPONENT``) and ``MULTI_CONF`` components are
|
components (``is_target_platform``, e.g. ``esp32``) are skipped entirely:
|
||||||
initialised as ``[]`` so the sibling ``domain.platform`` branch can
|
a host build targets ``host``, so a foreign target platform's sources are
|
||||||
``append`` into them. Everything else is populated by running the
|
guarded out and its schema must not run here (it would mutate global CORE
|
||||||
component's schema with ``{}`` so defaults exist; if the schema requires
|
state as a side effect). Platform components (``IS_PLATFORM_COMPONENT``)
|
||||||
explicit input, an empty ``{}`` is used as a fallback.
|
and ``MULTI_CONF`` components are initialised as ``[]`` so the sibling
|
||||||
|
``domain.platform`` branch can ``append`` into them. Everything else is
|
||||||
|
populated by running the component's schema with ``{}`` so defaults exist;
|
||||||
|
if the schema requires explicit input, an empty ``{}`` is used as a fallback.
|
||||||
|
|
||||||
Platform components must always be a list here even when no
|
Platform components must always be a list here even when no
|
||||||
``domain.platform`` entry follows, because the ``domain.platform`` branch
|
``domain.platform`` entry follows, because the ``domain.platform`` branch
|
||||||
@@ -96,6 +99,12 @@ def populate_dependency_config(
|
|||||||
component = get_component_fn(component_name)
|
component = get_component_fn(component_name)
|
||||||
if component is None:
|
if component is None:
|
||||||
continue
|
continue
|
||||||
|
# Skip target platforms (e.g. esp32): a host build targets `host`, so a
|
||||||
|
# foreign target's sources are guarded out, and running its schema with
|
||||||
|
# {} leaks global CORE state (esp32 pins CORE.toolchain to ESP-IDF),
|
||||||
|
# crashing the host compile. See #17035.
|
||||||
|
if component.is_target_platform:
|
||||||
|
continue
|
||||||
if component.multi_conf or component.is_platform_component:
|
if component.multi_conf or component.is_platform_component:
|
||||||
config.setdefault(component_name, [])
|
config.setdefault(component_name, [])
|
||||||
elif component_name not in config:
|
elif component_name not in config:
|
||||||
|
|||||||
76
tests/script/test_build_helpers.py
Normal file
76
tests/script/test_build_helpers.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
"""Unit tests for script/build_helpers.py."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
# Add the script directory to the path so we can import build_helpers.
|
||||||
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "script"))
|
||||||
|
|
||||||
|
import build_helpers # noqa: E402
|
||||||
|
|
||||||
|
from esphome.core import CORE # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
class _FakeComponent:
|
||||||
|
def __init__(self, config_schema, *, is_target_platform=False):
|
||||||
|
self.multi_conf = False
|
||||||
|
self.is_platform_component = False
|
||||||
|
self.is_target_platform = is_target_platform
|
||||||
|
self.config_schema = config_schema
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def _restore_core_toolchain():
|
||||||
|
"""Keep CORE.toolchain changes from leaking between tests."""
|
||||||
|
saved = CORE.toolchain
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
CORE.toolchain = saved
|
||||||
|
|
||||||
|
|
||||||
|
def test_populate_dependency_config_skips_target_platforms() -> None:
|
||||||
|
"""Target-platform deps must be skipped, not config-populated, in a host build.
|
||||||
|
|
||||||
|
Regression test for #17035: esp32 (a target platform) appears only as a
|
||||||
|
transitive dependency of a host C++ unit test. Running its schema with {}
|
||||||
|
set ``CORE.toolchain = ESP_IDF`` as a side effect before failing validation,
|
||||||
|
which crashed the host compile with KeyError('esp32'). The fix skips
|
||||||
|
target-platform components entirely so their schema never runs.
|
||||||
|
"""
|
||||||
|
CORE.toolchain = None # the state a host build starts from
|
||||||
|
schema_calls = []
|
||||||
|
|
||||||
|
def leaky_schema(value):
|
||||||
|
# If this ever runs for a target platform, the bug is back.
|
||||||
|
schema_calls.append(value)
|
||||||
|
CORE.toolchain = "esp-idf-leak"
|
||||||
|
raise ValueError("no board or variant")
|
||||||
|
|
||||||
|
config: dict = {}
|
||||||
|
build_helpers.populate_dependency_config(
|
||||||
|
config,
|
||||||
|
["esp32"],
|
||||||
|
get_component_fn=lambda name: _FakeComponent(
|
||||||
|
leaky_schema, is_target_platform=True
|
||||||
|
),
|
||||||
|
register_platform_fn=lambda domain: None,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "esp32" not in config # skipped: no synthesized entry
|
||||||
|
assert schema_calls == [] # schema never run
|
||||||
|
assert CORE.toolchain is None # no global side effect leaked
|
||||||
|
|
||||||
|
|
||||||
|
def test_populate_dependency_config_populates_defaults() -> None:
|
||||||
|
"""A non-target-platform dep still has its schema defaults harvested."""
|
||||||
|
config: dict = {}
|
||||||
|
build_helpers.populate_dependency_config(
|
||||||
|
config,
|
||||||
|
["ok"],
|
||||||
|
get_component_fn=lambda name: _FakeComponent(lambda value: {"default": 1}),
|
||||||
|
register_platform_fn=lambda domain: None,
|
||||||
|
)
|
||||||
|
assert config["ok"] == {"default": 1}
|
||||||
@@ -266,11 +266,13 @@ def _make_component_stub(
|
|||||||
*,
|
*,
|
||||||
multi_conf: bool = False,
|
multi_conf: bool = False,
|
||||||
is_platform_component: bool = False,
|
is_platform_component: bool = False,
|
||||||
|
is_target_platform: bool = False,
|
||||||
config_schema=None,
|
config_schema=None,
|
||||||
) -> MagicMock:
|
) -> MagicMock:
|
||||||
stub = MagicMock()
|
stub = MagicMock()
|
||||||
stub.multi_conf = multi_conf
|
stub.multi_conf = multi_conf
|
||||||
stub.is_platform_component = is_platform_component
|
stub.is_platform_component = is_platform_component
|
||||||
|
stub.is_target_platform = is_target_platform
|
||||||
stub.config_schema = config_schema
|
stub.config_schema = config_schema
|
||||||
return stub
|
return stub
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user