mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-01 18:50:34 +03:00
feat(cmakev2): Added support for injecting dependencies for components
This commit is contained in:
committed by
Frantisek Hrbata
parent
506fa52485
commit
7835d5d38c
@@ -186,10 +186,15 @@ function(__get_component_interface)
|
||||
idf_build_get_property(component_prefix PREFIX)
|
||||
|
||||
set(component_interface NOTFOUND)
|
||||
if("${ARG_COMPONENT}" IN_LIST component_names)
|
||||
# The component name is among the discovered components, and the
|
||||
# component interface is simply the component name with a prefix.
|
||||
set(component_interface "${component_prefix}_${ARG_COMPONENT}")
|
||||
|
||||
# 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}")
|
||||
else()
|
||||
# The component name might be an alias, so retrieve the actual target
|
||||
# name.
|
||||
@@ -467,6 +472,153 @@ 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.
|
||||
#]]
|
||||
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}")
|
||||
|
||||
# 1. If exact name exists, return it (highest priority match)
|
||||
if("${component}" IN_LIST known_components)
|
||||
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}'")
|
||||
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}'")
|
||||
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})")
|
||||
set(${ARG_OUTPUT} "${best_match}" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
# 5. No match found, return original name (will likely cause CMake error)
|
||||
set(${ARG_OUTPUT} "${component}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
#[[api
|
||||
.. cmakev2:function:: idf_component_include
|
||||
|
||||
@@ -498,13 +650,21 @@ function(idf_component_include name)
|
||||
set(multi_value)
|
||||
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
||||
|
||||
# Check that the specified component name is among the discovered and
|
||||
# recognized components.
|
||||
# Resolve the component name to its namespaced form if needed
|
||||
idf_build_get_property(components_discovered COMPONENTS_DISCOVERED)
|
||||
if(NOT "${name}" IN_LIST components_discovered)
|
||||
idf_die("Component '${name}' not found. Available components: '${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}")
|
||||
|
||||
# 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)
|
||||
@@ -635,12 +795,61 @@ function(idf_component_include name)
|
||||
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.
|
||||
idf_component_get_property(component_format "${component_name}" COMPONENT_FORMAT)
|
||||
if("${component_format}" STREQUAL "CMAKEV1")
|
||||
return()
|
||||
endif()
|
||||
|
||||
+120
-12
@@ -82,9 +82,6 @@ function(__fetch_components_from_registry)
|
||||
idf_die("IDF Component Manager error: ${cmgr_result}")
|
||||
endif()
|
||||
endwhile()
|
||||
|
||||
# Auto-include all project-level managed components after component manager is done
|
||||
__include_project_level_managed_components()
|
||||
endfunction()
|
||||
|
||||
#[[
|
||||
@@ -178,15 +175,8 @@ function(__download_component_level_managed_components)
|
||||
set(__contents "components:\n")
|
||||
idf_build_get_property(component_names COMPONENTS_DISCOVERED)
|
||||
foreach(name ${component_names})
|
||||
# Respect EXCLUDE_COMPONENTS passed via -D
|
||||
set(__include 1)
|
||||
if(DEFINED EXCLUDE_COMPONENTS AND name IN_LIST EXCLUDE_COMPONENTS)
|
||||
set(__include 0)
|
||||
endif()
|
||||
if(__include)
|
||||
idf_component_get_property(dir ${name} COMPONENT_DIR)
|
||||
set(__contents "${__contents} - name: \"${name}\"\n path: \"${dir}\"\n")
|
||||
endif()
|
||||
idf_component_get_property(dir ${name} COMPONENT_DIR)
|
||||
set(__contents "${__contents} - name: \"${name}\"\n path: \"${dir}\"\n")
|
||||
endforeach()
|
||||
file(WRITE ${local_components_list_file} "${__contents}")
|
||||
|
||||
@@ -232,3 +222,121 @@ function(__component_manager_warn_if_disabled_and_manifests_exist)
|
||||
${with_lines}\nHowever, the component manager is not enabled.")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
#[[
|
||||
__component_set_property(target property value)
|
||||
|
||||
Shim for setting component properties, primarily for use by the component manager
|
||||
in build system v2. This function only processes dependency-related properties
|
||||
(MANAGED_REQUIRES and MANAGED_PRIV_REQUIRES) produced by the component manager's
|
||||
injection file. Other properties are ignored to avoid interfering with the
|
||||
cmakev2 build flow. Target names with triple underscores are normalized.
|
||||
#]]
|
||||
function(__component_set_property target property value)
|
||||
# If the target has 3 underscores, remove all of them and normalize the target
|
||||
# This shim is only intended to process dependency-related properties produced
|
||||
# by the component manager injection file. Ignore unrelated properties to avoid
|
||||
# clobbering configuration already set by the cmakev2 build flow.
|
||||
string(REPLACE "___" "" target "${target}")
|
||||
|
||||
# We only consume MANAGED_REQUIRES and MANAGED_PRIV_REQUIRES from the component manager.
|
||||
# The manager's REQUIRES/PRIV_REQUIRES output contains both original and resolved names,
|
||||
# which we don't want. We'll handle name resolution locally using our utility functions.
|
||||
if(property STREQUAL "MANAGED_REQUIRES")
|
||||
# Set the managed property for tracking
|
||||
idf_component_set_property("${target}" "${property}" "${value}")
|
||||
# Also append to the regular REQUIRES property
|
||||
idf_component_set_property("${target}" REQUIRES "${value}" APPEND)
|
||||
elseif(property STREQUAL "MANAGED_PRIV_REQUIRES")
|
||||
# Set the managed property for tracking
|
||||
idf_component_set_property("${target}" "${property}" "${value}")
|
||||
# Also append to the regular PRIV_REQUIRES property
|
||||
idf_component_set_property("${target}" PRIV_REQUIRES "${value}" APPEND)
|
||||
else()
|
||||
# Ignore REQUIRES, PRIV_REQUIRES, and other properties like INCLUDE_DIRS,
|
||||
# __COMPONENT_SOURCE, __COMPONENT_REGISTERED, etc.
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
#[[
|
||||
__inject_requirements_for_component_from_manager(<component_name>)
|
||||
|
||||
Managed dependency injection for a single component in build system v2.
|
||||
Calls the Component Manager to compute manifest-derived dependencies and
|
||||
updates the component's MANAGED_* properties.
|
||||
#]]
|
||||
function(__inject_requirements_for_component_from_manager component_name)
|
||||
# Skip if already injected
|
||||
idf_component_get_property(already_injected "${component_name}" __MANAGED_INJECTED)
|
||||
if(already_injected)
|
||||
return()
|
||||
endif()
|
||||
|
||||
idf_dbg("Injecting requirements for component '${component_name}' from the component manager")
|
||||
|
||||
idf_build_get_property(python PYTHON)
|
||||
idf_build_get_property(project_dir PROJECT_DIR)
|
||||
idf_build_get_property(build_dir BUILD_DIR)
|
||||
idf_build_get_property(dependencies_lock_file DEPENDENCIES_LOCK)
|
||||
idf_build_get_property(sdkconfig_json __SDKCONFIG_JSON)
|
||||
idf_build_get_property(component_manager_interface_version IDF_COMPONENT_MANAGER_INTERFACE_VERSION)
|
||||
idf_build_get_property(idf_path IDF_PATH)
|
||||
idf_build_get_property(component_prefix PREFIX)
|
||||
idf_component_get_property(component_source "${component_name}" COMPONENT_SOURCE)
|
||||
idf_component_get_property(component_dir "${component_name}" COMPONENT_DIR)
|
||||
|
||||
# The component manager will inject requirements for this component. To do this, it needs to files:
|
||||
#
|
||||
# 1. An input file which states the component's source type. This is a minimal build system v1-style file
|
||||
# which contains the component's source type. To make the component manager happy, we create a file with
|
||||
# shim __component_set_property(), which calls idf_component_set_property(). The component manager will
|
||||
# modify this file by adding the component's requirements. TODO: Improve this.
|
||||
# 2. A file which lists the components with manifests. This file is created by the component manager,
|
||||
# and is deleted after the component manager is done. This works for build system v1 where we provide
|
||||
# a global list of components with manifests. However, for build system v2, we need to provide this file
|
||||
# for each component. Hence, we create this file and place it in the build directory.
|
||||
set(out_file "${build_dir}/component_requires.${component_name}.temp.cmake")
|
||||
set(cmgr_target "___${component_prefix}_${component_name}")
|
||||
# We only provide component source to the component manager
|
||||
file(WRITE "${out_file}" "__component_set_property(${cmgr_target} __COMPONENT_SOURCE \"${component_source}\")\n")
|
||||
|
||||
# Create components_with_manifests_list.temp file with only this component if it has a manifest
|
||||
set(components_with_manifests_file "${build_dir}/components_with_manifests_list.temp")
|
||||
if(EXISTS "${component_dir}/idf_component.yml")
|
||||
file(WRITE "${components_with_manifests_file}" "${component_dir}\n")
|
||||
else()
|
||||
file(WRITE "${components_with_manifests_file}" "")
|
||||
endif()
|
||||
|
||||
# Call component manager to inject requirements
|
||||
execute_process(COMMAND ${python}
|
||||
"-m"
|
||||
"idf_component_manager.prepare_components"
|
||||
"--project_dir=${project_dir}"
|
||||
"--lock_path=${dependencies_lock_file}"
|
||||
"--sdkconfig_json_file=${sdkconfig_json}"
|
||||
"--interface_version=${component_manager_interface_version}"
|
||||
"inject_requirements"
|
||||
"--idf_path=${idf_path}"
|
||||
"--build_dir=${build_dir}"
|
||||
"--component_requires_file=${out_file}"
|
||||
RESULT_VARIABLE result
|
||||
ERROR_VARIABLE error)
|
||||
|
||||
if(NOT result EQUAL 0)
|
||||
idf_die("Component manager requirements injection failed for '${component_name}': ${error}")
|
||||
endif()
|
||||
|
||||
# Include the component manager's output
|
||||
if(EXISTS "${out_file}")
|
||||
include("${out_file}")
|
||||
endif()
|
||||
|
||||
# Clean up temporary files
|
||||
if(NOT DEFINED ENV{IDF_KEEP_CMANAGER_TEMP} OR NOT "$ENV{IDF_KEEP_CMANAGER_TEMP}" STREQUAL "1")
|
||||
file(REMOVE "${out_file}")
|
||||
file(REMOVE "${components_with_manifests_file}")
|
||||
endif()
|
||||
|
||||
idf_component_set_property("${component_name}" __MANAGED_INJECTED YES)
|
||||
endfunction()
|
||||
|
||||
Reference in New Issue
Block a user