mirror of
https://github.com/espressif/esp-idf.git
synced 2026-01-05 17:48:59 +00:00
Currently, the archive file name is set only in the cmakev1 idf_component_register shim. The predictable component archive file name is important, for example, for usage in linker fragments. Ensure that the cmakev2 component also has the archive file name set. Signed-off-by: Frantisek Hrbata <frantisek.hrbata@espressif.com>
1027 lines
41 KiB
CMake
1027 lines
41 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.
|
|
|
|
# Store the overridden component directory (original component directory) for compatibility with cmakev1
|
|
idf_component_set_property("${component_name}" COMPONENT_OVERRIDEN_DIR "${existing_component_directory}")
|
|
|
|
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}")
|
|
|
|
idf_build_get_property(build_dir BUILD_DIR)
|
|
set(component_build_dir "${build_dir}/esp-idf/${component_name}")
|
|
|
|
# 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_BUILD_DIR "${component_build_dir}")
|
|
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()
|
|
|
|
#[[
|
|
__set_component_cmakev1_properties(<component>)
|
|
|
|
*component[in]*
|
|
|
|
Component name.
|
|
|
|
To maintain backward compatibility with cmakev1 components, add the
|
|
component properties: INCLUDE_DIRS, PRIV_INCLUDE_DIRS, REQUIRES,
|
|
PRIV_REQUIRES, and SRCS. This is achieved by examining the real target
|
|
properties of the component and reconstructing the variables that are
|
|
otherwise explicitly provided with idf_component_register for cmakev1
|
|
components.
|
|
#]]
|
|
function(__set_component_cmakev1_properties component_name)
|
|
idf_component_get_property(component_real_target "${component_name}" COMPONENT_REAL_TARGET)
|
|
idf_component_get_property(component_dir "${component_name}" COMPONENT_DIR)
|
|
|
|
if(NOT component_real_target)
|
|
return()
|
|
endif()
|
|
|
|
# Set SRCS and COMPONENT_TYPE properties
|
|
get_target_property(component_sources "${component_real_target}" SOURCES)
|
|
if(component_sources)
|
|
__get_absolute_paths(PATHS "${component_sources}" BASE_DIR "${component_dir}" OUTPUT sources)
|
|
idf_component_set_property("${component_name}" SOURCES "${sources}")
|
|
idf_component_set_property("${component_name}" COMPONENT_TYPE LIBRARY)
|
|
else()
|
|
idf_component_set_property("${component_name}" COMPONENT_TYPE CONFIG_ONLY)
|
|
endif()
|
|
|
|
# Set INCLUDE_DIRS and PRIV_INCLUDE_DIRS properties
|
|
get_target_property(include_dirs "${component_real_target}" INCLUDE_DIRECTORIES)
|
|
if(NOT include_dirs)
|
|
# get_target_property may return include_dirs-NOTFOUND
|
|
set(include_dirs "")
|
|
endif()
|
|
__remove_genex(include_dirs)
|
|
|
|
get_target_property(interface_include_dirs "${component_real_target}" INTERFACE_INCLUDE_DIRECTORIES)
|
|
if(NOT interface_include_dirs)
|
|
# get_target_property may return interface_include_dirs-NOTFOUND
|
|
set(interface_include_dirs "")
|
|
endif()
|
|
__remove_genex(interface_include_dirs)
|
|
|
|
__list_intersection(interface_include_dirs include_dirs public_include_dirs_abs)
|
|
__list_difference(include_dirs public_include_dirs_abs priv_include_dirs_abs)
|
|
|
|
__get_relative_paths(PATHS "${public_include_dirs_abs}" BASE_DIR "${component_dir}" OUTPUT public_include_dirs)
|
|
__get_relative_paths(PATHS "${priv_include_dirs_abs}" BASE_DIR "${component_dir}" OUTPUT priv_include_dirs)
|
|
|
|
idf_component_set_property(${component_name} INCLUDE_DIRS "${public_include_dirs}")
|
|
idf_component_set_property(${component_name} PRIV_INCLUDE_DIRS "${priv_include_dirs}")
|
|
|
|
# Set REQUIRES and PRIV_REQUIRES properties
|
|
get_target_property(link_libraries ${component_real_target} LINK_LIBRARIES)
|
|
set(link_components "")
|
|
foreach(lib IN LISTS link_libraries)
|
|
__get_component_interface(COMPONENT "${lib}" OUTPUT req_interface)
|
|
if(req_interface)
|
|
idf_component_get_property(req_name "${req_interface}" COMPONENT_NAME)
|
|
list(APPEND link_components "${req_name}")
|
|
endif()
|
|
endforeach()
|
|
|
|
get_target_property(interface_link_libraries ${component_real_target} INTERFACE_LINK_LIBRARIES)
|
|
set(interface_link_components "")
|
|
foreach(lib IN LISTS interface_link_libraries)
|
|
__get_component_interface(COMPONENT "${lib}" OUTPUT req_interface)
|
|
if(req_interface)
|
|
idf_component_get_property(req_name "${req_interface}" COMPONENT_NAME)
|
|
list(APPEND interface_link_components "${req_name}")
|
|
endif()
|
|
endforeach()
|
|
|
|
__list_intersection(interface_link_components link_components requires)
|
|
__list_difference(link_components requires priv_requires)
|
|
|
|
idf_component_set_property(${component_name} REQUIRES "${requires}")
|
|
idf_component_set_property(${component_name} PRIV_REQUIRES "${priv_requires}")
|
|
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_component_get_property(component_build_dir "${component_name}" COMPONENT_BUILD_DIR)
|
|
add_subdirectory("${component_directory}" "${component_build_dir}")
|
|
|
|
# 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_options("${component_interface}" INTERFACE
|
|
"SHELL:-Wl,--whole-archive $<TARGET_FILE:${component_real_target}> -Wl,--no-whole-archive")
|
|
target_link_libraries("${component_interface}" INTERFACE "${component_real_target}")
|
|
elseif(linker_type STREQUAL "Darwin")
|
|
target_link_options("${component_interface}" INTERFACE
|
|
"SHELL:-Wl,-force_load $<TARGET_FILE:${component_real_target}>")
|
|
target_link_libraries("${component_interface}" INTERFACE "${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)
|
|
|
|
# Add INCLUDE_DIRS, PRIV_INCLUDE_DIRS, REQUIRES, PRIV_REQUIRES and SRCS
|
|
# component properties.
|
|
__set_component_cmakev1_properties("${component_name}")
|
|
|
|
# The component's target is an interface library; nothing more needs to be
|
|
# set.
|
|
if("${component_real_target_type}" STREQUAL "INTERFACE_LIBRARY")
|
|
return()
|
|
endif()
|
|
|
|
# Set the component archive file name.
|
|
set_target_properties(${component_real_target} PROPERTIES OUTPUT_NAME ${component_name} LINKER_LANGUAGE C)
|
|
|
|
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()
|