Merge branch 'feat/idf_component_include_optional_v6.0' into 'release/v6.0'

feat(cmakev2/component): add OPTIONAL flag to idf_component_include (v6.0)

See merge request espressif/esp-idf!49571
This commit is contained in:
Roland Dobai
2026-09-03 09:20:16 +02:00
3 changed files with 59 additions and 3 deletions
+37 -3
View File
@@ -807,12 +807,20 @@ endfunction()
.. code-block:: cmake
idf_component_include(<name>
[OPTIONAL]
[INTERFACE <variable>])
*name[in]*
Component name.
*OPTIONAL[opt]*
If specified, the call is a silent no-op when the component is not
known to the build, instead of aborting. When combined with
``INTERFACE``, the output variable is set to the empty string on
miss, so callers can use ``if(${variable})`` as a truth check.
*INTERFACE[out,opt]*
Optional variable where the name of the target interface for the
@@ -830,15 +838,41 @@ endfunction()
When the ``INTERFACE`` variable is provided, the name of the included
component interface target will be stored in it.
By default, the function aborts the build if ``<name>`` is not known to
the build system. Pass ``OPTIONAL`` to make the call non-fatal -- useful
for integrations with components that may or may not be in the build,
such as managed dependencies that only some board configurations pull
in.
.. code-block:: cmake
# Wire up an optional integration only when the component is present.
idf_component_include(button OPTIONAL INTERFACE button_iface)
if(button_iface)
target_sources(${COMPONENT_TARGET} PRIVATE button_glue.c)
target_link_libraries(${COMPONENT_TARGET} PRIVATE ${button_iface})
endif()
#]]
function(idf_component_include name)
set(options)
set(options OPTIONAL)
set(one_value INTERFACE)
set(multi_value)
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
__get_component_interface_or_die(COMPONENT "${name}"
OUTPUT component_interface)
if(ARG_OPTIONAL)
__get_component_interface(COMPONENT "${name}"
OUTPUT component_interface)
if("${component_interface}" STREQUAL "NOTFOUND")
if(DEFINED ARG_INTERFACE)
set(${ARG_INTERFACE} "" PARENT_SCOPE)
endif()
return()
endif()
else()
__get_component_interface_or_die(COMPONENT "${name}"
OUTPUT component_interface)
endif()
# Check if the component is already included, meaning the add_subdirectory
# has already been called for it and the component has been processed. If
@@ -5,6 +5,7 @@
from pathlib import Path
from test_build_system_helpers import IdfPyFunc
from test_build_system_helpers import replace_in_file
COMP_A_DEFS_NO_OVERLAP = """\
#include "esp_blockdev.h"
@@ -46,10 +47,19 @@ def _add_component(app_path: Path, name: str, defs_content: str) -> None:
(comp_dir / 'CMakeLists.txt').write_text(COMPONENT_CMAKELISTS.format(filename=filename))
def _wire_components(app_path: Path) -> None:
replace_in_file(
app_path / 'main' / 'CMakeLists.txt',
'# placeholder_inside_idf_component_register',
'REQUIRES esp_blockdev comp_a comp_b\n # placeholder_inside_idf_component_register',
)
def test_ioctl_overlap_checker_passes_clean_build_v2(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
"""Build succeeds when registered ioctl ranges do not overlap (cmakev2)."""
_add_component(test_app_copy, 'comp_a', COMP_A_DEFS_NO_OVERLAP)
_add_component(test_app_copy, 'comp_b', COMP_B_DEFS_NO_OVERLAP)
_wire_components(test_app_copy)
ret = idf_py('build')
assert ret.returncode == 0
@@ -59,6 +69,7 @@ def test_ioctl_overlap_checker_fails_on_overlap_v2(idf_py: IdfPyFunc, test_app_c
"""Build fails when registered ioctl ranges overlap (cmakev2)."""
_add_component(test_app_copy, 'comp_a', COMP_A_DEFS_NO_OVERLAP)
_add_component(test_app_copy, 'comp_b', COMP_B_DEFS_OVERLAP)
_wire_components(test_app_copy)
ret = idf_py('build', check=False)
assert ret.returncode != 0
@@ -7,6 +7,7 @@ from pathlib import Path
import pytest
from test_build_system_helpers import IdfPyFunc
from test_build_system_helpers import replace_in_file
COMP_A_DEFS_NO_OVERLAP = """\
#include "esp_blockdev.h"
@@ -48,12 +49,21 @@ def _add_component(app_path: Path, name: str, defs_content: str) -> None:
(comp_dir / 'CMakeLists.txt').write_text(COMPONENT_CMAKELISTS.format(filename=filename))
def _wire_components(app_path: Path) -> None:
replace_in_file(
app_path / 'main' / 'CMakeLists.txt',
'# placeholder_inside_idf_component_register',
'REQUIRES esp_blockdev comp_a comp_b\n # placeholder_inside_idf_component_register',
)
@pytest.mark.usefixtures('test_app_copy')
def test_ioctl_overlap_checker_passes_clean_build(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
"""Build succeeds when registered ioctl ranges do not overlap."""
logging.info('Testing ioctl overlap checker with non-overlapping ranges')
_add_component(test_app_copy, 'comp_a', COMP_A_DEFS_NO_OVERLAP)
_add_component(test_app_copy, 'comp_b', COMP_B_DEFS_NO_OVERLAP)
_wire_components(test_app_copy)
ret = idf_py('build')
assert ret.returncode == 0
@@ -65,6 +75,7 @@ def test_ioctl_overlap_checker_fails_on_overlap(idf_py: IdfPyFunc, test_app_copy
logging.info('Testing ioctl overlap checker detects overlapping ranges')
_add_component(test_app_copy, 'comp_a', COMP_A_DEFS_NO_OVERLAP)
_add_component(test_app_copy, 'comp_b', COMP_B_DEFS_OVERLAP)
_wire_components(test_app_copy)
ret = idf_py('build', check=False)
assert ret.returncode != 0