diff --git a/tools/cmakev2/component.cmake b/tools/cmakev2/component.cmake index 4f3fa660703..0c0c053ad0c 100644 --- a/tools/cmakev2/component.cmake +++ b/tools/cmakev2/component.cmake @@ -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 + OUTPUT ) + + :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 + KNOWN_COMPONENTS + OUTPUT ) + + :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() diff --git a/tools/cmakev2/manager.cmake b/tools/cmakev2/manager.cmake index ca20b9062b9..e5ce56f5f88 100644 --- a/tools/cmakev2/manager.cmake +++ b/tools/cmakev2/manager.cmake @@ -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() + + 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()