[core] Add add_cmake_arg (#18498)

This commit is contained in:
David van 't Wout
2026-08-20 12:30:33 -05:00
committed by GitHub
parent a3ea77c2f1
commit 2ab09e1a77
9 changed files with 187 additions and 25 deletions
+27
View File
@@ -16,6 +16,7 @@ from esphome.components.esp32 import (
KEY_PATH,
KEY_REF,
KEY_REPO,
register_exclude_components_cmake_arg,
)
import esphome.config_validation as cv
from esphome.const import KEY_CORE
@@ -137,6 +138,27 @@ def test_get_project_cmakelists_full_emits_builtin_components_property(
assert "JPEGDEC APPEND" not in content
def test_get_project_cmakelists_emits_cmake_args() -> None:
"""Args registered via CORE.add_cmake_arg() are emitted as set() lines,
on minimal writes too."""
CORE.add_cmake_arg("EXECUTABLE_COMPONENT_NAME", "src")
content = _render(minimal=True)
assert 'set(EXECUTABLE_COMPONENT_NAME "src")' in content
def test_get_project_cmakelists_escapes_backslashes_in_cmake_args() -> None:
"""Backslashes (the only character escaping applies to; the rest are
rejected at registration) are doubled so CMake reads the value back
verbatim."""
CORE.add_cmake_arg("MY_PATH", r"C:\esp\idf")
content = _render(minimal=True)
assert r'set(MY_PATH "C:\\esp\\idf")' in content
def test_get_project_cmakelists_emits_exclude_components(tmp_path: Path) -> None:
"""Excluded components are passed to IDF via EXCLUDE_COMPONENTS and are
dropped from ESPHOME_PROJECT_BUILTIN_COMPONENTS even when a stale
@@ -151,6 +173,7 @@ def test_get_project_cmakelists_emits_exclude_components(tmp_path: Path) -> None
},
)
CORE.data[KEY_ESP32][KEY_EXCLUDE_COMPONENTS] = {"unity", "esp_lcd"}
register_exclude_components_cmake_arg()
content = _render()
@@ -169,6 +192,7 @@ def test_get_project_cmakelists_minimal_emits_exclude_components() -> None:
"""The discovery (minimal) write also excludes components so they never
register in project_description.json."""
CORE.data[KEY_ESP32][KEY_EXCLUDE_COMPONENTS] = {"unity"}
register_exclude_components_cmake_arg()
content = _render(minimal=True)
@@ -177,6 +201,8 @@ def test_get_project_cmakelists_minimal_emits_exclude_components() -> None:
def test_get_project_cmakelists_no_exclude_components_line_when_empty() -> None:
"""No EXCLUDE_COMPONENTS line at all when nothing is excluded."""
register_exclude_components_cmake_arg()
content = _render()
assert "EXCLUDE_COMPONENTS" not in content
@@ -197,6 +223,7 @@ def test_include_builtin_idf_component_removes_exclusion() -> None:
assert get_excluded_builtin_components() == ["unity"]
register_exclude_components_cmake_arg()
content = _render()
assert 'set(EXCLUDE_COMPONENTS "unity")' in content
@@ -169,6 +169,7 @@ def clean_core(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(CORE, "platformio_libraries", {})
monkeypatch.setattr(CORE, "build_flags", set())
monkeypatch.setattr(CORE, "build_unflags", set())
monkeypatch.setattr(CORE, "cmake_args", {})
def test_get_ini_content_pins_cpp_standard(
@@ -202,6 +203,49 @@ def test_get_ini_content_no_cpp_standard(
assert "-std=" not in content
def test_get_ini_content_emits_cmake_args(
clean_core: None, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Registered args are space-joined into one option, sorted by name."""
monkeypatch.setattr(
CORE,
"cmake_args",
{"EXECUTABLE_COMPONENT_NAME": "src", "EXCLUDE_COMPONENTS": "unity"},
)
content = platformio.get_ini_content()
assert (
"board_build.cmake_extra_args = "
"-DEXCLUDE_COMPONENTS=unity -DEXECUTABLE_COMPONENT_NAME=src" in content
)
def test_get_ini_content_no_cmake_option_when_no_args(clean_core: None) -> None:
"""No board_build.cmake_extra_args line at all when nothing registered
(ESP8266/RP2040/LibreTiny builds must not get a blank option)."""
content = platformio.get_ini_content()
assert "board_build.cmake_extra_args" not in content
def test_get_ini_content_overwrites_list_valued_user_cmake_option(
clean_core: None, monkeypatch: pytest.MonkeyPatch
) -> None:
"""A user-supplied board_build.cmake_extra_args may be a list; the
registered args must replace it without tripping add_platformio_option's
list-append assert."""
monkeypatch.setattr(
CORE, "platformio_options", {"board_build.cmake_extra_args": ["-DFOO=1"]}
)
monkeypatch.setattr(CORE, "cmake_args", {"EXECUTABLE_COMPONENT_NAME": "src"})
content = platformio.get_ini_content()
assert "board_build.cmake_extra_args = -DEXECUTABLE_COMPONENT_NAME=src" in content
assert "-DFOO=1" not in content
def test_write_cxx_flags_script_emits_registered_flags(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
+32
View File
@@ -990,3 +990,35 @@ class TestEsphomeCore:
)
# The unflag is still recorded either way.
assert target.build_unflags == {"-fno-rtti", "-fno-exceptions"}
def test_add_cmake_arg(self, target) -> None:
target.add_cmake_arg("EXCLUDE_COMPONENTS", "unity;esp_lcd")
assert target.cmake_args == {"EXCLUDE_COMPONENTS": "unity;esp_lcd"}
@pytest.mark.parametrize("name", ["", "BAD NAME", 'A"B', "A(B)", "1ABC"])
def test_add_cmake_arg__rejects_invalid_name(self, target, name: str) -> None:
with pytest.raises(ValueError, match="Invalid CMake arg name"):
target.add_cmake_arg(name, "value")
@pytest.mark.parametrize("value", ["a b", "a\tb", 'a"b', "a'b", "a${FOO}b"])
def test_add_cmake_arg__rejects_invalid_value(self, target, value: str) -> None:
"""Whitespace and quotes are rejected (the PlatformIO backend passes
args as one space-joined string, which would split such a value), and
so is '$' (expanded differently by CMake and PlatformIO)."""
with pytest.raises(ValueError, match="must not contain"):
target.add_cmake_arg("MY_ARG", value)
def test_add_cmake_arg__warns_on_overwrite(
self, target, caplog: pytest.LogCaptureFixture
) -> None:
"""Re-registering with a different value is last-writer-wins; warn so
the silently dropped value is diagnosable."""
target.add_cmake_arg("MY_ARG", "one")
target.add_cmake_arg("MY_ARG", "one")
assert "overwriting" not in caplog.text
target.add_cmake_arg("MY_ARG", "two")
assert (
"CMake arg MY_ARG already set to one; overwriting with two" in caplog.text
)
assert target.cmake_args == {"MY_ARG": "two"}