feat(cmakev2/build): introduce component interface cache

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>
This commit is contained in:
Frantisek Hrbata
2025-11-04 07:48:14 +01:00
parent aa6f4e076a
commit 1afc554e6c
2 changed files with 179 additions and 265 deletions
+173 -265
View File
@@ -99,43 +99,149 @@ function(idf_component_get_property variable component property)
endfunction()
#[[
idf_component_get_cached_name(<variable> <component>)
__get_component_cached_interface(<variable> <component>)
:variable[out]: Variable to store the cached resolved name in, or empty if not found.
:component[in]: Component name to look up in the cache.
*variable[out]*
Get a cached resolved component name
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(idf_component_get_cached_name variable component)
# Create a target to use to store the cached resolved name
if(NOT TARGET __idf_component_name_cache)
add_library(__idf_component_name_cache INTERFACE)
endif()
__get_property(TARGET __idf_component_name_cache
PROPERTY "__IDF_COMPONENT_NAME_CACHE_${component}"
OUTPUT cached_name)
set(${variable} "${cached_name}" PARENT_SCOPE)
function(__get_component_cached_interface variable component)
__get_property(TARGET __idf_component_interface_cache
PROPERTY "${component}"
OUTPUT interface)
set(${variable} "${interface}" PARENT_SCOPE)
endfunction()
#[[
idf_component_set_cached_name(<component> <resolved_name>)
__set_component_cached_interface(<component> <interface>)
:component[in]: Original component name.
:resolved_name[in]: Resolved component name to cache.
*component[in]*
Store a resolved component name in the component name cache.
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(idf_component_set_cached_name component resolved_name)
# Create a target to use to store the cached resolved name
if(NOT TARGET __idf_component_name_cache)
add_library(__idf_component_name_cache INTERFACE)
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()
__set_property(TARGET __idf_component_name_cache
PROPERTY "__IDF_COMPONENT_NAME_CACHE_${component}"
PROPERTIES __IDF_COMPONENT_NAME_CACHE_PROPERTIES
VALUE "${resolved_name}")
__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()
#[[
@@ -238,9 +344,9 @@ endfunction()
Output variable to store the component interface.
Identify the component interface target using the ``<component>`` value,
which could be a component name, target, target alias, or interface. Return
the component interface target, or NOTFOUND if the interface cannot be
located.
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>
@@ -260,54 +366,28 @@ function(__get_component_interface)
idf_die("OUTPUT option is required")
endif()
idf_build_get_property(component_names COMPONENTS_DISCOVERED)
idf_build_get_property(component_interfaces COMPONENT_INTERFACES)
idf_build_get_property(component_prefix PREFIX)
set(component_interface NOTFOUND)
# Search by the exact component name first (most common case)
if("${ARG_COMPONENT}" IN_LIST component_names)
set(component_interface "${component_prefix}_${ARG_COMPONENT}")
__get_component_cached_interface(component_interface_cached "${ARG_COMPONENT}")
if(component_interface_cached)
set(component_interface "${component_interface_cached}")
else()
# Search by component interface
set(interface "${component_prefix}_${ARG_COMPONENT}")
if("${interface}" IN_LIST component_interfaces)
set(component_interface "${interface}")
else()
# Search by real target/alias
__get_real_target(TARGET ${ARG_COMPONENT} OUTPUT real_target)
if("${real_target}" IN_LIST component_interfaces)
# The component name is already a component interface or its alias
set(component_interface "${real_target}")
else()
string(SUBSTRING "${ARG_COMPONENT}" 1 -1 interface)
if("${interface}" IN_LIST component_interfaces)
# The component name is the actual target of the component
set(component_interface "${interface}")
else()
# Try to resolve component name from short name to namespaced name
__resolve_component_name(COMPONENT "${ARG_COMPONENT}"
KNOWN_COMPONENTS "${component_names}"
OUTPUT resolved_component)
if("${resolved_component}" IN_LIST component_names)
# Found a resolved component name, use it
set(component_interface "${component_prefix}_${resolved_component}")
endif()
endif()
# 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()
# Sanity check
if(NOT "${component_interface}" STREQUAL "NOTFOUND"
AND NOT "${component_interface}" IN_LIST component_interfaces)
idf_warn("Interface target '${component_interface}' found for component "
"'${ARG_COMPONENT}', but it's not present in the component "
"interface list.")
set(component_interface NOTFOUND)
endif()
set(${ARG_OUTPUT} ${component_interface} PARENT_SCOPE)
endfunction()
@@ -342,7 +422,9 @@ function(__get_component_interface_or_die)
__get_component_interface(COMPONENT "${ARG_COMPONENT}" OUTPUT component_interface)
if("${component_interface}" STREQUAL "NOTFOUND")
idf_die("Component interface for component '${ARG_COMPONENT}' does not exist")
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()
@@ -481,7 +563,7 @@ function(__init_component)
endif()
__get_component_interface(COMPONENT "${component_name}" OUTPUT existing_component_interface)
if(NOT "${existing_component_interface}" STREQUAL "NOTFOUND")
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
@@ -537,6 +619,18 @@ function(__init_component)
# 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)
@@ -580,181 +674,6 @@ function(__dump_component_properties components)
endforeach()
endfunction()
#[[
__component_name_without_namespace(COMPONENT <component_name>
OUTPUT <variable>)
*COMPONENT[in]*
Component name that may include namespace.
*OUTPUT[out]*
Output variable to store the component name without namespace.
Extract the component name without namespace. For example:
- "espressif__led_strip" -> "led_strip"
- "led_strip" -> "led_strip" This follows the same logic as the component
manager's name_without_namespace function.
#]]
function(__component_name_without_namespace)
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()
# Split by "__" and take the last part (same logic as component manager)
# Use rsplit with maxsplit=1 equivalent: split and take last part
string(FIND "${ARG_COMPONENT}" "__" last_pos REVERSE)
if(last_pos GREATER -1)
math(EXPR start_pos "${last_pos} + 2")
string(SUBSTRING "${ARG_COMPONENT}" ${start_pos} -1 name_without_ns)
else()
set(name_without_ns "${ARG_COMPONENT}")
endif()
set(${ARG_OUTPUT} "${name_without_ns}" PARENT_SCOPE)
endfunction()
#[[
__resolve_component_name(COMPONENT <component_name>
KNOWN_COMPONENTS <known_components_list>
OUTPUT <variable>)
*COMPONENT[in]*
Component name to resolve (may be short name or canonical).
*KNOWN_COMPONENTS[in]*
List of known/discovered component names.
*OUTPUT[out]*
Output variable to store the resolved component name.
Resolve a component name to its canonical form by checking against known components.
This implements the same logic as the component manager's _choose_component function:
1. If the exact name exists in known components, return it
2. If a namespaced version exists (e.g., "espressif__led_strip" for "led_strip"), return that
3. If a non-namespaced version exists (e.g., "led_strip" for "espressif__led_strip"), return that
4. If any known component has the same name without namespace , return that
5. Otherwise, return the original name (will likely cause CMake error)
This function takes component priority into account by preferring
components with higher priority when multiple matches exist. This behavior
differs from the component manager's _choose_component function.
This function also caches the resolved component name in the component name cache
to avoid re-resolving the same component name multiple times.
#]]
function(__resolve_component_name)
set(options)
set(one_value COMPONENT OUTPUT)
set(multi_value KNOWN_COMPONENTS)
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 "${ARG_COMPONENT}")
set(known_components "${ARG_KNOWN_COMPONENTS}")
# Lookup for previously resolved components in the cache
idf_component_get_cached_name(cached_result "${component}")
if(cached_result)
set(${ARG_OUTPUT} "${cached_result}" PARENT_SCOPE)
return()
endif()
# 1. If exact name exists, return it (highest priority match)
if("${component}" IN_LIST known_components)
idf_component_set_cached_name("${component}" "${component}")
set(${ARG_OUTPUT} "${component}" PARENT_SCOPE)
return()
endif()
# 2. Check for namespaced version (e.g., "led_strip" -> "espressif__led_strip")
# Look for components ending with "__${component}"
set(namespaced_suffix "__${component}")
set(best_match "")
set(best_priority -1)
foreach(known_component IN LISTS known_components)
string(LENGTH "${namespaced_suffix}" suffix_len)
string(LENGTH "${known_component}" comp_len)
if(comp_len GREATER_EQUAL suffix_len)
math(EXPR start_pos "${comp_len} - ${suffix_len}")
string(SUBSTRING "${known_component}" ${start_pos} -1 comp_suffix)
if("${comp_suffix}" STREQUAL "${namespaced_suffix}")
# Get component priority to choose the best match
idf_component_get_property(comp_priority "${known_component}" COMPONENT_PRIORITY)
if(comp_priority GREATER best_priority)
set(best_match "${known_component}")
set(best_priority ${comp_priority})
endif()
endif()
endif()
endforeach()
if(NOT "${best_match}" STREQUAL "")
idf_dbg("Component '${component}' resolved to namespaced version '${best_match}'")
idf_component_set_cached_name("${component}" "${best_match}")
set(${ARG_OUTPUT} "${best_match}" PARENT_SCOPE)
return()
endif()
# 3. Check for non-namespaced version (e.g., "espressif__led_strip" -> "led_strip")
__component_name_without_namespace(COMPONENT "${component}" OUTPUT component_without_ns)
if("${component_without_ns}" IN_LIST known_components)
idf_dbg("Component '${component}' resolved to non-namespaced version '${component_without_ns}'")
idf_component_set_cached_name("${component}" "${component_without_ns}")
set(${ARG_OUTPUT} "${component_without_ns}" PARENT_SCOPE)
return()
endif()
# 4. Check if any known component has the same name without namespace (cross-namespace matching)
set(best_match "")
set(best_priority -1)
foreach(known_component IN LISTS known_components)
__component_name_without_namespace(COMPONENT "${known_component}" OUTPUT known_without_ns)
if("${component_without_ns}" STREQUAL "${known_without_ns}")
# Get component priority to choose the best match
idf_component_get_property(comp_priority "${known_component}" COMPONENT_PRIORITY)
if(comp_priority GREATER best_priority)
set(best_match "${known_component}")
set(best_priority ${comp_priority})
endif()
endif()
endforeach()
if(NOT "${best_match}" STREQUAL "")
idf_dbg("Component '${component}'
resolved via cross-namespace matching to '${best_match}' (priority ${best_priority})")
idf_component_set_cached_name("${component}" "${best_match}")
set(${ARG_OUTPUT} "${best_match}" PARENT_SCOPE)
return()
endif()
# 5. No match found, return original name (will likely cause CMake error)
idf_component_set_cached_name("${component}" "${component}")
set(${ARG_OUTPUT} "${component}" PARENT_SCOPE)
endfunction()
#[[api
.. cmakev2:function:: idf_component_include
@@ -791,30 +710,19 @@ function(idf_component_include name)
set(multi_value)
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
# Resolve the component name to its namespaced form if needed
idf_build_get_property(components_discovered COMPONENTS_DISCOVERED)
__resolve_component_name(COMPONENT "${name}"
KNOWN_COMPONENTS "${components_discovered}"
OUTPUT resolved_name)
# Check that the resolved component name is among the discovered components
if(NOT "${resolved_name}" IN_LIST components_discovered)
idf_die("Component '${name}' (resolved to '${resolved_name}') not found.
Available components: '${components_discovered}'")
endif()
# Use the resolved name for the rest of the function
set(name "${resolved_name}")
__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.
idf_build_get_property(components_included COMPONENTS_INCLUDED)
if("${name}" IN_LIST components_included)
# 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)
idf_component_get_property(component_interface "${name}" COMPONENT_INTERFACE)
set(${ARG_INTERFACE} ${component_interface} PARENT_SCOPE)
endif()
return()
+6
View File
@@ -549,6 +549,12 @@ endfunction()
#]]
add_library(idf_build_properties INTERFACE)
# The __idf_component_interface_cache target is used to maintain internal
# mappings between component identifiers, such as component name or alias, and
# the component interface target, which is the primary target for the
# component.
add_library(__idf_component_interface_cache INTERFACE)
# Set build system prefix for component targets.
idf_build_set_property(PREFIX "idf")