[core] Make set_cpp_standard work on the native IDF toolchain (#16907)

This commit is contained in:
Jonathan Swoboda
2026-06-10 15:25:03 -04:00
committed by GitHub
parent dafc3560dd
commit 29a79b1373
6 changed files with 129 additions and 12 deletions

View File

@@ -162,3 +162,53 @@ def test_get_project_cmakelists_emits_managed_components_property(
"idf_build_set_property(ESPHOME_PROJECT_MANAGED_COMPONENTS"
" espressif__esp-dsp APPEND)"
) in content
def test_get_project_cmakelists_replaces_cpp_standard(tmp_path: Path) -> None:
"""cg.set_cpp_standard() replaces the IDF default -std in
CXX_COMPILE_OPTIONS between include(project.cmake) and project()."""
with (
patch("esphome.build_gen.espidf.get_esp32_variant", return_value="ESP32"),
patch.object(CORE, "name", "test"),
patch.object(CORE, "cpp_standard", "gnu++20"),
):
from esphome.build_gen.espidf import get_project_cmakelists
content = get_project_cmakelists(minimal=True)
assert (
"idf_build_get_property(esphome_cxx_compile_options CXX_COMPILE_OPTIONS)"
in content
)
assert 'list(FILTER esphome_cxx_compile_options EXCLUDE REGEX "^-std=")' in content
assert 'list(APPEND esphome_cxx_compile_options "-std=gnu++20")' in content
# The replacement must come after project.cmake (which appends the IDF
# default) and before project() (which consumes the options).
include_pos = content.index("tools/cmake/project.cmake")
replace_pos = content.index("CXX_COMPILE_OPTIONS")
project_pos = content.index("project(test)")
assert include_pos < replace_pos < project_pos
def test_get_project_cmakelists_no_cpp_standard(tmp_path: Path) -> None:
with (
patch("esphome.build_gen.espidf.get_esp32_variant", return_value="ESP32"),
patch.object(CORE, "name", "test"),
patch.object(CORE, "cpp_standard", None),
):
from esphome.build_gen.espidf import get_project_cmakelists
content = get_project_cmakelists(minimal=True)
assert "CXX_COMPILE_OPTIONS" not in content
def test_get_component_cmakelists_no_compile_features() -> None:
"""The C++ standard is pinned project-wide via CXX_COMPILE_OPTIONS in the
top-level CMakeLists; the src component must not set its own."""
with patch.object(CORE, "build_flags", set()):
from esphome.build_gen.espidf import get_component_cmakelists
content = get_component_cmakelists()
assert "target_compile_features" not in content

View File

@@ -160,3 +160,43 @@ def test_write_ini_no_change_when_content_same(
call_args = mock_write_file_if_changed.call_args[0]
assert call_args[0] == ini_file
assert content in call_args[1]
@pytest.fixture
def clean_core(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(CORE, "name", "test")
monkeypatch.setattr(CORE, "platformio_options", {})
monkeypatch.setattr(CORE, "platformio_libraries", {})
monkeypatch.setattr(CORE, "build_flags", set())
monkeypatch.setattr(CORE, "build_unflags", set())
def test_get_ini_content_pins_cpp_standard(
clean_core: None, monkeypatch: pytest.MonkeyPatch
) -> None:
"""cg.set_cpp_standard() pins -std via build_flags and unflags every other
known standard so the platform/framework default is stripped."""
monkeypatch.setattr(CORE, "cpp_standard", "gnu++20")
content = platformio.get_ini_content()
flags_section = content.split("build_flags =")[1].split("build_unflags =")[0]
unflags_section = content.split("build_unflags =")[1].split("extra_scripts")[0]
assert "-std=gnu++20\n" in flags_section
# Both the GNU and strict dialects of every other standard are stripped.
for year in ("11", "14", "17", "23", "26", "2a", "2b", "2c"):
assert f"-std=gnu++{year}\n" in unflags_section
assert f"-std=c++{year}\n" in unflags_section
assert "-std=c++20\n" in unflags_section
# The selected standard must not unflag itself.
assert "-std=gnu++20\n" not in unflags_section
def test_get_ini_content_no_cpp_standard(
clean_core: None, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setattr(CORE, "cpp_standard", None)
content = platformio.get_ini_content()
assert "-std=" not in content