From 977376d55c052991d70c37333914b5eea200cd87 Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Sun, 19 Jul 2026 17:20:43 -0500 Subject: [PATCH] Split multi-token build.flags entries when generating ESP-IDF component CMakeLists (#17649) --- esphome/espidf/component.py | 21 ++++++++++++ tests/unit_tests/test_espidf_component.py | 41 +++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/esphome/espidf/component.py b/esphome/espidf/component.py index e9ec170a5e..51d023099e 100644 --- a/esphome/espidf/component.py +++ b/esphome/espidf/component.py @@ -83,6 +83,10 @@ def generate_cmakelists_txt(component: IDFComponent) -> str: Returns: str: The complete CMakeLists.txt content as a string """ + # Late import: this module loads with the esp32 platform on every + # validate/compile, but shlex is only needed when generating component + # CMakeLists. + import shlex def escape_entry(p: PathType) -> str: # In CMakeLists.txt, backslashes need to be escaped @@ -105,6 +109,23 @@ def generate_cmakelists_txt(component: IDFComponent) -> str: build_flags = ensure_list( component.data.get("build", {}).get("flags", DEFAULT_BUILD_FLAGS) ) + # PlatformIO shell-lexes each build.flags entry, so one entry can carry a + # flag and its argument (e.g. "-include cp_custom_alloc.h"). Split the + # same way; emitting such an entry as a single quoted compile option + # hands the compiler one argv with an embedded space. + build_flags = [token for entry in build_flags for token in shlex.split(entry)] + # Re-glue bare -I/-L/-l tokens to their argument ("-I foo" -> "-Ifoo") so + # the prefix classifiers below still route them to INCLUDE_DIRS and the + # link handling. + tokens, build_flags = build_flags, [] + i = 0 + while i < len(tokens): + if tokens[i] in ("-I", "-L", "-l") and i + 1 < len(tokens): + build_flags.append(tokens[i] + tokens[i + 1]) + i += 2 + else: + build_flags.append(tokens[i]) + i += 1 # List all sources files build_src_files = collect_filtered_files( diff --git a/tests/unit_tests/test_espidf_component.py b/tests/unit_tests/test_espidf_component.py index 055e9c8502..89d5ce3cf2 100644 --- a/tests/unit_tests/test_espidf_component.py +++ b/tests/unit_tests/test_espidf_component.py @@ -193,6 +193,47 @@ target_link_libraries(${{COMPONENT_LIB}} INTERFACE ) +def test_generate_cmakelists_txt_multi_token_flag(tmp_component): + # PlatformIO shell-lexes each build.flags entry, so a single entry can + # carry a flag and its argument. The generated CMakeLists must emit them + # as separate compile options, not one argument with an embedded space. + src_dir = tmp_component.path / "src" + src_dir.mkdir() + (src_dir / "main.c").write_text("int main() {}") + + tmp_component.data = {"build": {"flags": ["-include cp_custom_alloc.h", "-DTEST"]}} + + content = generate_cmakelists_txt(tmp_component) + assert '"-include cp_custom_alloc.h"' not in content + assert ' "-include"\n "cp_custom_alloc.h"\n' in content + + +def test_generate_cmakelists_txt_space_separated_classified_flags(tmp_component): + # Space-separated -I/-L/-l entries routed to INCLUDE_DIRS and the link + # handling before the shlex split was added; splitting must not leak + # them into raw compile options. + src_dir = tmp_component.path / "src" + src_dir.mkdir() + (src_dir / "main.c").write_text("int main() {}") + (tmp_component.path / "extra_inc").mkdir() + + tmp_component.data = { + "build": {"flags": ["-I extra_inc", "-L extra_lib", "-l extralib", "-DTEST"]} + } + + content = generate_cmakelists_txt(tmp_component) + assert 'INCLUDE_DIRS "src" "extra_inc"' in content + assert 'target_link_directories(${COMPONENT_LIB} INTERFACE\n "extra_lib"\n)' in ( + content + ) + assert 'target_link_libraries(${COMPONENT_LIB} INTERFACE\n "extralib"\n)' in ( + content + ) + assert '"-I"' not in content + assert '"-L"' not in content + assert '"-l"' not in content + + def test_generate_cmakelists_txt_references_project_managed_components_variable( tmp_component: IDFComponent, ) -> None: