mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
The component can be referenced by multiple identifiers such as the component name, which is derived from the component directory name, component target, interface, or aliases. All component properties are attached to the component interface target, which is also used for declaring component dependencies. The cmakev2 build system has a function called __get_component_interface, which is responsible for identifying the component interface based on the given component identifier. Since this function is called frequently, it needs to be reasonably fast. This introduces a new __idf_component_interface_cache INTERFACE target, which serves as a mapping cache between component identifiers and the component interface. The cache for each component is initialized in the __init_component function, which introduces a component to the build system. Currently, the component interface search is conducted by examining the COMPONENTS_DISCOVERED and COMPONENT_INTERFACES lists stored as build properties. Since the build system is aware of most component identifiers, such as component name, target, and alias, during component initialization, it can add mappings between component identifiers and the component interface to a cache, which is built as the components are initialized. This cache is used in the __get_component_interface function instead of looking into the COMPONENTS_DISCOVERED or COMPONENT_INTERFACES lists. This significantly speeds up the component interface search and also makes the code much simpler and more readable. The component interface cache also completely replaces the existing component name resolution, which was introduced because of the component manager, and the cache used for resolved component names. This is possible because all the necessary information is available during component initialization when the component interface cache is populated. The ambiguity of components is resolved based on component source/priority. Here is an example of the component interface mapping for the espressif__led_strip component to the idf_espressif__led_strip interface target. The component name, without the namespace, is referred to as the short name. In this example, it is led_strip. - espressif__led_strip -> idf_espressif__led_strip # name(directory name) - idf_espressif__led_strip -> idf_espressif__led_strip # interface - idf::espressif__led_strip -> idf_espressif__led_strip # alias - _idf_espressif__led_strip -> idf_espressif__led_strip # real target - led_strip -> idf_espressif__led_strip # short name When another component with the same espressif__led_strip name is initialized with a different priority, there is no need to change anything in the cache, because the mapping stays the same. The cache must be updated when two components share the same short name but belong to different namespaces. This situation is likely uncommon. For instance, consider espressif__led_strip and my__led_strip. If my__led_strip has a higher priority, the cache is updated to reflect the short name as follows: - led_strip -> idf_my__led_strip If both components have the same priority, the short name mapping for led_strip is entirely removed. Conversely, if my__led_strip has a lower priority, no short name mapping is added for it. The short name is also added to the COMPONENT_SHORT_NAME property of the component. Signed-off-by: Frantisek Hrbata <frantisek.hrbata@espressif.com>
925 lines
36 KiB
CMake
925 lines
36 KiB
CMake
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
include_guard(GLOBAL)
|
|
|
|
include(utilities)
|
|
include(build)
|
|
include(kconfig)
|
|
|
|
#[[api
|
|
.. cmakev2:function:: idf_component_set_property
|
|
|
|
.. code-block:: cmake
|
|
|
|
idf_component_set_property(<component> <property> <value> [APPEND])
|
|
|
|
*component[in]*
|
|
|
|
Component name, target, target alias or interface.
|
|
|
|
*property[in]*
|
|
|
|
Property name.
|
|
|
|
*value[in]*
|
|
|
|
Property value.
|
|
|
|
*APPEND*
|
|
Append the value to the property's current value instead of replacing
|
|
it.
|
|
|
|
Set the value of the specified component property. The property is also
|
|
added to the internal list of component properties if it isn't already
|
|
there.
|
|
#]]
|
|
function(idf_component_set_property component property value)
|
|
set(options APPEND)
|
|
set(one_value)
|
|
set(multi_value)
|
|
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
|
|
|
set(append)
|
|
if(ARG_APPEND)
|
|
set(append APPEND)
|
|
endif()
|
|
|
|
__get_component_interface_or_die(COMPONENT "${component}" OUTPUT component_interface)
|
|
__set_property(TARGET "${component_interface}"
|
|
PROPERTY "${property}"
|
|
PROPERTIES COMPONENT_PROPERTIES
|
|
VALUE "${value}"
|
|
${append})
|
|
endfunction()
|
|
|
|
#[[api
|
|
.. cmakev2:function:: idf_component_get_property
|
|
|
|
.. code-block:: cmake
|
|
|
|
idf_component_get_property(<variable> <component> <property> [GENERATOR_EXPRESSION])
|
|
|
|
*variable[out]*
|
|
|
|
Variable to store the value in.
|
|
|
|
*component[in]*
|
|
|
|
Component name, target, target alias or interface.
|
|
|
|
*property[in]*
|
|
|
|
Property name to get the value of.
|
|
|
|
*GENERATOR_EXPRESSION*
|
|
|
|
Obtain the generator expression for the property rather than the actual
|
|
value.
|
|
|
|
Retrieve the value of the specified component property.
|
|
#]]
|
|
function(idf_component_get_property variable component property)
|
|
set(options GENERATOR_EXPRESSION)
|
|
set(one_value)
|
|
set(multi_value)
|
|
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
|
|
|
set(genexpr)
|
|
if(ARG_GENERATOR_EXPRESSION)
|
|
set(genexpr GENERATOR_EXPRESSION)
|
|
endif()
|
|
|
|
__get_component_interface_or_die(COMPONENT "${component}" OUTPUT component_interface)
|
|
__get_property(TARGET "${component_interface}"
|
|
PROPERTY "${property}"
|
|
OUTPUT value
|
|
${genexpr})
|
|
set(${variable} ${value} PARENT_SCOPE)
|
|
endfunction()
|
|
|
|
#[[
|
|
__get_component_cached_interface(<variable> <component>)
|
|
|
|
*variable[out]*
|
|
|
|
Variable to store the cached component target name, or left empty if
|
|
not found.
|
|
|
|
*component[in]*
|
|
|
|
Component name to search for in the cache.
|
|
|
|
Get the component target interface for a given ``component`` name. The cache
|
|
maps multiple component names to the component interface target. The name
|
|
can be a component name with or without a namespace, target, interface, or
|
|
alias.
|
|
#]]
|
|
function(__get_component_cached_interface variable component)
|
|
__get_property(TARGET __idf_component_interface_cache
|
|
PROPERTY "${component}"
|
|
OUTPUT interface)
|
|
set(${variable} "${interface}" PARENT_SCOPE)
|
|
endfunction()
|
|
|
|
#[[
|
|
__set_component_cached_interface(<component> <interface>)
|
|
|
|
*component[in]*
|
|
|
|
Component name with or without a namespace, target, interface, or
|
|
alias.
|
|
|
|
*interface[in]*
|
|
|
|
Component interface target.
|
|
|
|
Set the name mapping for the ``component`` to the ``interface`` target in
|
|
the cache.
|
|
#]]
|
|
function(__set_component_cached_interface component interface)
|
|
__set_property(TARGET __idf_component_interface_cache
|
|
PROPERTY "${component}"
|
|
PROPERTIES INTERFACE_CACHE_PROPERTIES
|
|
VALUE "${interface}")
|
|
endfunction()
|
|
|
|
#[[
|
|
__dump_component_interface_cache()
|
|
|
|
Dump content of internal component interface cache.
|
|
#]]
|
|
function(__dump_component_interface_cache)
|
|
__get_property(TARGET __idf_component_interface_cache
|
|
PROPERTY INTERFACE_CACHE_PROPERTIES
|
|
OUTPUT properties)
|
|
idf_msg("component interface cache: ${properties}")
|
|
foreach(property IN LISTS properties)
|
|
__get_property(TARGET __idf_component_interface_cache
|
|
PROPERTY "${property}"
|
|
OUTPUT value)
|
|
idf_msg(" ${property}: ${value}")
|
|
endforeach()
|
|
endfunction()
|
|
|
|
#[[
|
|
__init_component_interface_cache(<name> <interface> <alias> <target> <priority>)
|
|
|
|
*name[in]*
|
|
|
|
Component name. The component name is the same as the component
|
|
directory name.
|
|
|
|
*interface[in]*
|
|
|
|
Component interface.
|
|
|
|
*alias[in]*
|
|
|
|
Component alias.
|
|
|
|
*target[in]*
|
|
|
|
Component target.
|
|
|
|
*priority[in]*
|
|
|
|
Component priority.
|
|
|
|
Initialize the component interface cache for given component by adding
|
|
mappings from the component name, interface, alias, and target to the
|
|
component interface. The component interface cache is used by the
|
|
__get_component_interface function to quickly get a component interface
|
|
target based on the given component name, target, and alias. If the
|
|
component name contains a namespace prefix, the short name of the component
|
|
is also added to the cache. For example, for "espressif__led_strip," a
|
|
short name "led_strip" is added as a mapping to the "espressif__led_strip"
|
|
component target in the component interface cache. Since the short name may
|
|
cause ambiguity, component preference is used to determine which component
|
|
interface the short name should point to.
|
|
#]]
|
|
function(__init_component_interface_cache name interface alias target priority)
|
|
__set_component_cached_interface("${name}" "${interface}")
|
|
__set_component_cached_interface("${interface}" "${interface}")
|
|
__set_component_cached_interface("${alias}" "${interface}")
|
|
__set_component_cached_interface("${target}" "${interface}")
|
|
|
|
string(REGEX REPLACE ".*__" "" short_name "${name}")
|
|
if("${short_name}" STREQUAL "${name}")
|
|
# The component name does not include a namespace, so there is nothing
|
|
# further to set.
|
|
return()
|
|
endif()
|
|
|
|
__get_component_cached_interface(short_name_interface "${short_name}")
|
|
if(NOT short_name_interface)
|
|
# The component interface cache does not yet contain an entry for a
|
|
# component name without a namespace, so add it as the name for the
|
|
# given component interface.
|
|
idf_component_set_property("${name}" COMPONENT_SHORT_NAME "${short_name}")
|
|
__set_component_cached_interface("${short_name}" "${interface}")
|
|
return()
|
|
endif()
|
|
|
|
# We have two components with the same short name but different namespaces.
|
|
# For example, there is "espressif__led_strip" and "my__led_strip", and the
|
|
# cache already contains a mapping of "led_strip" to
|
|
# "espressif__led_strip."
|
|
|
|
idf_component_get_property(short_name_priority "${short_name}" COMPONENT_PRIORITY)
|
|
|
|
if(${priority} GREATER ${short_name_priority})
|
|
# The short component name in the cache currently refers to a component
|
|
# with lower priority. Update the short name to point to the component
|
|
# with higher priority.
|
|
idf_component_set_property("${short_name}" COMPONENT_SHORT_NAME "")
|
|
idf_component_set_property("${name}" COMPONENT_SHORT_NAME "${short_name}")
|
|
__set_component_cached_interface("${short_name}" "${interface}")
|
|
elseif(${priority} EQUAL ${short_name_priority})
|
|
# The short component name refers to a component with the same
|
|
# priority. Resolve this ambiguity by completely removing the short
|
|
# component name from the cache.
|
|
idf_component_set_property("${short_name}" COMPONENT_SHORT_NAME "")
|
|
__set_component_cached_interface("${short_name}" "")
|
|
endif()
|
|
endfunction()
|
|
|
|
#[[
|
|
__get_component_paths(PATHS <path>...
|
|
[EXCLUDE_PATHS <path>...]
|
|
[SOURCE <source>]
|
|
[CHECK]
|
|
OUTPUT <var>)
|
|
|
|
*PATHS[in]*
|
|
|
|
List of paths to search for component directories.
|
|
|
|
*EXCLUDE_PATHS[in,opt]*
|
|
|
|
Optional list of paths to exclude from the search of component
|
|
directories.
|
|
|
|
*SOURCE[in,opt]*
|
|
|
|
Source of the ``PATHS``. If provided, it will be included in the error
|
|
message when ``CHECK`` is specified.
|
|
|
|
*CHECK[in,opt]*
|
|
|
|
Verify whether the paths listed in "PATHS" exist. If any path is
|
|
missing, abort the build process.
|
|
|
|
*OUTPUT[out]*
|
|
|
|
Output variable to store the list of found component directories.
|
|
|
|
Search for component directories in the specified ``PATHS``, excluding
|
|
those in ``EXCLUDE_PATHS``, and store the list of absolute component paths
|
|
in the ``OUTPUT`` variable. If ``CHECK`` is specified, ensure that the
|
|
paths listed in ``PATHS`` exist, and stop the build process if they do not.
|
|
#]]
|
|
function(__get_component_paths)
|
|
set(options CHECK)
|
|
set(one_value SOURCE OUTPUT)
|
|
set(multi_value PATHS EXCLUDE_PATHS)
|
|
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
|
|
|
if(NOT DEFINED ARG_PATHS)
|
|
idf_die("PATHS option is required")
|
|
endif()
|
|
|
|
if(NOT DEFINED ARG_OUTPUT)
|
|
idf_die("OUTPUT option is required")
|
|
endif()
|
|
|
|
__get_absolute_paths(PATHS "${ARG_PATHS}" OUTPUT include_paths)
|
|
__get_absolute_paths(PATHS "${ARG_EXCLUDE_PATHS}" OUTPUT exclude_paths)
|
|
|
|
if(ARG_CHECK)
|
|
foreach(path IN LISTS include_paths)
|
|
if(NOT IS_DIRECTORY "${path}")
|
|
if(DEFINED ARG_SOURCE)
|
|
idf_die("Directory specified in '${ARG_SOURCE}' doesn't exist: '${path}'")
|
|
else()
|
|
idf_die("Directory doesn't exist: '${path}'")
|
|
endif()
|
|
endif()
|
|
endforeach()
|
|
endif()
|
|
|
|
set(paths "${include_paths}")
|
|
set(component_paths "")
|
|
|
|
while(paths)
|
|
list(POP_FRONT paths path)
|
|
|
|
if(NOT IS_DIRECTORY "${path}" OR "${path}" IN_LIST exclude_paths)
|
|
continue()
|
|
endif()
|
|
|
|
if(EXISTS "${path}/CMakeLists.txt")
|
|
list(APPEND component_paths "${path}")
|
|
elseif("${path}" IN_LIST include_paths)
|
|
file(GLOB dirs "${path}/*")
|
|
__get_absolute_paths(PATHS "${dirs}" OUTPUT dirs_abs)
|
|
list(APPEND paths "${dirs_abs}")
|
|
endif()
|
|
|
|
endwhile()
|
|
|
|
set(${ARG_OUTPUT} "${component_paths}" PARENT_SCOPE)
|
|
endfunction()
|
|
|
|
#[[
|
|
__get_component_interface(COMPONENT <component>
|
|
OUTPUT <variable>)
|
|
|
|
*COMPONENT[int]*
|
|
|
|
Component name, target, target alias or interface.
|
|
|
|
*OUTPUT[out]*
|
|
|
|
Output variable to store the component interface.
|
|
|
|
Identify the component interface target using the ``<component>`` value,
|
|
which could be a component name with or without a namespace, target, target
|
|
alias, or interface. Return the component interface target, or NOTFOUND if
|
|
the interface cannot be located.
|
|
|
|
component interface: <component_prefix>_<component name>
|
|
component target: _<component_prefix>_<component name>
|
|
component alias: <component_prefix>::<component name>
|
|
#]]
|
|
function(__get_component_interface)
|
|
set(options)
|
|
set(one_value COMPONENT OUTPUT)
|
|
set(multi_value)
|
|
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
|
|
|
if(NOT DEFINED ARG_COMPONENT)
|
|
idf_die("COMPONENT option is required")
|
|
endif()
|
|
|
|
if(NOT DEFINED ARG_OUTPUT)
|
|
idf_die("OUTPUT option is required")
|
|
endif()
|
|
|
|
set(component_interface NOTFOUND)
|
|
|
|
__get_component_cached_interface(component_interface_cached "${ARG_COMPONENT}")
|
|
|
|
if(component_interface_cached)
|
|
set(component_interface "${component_interface_cached}")
|
|
else()
|
|
# Check if the COMPONENT argument is not an alias. If it is an alias,
|
|
# resolve it to the real target and search the interface cache again
|
|
# using the real target.
|
|
__get_real_target(TARGET "${ARG_COMPONENT}" OUTPUT real_target)
|
|
if(real_target)
|
|
__get_component_cached_interface(component_interface_cached "${real_target}")
|
|
if(component_interface_cached)
|
|
set(component_interface "${component_interface_cached}")
|
|
# The component is referred to by a new alias, so it should be
|
|
# added to the component interface cache.
|
|
__set_component_cached_interface("${ARG_COMPONENT}" "${component_interface_cached}")
|
|
endif()
|
|
endif()
|
|
endif()
|
|
|
|
set(${ARG_OUTPUT} ${component_interface} PARENT_SCOPE)
|
|
endfunction()
|
|
|
|
#[[
|
|
__get_component_interface_or_die(COMPONENT <component>
|
|
OUTPUT <variable>)
|
|
|
|
*COMPONENT[in]*
|
|
|
|
Component name, target, target alias or interface.
|
|
|
|
*OUTPUT[out]*
|
|
|
|
Output variable to store the component interface.
|
|
|
|
A simple wrapper for ``__get_component_interface`` that aborts the build
|
|
process if the component interface is not found.
|
|
#]]
|
|
function(__get_component_interface_or_die)
|
|
set(options)
|
|
set(one_value COMPONENT OUTPUT)
|
|
set(multi_value)
|
|
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
|
|
|
if(NOT DEFINED ARG_COMPONENT)
|
|
idf_die("COMPONENT option is required")
|
|
endif()
|
|
|
|
if(NOT DEFINED ARG_OUTPUT)
|
|
idf_die("OUTPUT option is required")
|
|
endif()
|
|
|
|
__get_component_interface(COMPONENT "${ARG_COMPONENT}" OUTPUT component_interface)
|
|
if("${component_interface}" STREQUAL "NOTFOUND")
|
|
idf_build_get_property(components_discovered COMPONENTS_DISCOVERED)
|
|
idf_die("Failed to resolve component '${ARG_COMPONENT}'.\n"
|
|
"Available components: ${components_discovered}")
|
|
endif()
|
|
set(${ARG_OUTPUT} ${component_interface} PARENT_SCOPE)
|
|
endfunction()
|
|
|
|
#[[
|
|
__get_component_priority(SOURCE <source>
|
|
OUTPUT <variable>)
|
|
|
|
*SOURCE[in]*
|
|
|
|
String identifying the component source.
|
|
|
|
*OUTPUT[out]*
|
|
|
|
Output variable to store the component priority.
|
|
|
|
Return the priority number of a component, where a higher number indicates
|
|
a higher priority, based on the given ``source`` string. If the ``source``
|
|
is not valid, return ``NOTFOUND``.
|
|
#]]
|
|
function(__get_component_priority)
|
|
set(options)
|
|
set(one_value SOURCE OUTPUT)
|
|
set(multi_value)
|
|
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
|
|
|
if(NOT DEFINED ARG_SOURCE)
|
|
idf_die("SOURCE option is required")
|
|
endif()
|
|
|
|
if(NOT DEFINED ARG_OUTPUT)
|
|
idf_die("OUTPUT option is required")
|
|
endif()
|
|
|
|
if("${ARG_SOURCE}" STREQUAL "project_components")
|
|
set(priority 3)
|
|
elseif("${ARG_SOURCE}" STREQUAL "project_extra_components")
|
|
set(priority 2)
|
|
elseif("${ARG_SOURCE}" STREQUAL "project_managed_components")
|
|
set(priority 1)
|
|
elseif("${ARG_SOURCE}" STREQUAL "idf_components")
|
|
set(priority 0)
|
|
else()
|
|
set(priority NOTFOUND)
|
|
endif()
|
|
|
|
set(${ARG_OUTPUT} "${priority}" PARENT_SCOPE)
|
|
endfunction()
|
|
|
|
#[[
|
|
__init_component(DIRECTORY <path>
|
|
PREFIX <prefix>
|
|
SOURCE <source>)
|
|
|
|
*DIRECTORY[in]*
|
|
|
|
``<path>`` where the component is located.
|
|
|
|
*PREFIX[in]*
|
|
|
|
Prefix for component target names.
|
|
|
|
*SOURCE[in]*
|
|
|
|
String identifying the component source.
|
|
|
|
Initialize the component by creating a component interface target, allowing
|
|
properties to be attached to it and also add initial component properties.
|
|
At this stage, the component is not included in the build. The actual
|
|
component target, named as specified in the `COMPONENT_LIB` property, is
|
|
created later when ``add_subdirectory`` is called for the component's
|
|
directory based on the project or other component requirements.
|
|
|
|
Components are identified by the directory name they reside in. This means
|
|
that components with the same name might exist in different directory
|
|
paths. In such cases, the component with the higher priority is used.
|
|
Priority is determined by the component's source, as defined in
|
|
``__get_component_priority``. If a component with a higher priority than an
|
|
existing one is initialized, its name, targets, and other properties remain
|
|
the same. Only the directory, priority, and source are updated in the
|
|
already initialized component.
|
|
|
|
The name of the initialized component is added to the
|
|
``COMPONENTS_DISCOVERED`` build property, and its interface target is added
|
|
to the ``COMPONENT_INTERFACES`` build property. These two lists are used by
|
|
``__get_component_interface``, which searches for a component interface
|
|
based on the component name, alias, or targets, enabling the setting and
|
|
retrieval of component properties.
|
|
#]]
|
|
function(__init_component)
|
|
set(options)
|
|
set(one_value DIRECTORY PREFIX SOURCE)
|
|
set(multi_value)
|
|
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
|
|
|
if(NOT DEFINED ARG_DIRECTORY)
|
|
idf_die("DIRECTORY option is required")
|
|
endif()
|
|
|
|
if(NOT DEFINED ARG_SOURCE)
|
|
idf_die("SOURCE option is required")
|
|
endif()
|
|
|
|
if(NOT DEFINED ARG_PREFIX)
|
|
idf_die("PREFIX option is required")
|
|
endif()
|
|
|
|
set(component_source "${ARG_SOURCE}")
|
|
set(component_prefix "${ARG_PREFIX}")
|
|
get_filename_component(component_directory ${ARG_DIRECTORY} ABSOLUTE)
|
|
get_filename_component(component_name ${component_directory} NAME)
|
|
__get_component_priority(SOURCE "${component_source}" OUTPUT component_priority)
|
|
|
|
if(NOT EXISTS "${component_directory}/CMakeLists.txt")
|
|
idf_die("Directory '${component_directory}' does not contain a component")
|
|
endif()
|
|
|
|
if("${component_priority}" STREQUAL "NOTFOUND")
|
|
idf_die("Unknown component source '${component_source}' "
|
|
"for directory '${component_directory}'")
|
|
endif()
|
|
|
|
# Collect Kconfig files from this component directory.
|
|
idf_build_get_property(target IDF_TARGET)
|
|
__collect_kconfig_files_from_directory("${component_directory}" "${target}"
|
|
component_kconfig component_projbuild component_rename)
|
|
|
|
set(component_project_include "${component_directory}/project_include.cmake")
|
|
if(NOT EXISTS "${component_project_include}")
|
|
set(component_project_include "")
|
|
endif()
|
|
|
|
# Track manifest presence
|
|
if(EXISTS "${component_directory}/idf_component.yml")
|
|
idf_build_set_property(__COMPONENTS_WITH_MANIFESTS "${component_directory}" APPEND)
|
|
endif()
|
|
|
|
__get_component_interface(COMPONENT "${component_name}" OUTPUT existing_component_interface)
|
|
if(existing_component_interface)
|
|
# A component with the same name is already initialized. Check if it
|
|
# should be replaced with the component currently being initialized.
|
|
idf_component_get_property(existing_component_priority
|
|
"${existing_component_interface}"
|
|
COMPONENT_PRIORITY)
|
|
idf_component_get_property(existing_component_directory
|
|
"${existing_component_interface}"
|
|
COMPONENT_DIR)
|
|
idf_component_get_property(existing_component_source
|
|
"${existing_component_interface}"
|
|
COMPONENT_SOURCE)
|
|
|
|
if(${component_priority} EQUAL ${existing_component_priority})
|
|
idf_die("Component directory '${component_directory}' has the same "
|
|
"priority '${component_source}' as component directory "
|
|
"'${existing_component_directory}'")
|
|
|
|
elseif(${component_priority} LESS ${existing_component_priority})
|
|
idf_warn("Component directory '${component_directory}' has lower "
|
|
"priority '${component_source}' than component directory "
|
|
"'${existing_component_directory}' with priority "
|
|
"'${existing_component_source}' and will be ignored")
|
|
else()
|
|
idf_warn("Component '${component_name}' directory '${component_directory}' "
|
|
"with higher priority '${component_source}' will be used instead of "
|
|
"component directory '${existing_component_directory}' "
|
|
"with lower priority '${existing_component_source}'")
|
|
# The newly added component has a higher priority than the existing
|
|
# one. Since the component name and targets are identical, update
|
|
# the existing component with the new directory and priority.
|
|
idf_component_set_property("${component_name}" COMPONENT_DIR "${component_directory}")
|
|
idf_component_set_property("${component_name}" COMPONENT_SOURCE "${component_source}")
|
|
idf_component_set_property("${component_name}" COMPONENT_PRIORITY ${component_priority})
|
|
|
|
# Update component properties with new Kconfig files
|
|
idf_component_set_property("${component_name}" __KCONFIG "${component_kconfig}")
|
|
idf_component_set_property("${component_name}" __KCONFIG_PROJBUILD "${component_projbuild}")
|
|
idf_component_set_property("${component_name}" __SDKCONFIG_RENAME "${component_rename}")
|
|
|
|
# Update component project_include.cmake path.
|
|
idf_component_set_property("${component_name}" __PROJECT_INCLUDE "${component_project_include}")
|
|
endif()
|
|
|
|
return()
|
|
endif()
|
|
|
|
set(component_interface "${component_prefix}_${component_name}")
|
|
set(component_alias "${component_prefix}::${component_name}")
|
|
# Real component library target that needs to be created by the component.
|
|
set(component_target "_${component_interface}")
|
|
|
|
# Interface target is used to attach all component properties and is also
|
|
# used when the component is linked to other targets.
|
|
add_library("${component_interface}" INTERFACE)
|
|
|
|
# Initialize the component interface cache for the included component. This
|
|
# establishes mappings between the component name (with or without
|
|
# namespace), target, interface, and alias to the component interface. The
|
|
# component interface cache is utilized in the __get_component_interface
|
|
# method to retrieve a component interface, for example, based on the
|
|
# component name.
|
|
__init_component_interface_cache("${component_name}"
|
|
"${component_interface}"
|
|
"${component_alias}"
|
|
"${component_target}"
|
|
"${component_priority}")
|
|
|
|
idf_build_set_property(COMPONENTS_DISCOVERED "${component_name}" APPEND)
|
|
idf_build_set_property(COMPONENT_INTERFACES "${component_interface}" APPEND)
|
|
|
|
idf_component_set_property("${component_name}" COMPONENT_LIB "${component_target}")
|
|
idf_component_set_property("${component_name}" COMPONENT_TARGET "${component_target}")
|
|
idf_component_set_property("${component_name}" COMPONENT_NAME "${component_name}")
|
|
idf_component_set_property("${component_name}" COMPONENT_DIR "${component_directory}")
|
|
idf_component_set_property("${component_name}" COMPONENT_ALIAS "${component_alias}")
|
|
idf_component_set_property("${component_name}" COMPONENT_SOURCE "${component_source}")
|
|
idf_component_set_property("${component_name}" COMPONENT_INTERFACE "${component_interface}")
|
|
idf_component_set_property("${component_name}" COMPONENT_PRIORITY ${component_priority})
|
|
idf_component_set_property("${component_name}" COMPONENT_INCLUDED NO)
|
|
idf_component_set_property("${component_name}" __PREFIX "${ARG_PREFIX}")
|
|
|
|
# Set component properties for Kconfig files.
|
|
idf_component_set_property("${component_name}" __KCONFIG "${component_kconfig}")
|
|
idf_component_set_property("${component_name}" __KCONFIG_PROJBUILD "${component_projbuild}")
|
|
idf_component_set_property("${component_name}" __SDKCONFIG_RENAME "${component_rename}")
|
|
|
|
# Set component project_include.cmake.
|
|
idf_component_set_property("${component_name}" __PROJECT_INCLUDE "${component_project_include}")
|
|
endfunction()
|
|
|
|
#[[
|
|
__dump_component_properties(<components>)
|
|
|
|
*components[in]*
|
|
|
|
List of components whose properties should be displayed.
|
|
|
|
Dump all properties for the components listed in ``components``.
|
|
#]]
|
|
function(__dump_component_properties components)
|
|
foreach(component IN LISTS components)
|
|
idf_component_get_property(properties "${component}" COMPONENT_PROPERTIES)
|
|
idf_msg("component '${component}' properties: ${properties}")
|
|
foreach(property IN LISTS properties)
|
|
idf_component_get_property(value "${component}" "${property}")
|
|
idf_msg(" ${property}: ${value}")
|
|
endforeach()
|
|
endforeach()
|
|
endfunction()
|
|
|
|
#[[api
|
|
.. cmakev2:function:: idf_component_include
|
|
|
|
.. code-block:: cmake
|
|
|
|
idf_component_include(<name>
|
|
[INTERFACE <variable>])
|
|
|
|
*name[in]*
|
|
|
|
Component name.
|
|
|
|
*INTERFACE[out,opt]*
|
|
|
|
Optional variable where the name of the target interface for the
|
|
included component will be stored.
|
|
|
|
This is a core function of the build system, responsible for including the
|
|
specified component, as indicated by the ``name`` option, into the build
|
|
process. It does this by evaluating the component through the
|
|
``add_subdirectory`` call for the component's directory. The component is
|
|
responsible for creating its own CMake target, with the target name
|
|
provided via the ``COMPONENT_TARGET`` variable. This target is linked to
|
|
the component's interface target, which is managed internally by the build
|
|
system. The component's interface target is used by other components to
|
|
establish their dependencies.
|
|
|
|
When the ``INTERFACE`` variable is provided, the name of the included
|
|
component interface target will be stored in it.
|
|
#]]
|
|
function(idf_component_include name)
|
|
set(options)
|
|
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)
|
|
|
|
# Check if the component is already included, meaning the add_subdirectory
|
|
# has already been called for it and the component has been processed. If
|
|
# the component is already included, it will have the COMPONENT_INCLUDED
|
|
# set to YES.
|
|
idf_component_get_property(component_included "${name}" COMPONENT_INCLUDED)
|
|
if(component_included)
|
|
idf_dbg("Component '${name}' is already included.")
|
|
# Set the parent variable ARG_INTERFACE to include the target name of
|
|
# the component interface.
|
|
if(DEFINED ARG_INTERFACE)
|
|
set(${ARG_INTERFACE} ${component_interface} PARENT_SCOPE)
|
|
endif()
|
|
return()
|
|
endif()
|
|
|
|
idf_dbg("Including component '${name}'")
|
|
|
|
idf_component_get_property(component_name "${name}" COMPONENT_NAME)
|
|
idf_component_get_property(component_interface "${name}" COMPONENT_INTERFACE)
|
|
idf_component_get_property(component_target "${name}" COMPONENT_TARGET)
|
|
idf_component_get_property(component_directory "${name}" COMPONENT_DIR)
|
|
idf_component_get_property(component_alias "${name}" COMPONENT_ALIAS)
|
|
idf_component_get_property(component_lib "${name}" COMPONENT_LIB)
|
|
idf_component_get_property(component_version "${name}" COMPONENT_VERSION)
|
|
|
|
# The following cmakev1 backward-compatible variables are passed to the
|
|
# component's CMakeLists.txt.
|
|
set(COMPONENT_NAME ${component_name})
|
|
set(COMPONENT_DIR ${component_directory})
|
|
set(COMPONENT_ALIAS ${component_alias})
|
|
set(COMPONENT_LIB ${component_lib})
|
|
set(COMPONENT_VERSION ${component_version})
|
|
set(ESP_PLATFORM 1)
|
|
|
|
# The COMPONENT_TARGET is simply an alias for COMPONENT_LIB, which more
|
|
# accurately reflects its purpose in the cmakev2 build system. The
|
|
# component's responsibility is to create the target with the name
|
|
# specified in the COMPONENT_TARGET variable.
|
|
set(COMPONENT_TARGET ${component_target})
|
|
|
|
# To handle potential circular dependencies among components, maintain a
|
|
# record of currently evaluated components in the __DEPENDENCY_CHAIN. This
|
|
# helps in detecting and reporting circular dependencies, such as
|
|
# C1->C2->C1. In this scenario, C2 can still use the C1 interface target,
|
|
# but C1 will only be fully evaluated after C2 has been evaluated.
|
|
if("${component_name}" IN_LIST __DEPENDENCY_CHAIN)
|
|
idf_dbg("Component '${name}' in circular dependency chain '${__DEPENDENCY_CHAIN}'")
|
|
return()
|
|
endif()
|
|
|
|
list(APPEND __DEPENDENCY_CHAIN "${name}")
|
|
# Evaluate the CMakeLists.txt file of the component.
|
|
idf_build_get_property(build_dir BUILD_DIR build_dir)
|
|
add_subdirectory("${component_directory}" "${build_dir}/esp-idf/${component_name}")
|
|
|
|
# The component has been evaluated; remove it from the dependency chain.
|
|
list(POP_BACK __DEPENDENCY_CHAIN)
|
|
|
|
# Mark the component as included and add it to the COMPONENTS_INCLUDED
|
|
# build property. Note that COMPONENTS_INCLUDED is read again because it
|
|
# might have been updated further down the dependency chain.
|
|
idf_component_set_property("${component_name}" COMPONENT_INCLUDED YES)
|
|
idf_build_get_property(components_included COMPONENTS_INCLUDED)
|
|
list(APPEND components_included "${component_name}")
|
|
idf_build_set_property(COMPONENTS_INCLUDED "${components_included}")
|
|
|
|
idf_component_get_property(component_interface "${name}" COMPONENT_INTERFACE)
|
|
if(DEFINED ARG_INTERFACE)
|
|
set(${ARG_INTERFACE} ${component_interface} PARENT_SCOPE)
|
|
endif()
|
|
|
|
if(NOT TARGET "${component_target}")
|
|
# The cmakev1 system allows component to return without creating any
|
|
# cmake target. For instance, a component might not be supported for a
|
|
# given IDF_TARGET. Ideally, we should fail and abort the build process
|
|
# in such cases. However, for compatibility reasons, the absence of
|
|
# component's target is silently ignored here.
|
|
idf_dbg("Component '${name}' lacks a target")
|
|
idf_component_set_property("${component_name}" COMPONENT_REAL_TARGET NOTFOUND)
|
|
return()
|
|
endif()
|
|
|
|
# The component might use the COMPONENT_TARGET name as an alias for another
|
|
# target. In this case, although unlikely, identify the real target and
|
|
# store it in the COMPONENT_REAL_TARGET component property.
|
|
__get_real_target(TARGET "${component_target}" OUTPUT component_real_target)
|
|
idf_component_set_property("${component_name}" COMPONENT_REAL_TARGET "${component_real_target}")
|
|
|
|
# Create a component interface alias, but only after the real target is
|
|
# known, meaning the component is included. The alias has a well-defined
|
|
# name and can be used, for example, in generator expressions without
|
|
# needing to get the COMPONENT_ALIAS property.
|
|
idf_component_get_property(component_alias "${name}" COMPONENT_ALIAS)
|
|
add_library("${component_alias}" ALIAS "${component_interface}")
|
|
|
|
# Set component target type as COMPONENT_REAL_TARGET_TYPE property.
|
|
get_target_property(component_real_target_type "${component_real_target}" TYPE)
|
|
idf_component_set_property("${component_name}" COMPONENT_REAL_TARGET_TYPE "${component_real_target_type}")
|
|
|
|
# Link the target created by the component to the component interface
|
|
# target.
|
|
if("${component_real_target_type}" STREQUAL "INTERFACE_LIBRARY")
|
|
target_link_libraries("${component_interface}" INTERFACE "${component_real_target}")
|
|
elseif("${component_real_target_type}" STREQUAL "STATIC_LIBRARY")
|
|
idf_component_get_property(whole_archive "${component_name}" WHOLE_ARCHIVE)
|
|
if(whole_archive)
|
|
idf_build_get_property(linker_type LINKER_TYPE)
|
|
if(linker_type STREQUAL "GNU")
|
|
target_link_libraries("${component_interface}" INTERFACE
|
|
"-Wl,--whole-archive" "${component_real_target}" "-Wl,--no-whole-archive")
|
|
elseif(linker_type STREQUAL "Darwin")
|
|
target_link_libraries("${component_interface}" INTERFACE
|
|
"-Wl,-force_load" "${component_real_target}")
|
|
endif()
|
|
else()
|
|
target_link_libraries("${component_interface}" INTERFACE "${component_real_target}")
|
|
endif()
|
|
else()
|
|
idf_die("Unsupported target type '${component_real_target_type}' in component '${component_name}'")
|
|
endif()
|
|
|
|
idf_component_get_property(embed_files "${component_name}" EMBED_FILES)
|
|
foreach(file IN LISTS embed_files)
|
|
target_add_binary_data(${COMPONENT_TARGET} "${file}" "BINARY")
|
|
endforeach()
|
|
|
|
idf_component_get_property(embed_txtfiles "${component_name}" EMBED_TXTFILES)
|
|
foreach(file IN LISTS embed_txtfiles)
|
|
target_add_binary_data(${COMPONENT_TARGET} "${file}" "TEXT")
|
|
endforeach()
|
|
|
|
# Inject managed dependencies if component manager is enabled
|
|
idf_build_get_property(idf_component_manager IDF_COMPONENT_MANAGER)
|
|
idf_component_get_property(component_format "${component_name}" COMPONENT_FORMAT)
|
|
if(idf_component_manager EQUAL 1)
|
|
idf_component_get_property(component_dir "${component_name}" COMPONENT_DIR)
|
|
# Check if component has manifest for managed dependency injection
|
|
if(EXISTS "${component_dir}/idf_component.yml")
|
|
__inject_requirements_for_component_from_manager("${component_name}")
|
|
|
|
# Include any managed dependencies
|
|
idf_component_get_property(managed_requires "${component_name}" MANAGED_REQUIRES)
|
|
idf_component_get_property(managed_priv_requires "${component_name}" MANAGED_PRIV_REQUIRES)
|
|
|
|
foreach(dep IN LISTS managed_requires managed_priv_requires)
|
|
if(dep)
|
|
idf_component_include("${dep}")
|
|
endif()
|
|
endforeach()
|
|
|
|
# For cmakev1 components, automatically link managed dependencies to maintain
|
|
# backward compatibility.
|
|
if("${component_format}" STREQUAL "CMAKEV1")
|
|
idf_component_get_property(component_type "${component_name}" COMPONENT_TYPE)
|
|
|
|
# Link managed public requirements
|
|
foreach(req IN LISTS managed_requires)
|
|
if(req)
|
|
idf_component_get_property(req_interface "${req}" COMPONENT_INTERFACE)
|
|
if(${component_type} STREQUAL LIBRARY)
|
|
target_link_libraries("${component_target}" PUBLIC "${req_interface}")
|
|
else()
|
|
target_link_libraries("${component_target}" INTERFACE "${req_interface}")
|
|
endif()
|
|
endif()
|
|
endforeach()
|
|
|
|
# Link managed private requirements
|
|
foreach(req IN LISTS managed_priv_requires)
|
|
if(req)
|
|
idf_component_get_property(req_interface "${req}" COMPONENT_INTERFACE)
|
|
if(${component_type} STREQUAL CONFIG_ONLY)
|
|
continue()
|
|
endif()
|
|
target_link_libraries("${component_target}" PRIVATE "${req_interface}")
|
|
endif()
|
|
endforeach()
|
|
endif()
|
|
endif()
|
|
endif()
|
|
|
|
# Components for cmakev1 use the idf_component_register call and are
|
|
# managed in cmakev2 through a shim. This shim sets the COMPONENT_FORMAT
|
|
# property to CMAKEV1 and applies global compilation options and
|
|
# definitions using CMake's directory-scoped function for backward
|
|
# compatibility. Therefore, no additional configuration is required.
|
|
if("${component_format}" STREQUAL "CMAKEV1")
|
|
return()
|
|
endif()
|
|
|
|
# The component was not handled by the idf_component_register shim,
|
|
# consider it as a component for cmakev2.
|
|
idf_component_set_property("${component_name}" COMPONENT_FORMAT CMAKEV2)
|
|
|
|
# The component's target is an interface library; nothing more needs to be
|
|
# set.
|
|
if("${component_real_target_type}" STREQUAL "INTERFACE_LIBRARY")
|
|
return()
|
|
endif()
|
|
|
|
idf_build_get_property(include_directories INCLUDE_DIRECTORIES GENERATOR_EXPRESSION)
|
|
target_include_directories("${component_real_target}" BEFORE PRIVATE "${include_directories}")
|
|
|
|
idf_build_get_property(compile_definitions COMPILE_DEFINITIONS GENERATOR_EXPRESSION)
|
|
target_compile_definitions("${component_real_target}" PRIVATE "${compile_definitions}")
|
|
|
|
__get_compile_options(OUTPUT compile_options)
|
|
target_compile_options("${component_real_target}" BEFORE PRIVATE "${compile_options}")
|
|
endfunction()
|