mirror of
https://github.com/espressif/esp-idf.git
synced 2026-04-27 19:13:21 +00:00
3cbca31571
When the component manager is disabled via IDF_COMPONENT_MANAGER=0, the cmakev2 build system still entered the full component manager flow (__fetch_components_from_registry), which called __download_managed_component(). That function returned early with result=0 without creating the expected output file, violating the post-condition in __download_component_level_managed_components() that checks result==0 => file exists, causing a fatal error. Instead of patching the low-level function to write an empty stub file, this commit properly skips the entire component manager flow when the manager is disabled: - Move __init_component_manager() to idf.cmake global initialization sequence alongside other __init_*() calls, so IDF_COMPONENT_MANAGER build property is available early. - Set __SDKCONFIG_ORIG to the real sdkconfig path in __init_kconfig() as its default value. Previously it was only set inside __create_sdkconfig_orig_copy() and had a defensive fallback in __create_base_kconfgen_command(). The default ensures __SDKCONFIG_ORIG is always valid regardless of whether the component manager runs. - Guard __create_sdkconfig_orig_copy() with an IDF_COMPONENT_MANAGER check. The sdkconfig backup exists solely to preserve unknown Kconfig options from managed components during intermediate kconfgen rounds. When the manager is disabled, no managed components exist, so the backup is unnecessary. - Guard __fetch_components_from_registry() call in project.cmake behind IDF_COMPONENT_MANAGER == 1. When disabled, only the manifest warning is issued. No download loop runs, no temp files are created, and no "Component manager round N..." messages are printed. - Remove the now-redundant IDF_COMPONENT_MANAGER guard from __download_managed_component(), since it is only reachable when the manager is enabled. Closes https://github.com/espressif/esp-idf/issues/18372 Signed-off-by: Frantisek Hrbata <frantisek.hrbata@espressif.com>
354 lines
15 KiB
CMake
354 lines
15 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)
|
|
|
|
#[[
|
|
__init_component_manager()
|
|
|
|
Initialize component manager related build properties and defaults.
|
|
#]]
|
|
function(__init_component_manager)
|
|
# Set IDF_COMPONENT_MANAGER build property to 1 if not explicitly set to 0
|
|
# in the environment.
|
|
__get_default_value(VARIABLE IDF_COMPONENT_MANAGER
|
|
DEFAULT 1
|
|
OUTPUT component_manager_env)
|
|
if(component_manager_env STREQUAL "" OR NOT component_manager_env STREQUAL "0")
|
|
idf_build_set_property(IDF_COMPONENT_MANAGER 1)
|
|
else()
|
|
idf_build_set_property(IDF_COMPONENT_MANAGER 0)
|
|
endif()
|
|
|
|
# Set IDF_COMPONENT_MANAGER_INTERFACE_VERSION.
|
|
# Defaults to 4. Allow overriding via env/CMake.
|
|
__get_default_value(VARIABLE IDF_COMPONENT_MANAGER_INTERFACE_VERSION
|
|
DEFAULT 4
|
|
OUTPUT cmgr_iface)
|
|
idf_build_set_property(IDF_COMPONENT_MANAGER_INTERFACE_VERSION ${cmgr_iface})
|
|
|
|
# Set DEPENDENCIES_LOCK if provided via environment or CMake variable.
|
|
__get_default_value(VARIABLE DEPENDENCIES_LOCK
|
|
DEFAULT ""
|
|
OUTPUT deps_lock_file)
|
|
idf_build_set_property(DEPENDENCIES_LOCK "${deps_lock_file}")
|
|
endfunction()
|
|
|
|
#[[
|
|
__fetch_components_from_registry()
|
|
|
|
Iteratively run the component manager and Kconfig until stable or error
|
|
out. This routine allows 1 re-run if the manager fails with a missing
|
|
kconfig option. This behavior is similar to the build system v1.
|
|
|
|
This routine performs the following steps:
|
|
1. Run the component manager for all discovered components.
|
|
2. Re-collect Kconfig and regenerate sdkconfig with managed components included.
|
|
3. If the component manager run failed, error out.
|
|
#]]
|
|
function(__fetch_components_from_registry)
|
|
# Iteratively run the component manager and Kconfig until stable or error out.
|
|
set(__cmgr_round 0)
|
|
while(TRUE)
|
|
math(EXPR __cmgr_round "${__cmgr_round} + 1")
|
|
idf_msg("Component manager round ${__cmgr_round}...")
|
|
|
|
# Run the component manager for all discovered components
|
|
__download_component_level_managed_components(RESULT cmgr_result)
|
|
|
|
# Re-collect Kconfig and regenerate sdkconfig with managed components included
|
|
__generate_sdkconfig()
|
|
|
|
# If component manager run failed, use the failure result
|
|
if(cmgr_result EQUAL 0)
|
|
break()
|
|
elseif(cmgr_result EQUAL 10 AND __cmgr_round LESS 2)
|
|
# We can retry once if the manager fails with a missing kconfig option
|
|
continue()
|
|
elseif(cmgr_result EQUAL 10)
|
|
idf_die("Missing required kconfig option after retry.")
|
|
else()
|
|
idf_die("IDF Component Manager error: ${cmgr_result}")
|
|
endif()
|
|
endwhile()
|
|
|
|
# All managed components are now fetched and their Kconfig definitions
|
|
# are available. Point __SDKCONFIG_ORIG back to the real sdkconfig so
|
|
# that subsequent operations (menuconfig, save-defconfig, confserver)
|
|
# read and write the actual file, not the preserved copy.
|
|
idf_build_get_property(sdkconfig SDKCONFIG)
|
|
idf_build_set_property(__SDKCONFIG_ORIG "${sdkconfig}")
|
|
idf_build_get_property(sdkconfig_defaults SDKCONFIG_DEFAULTS)
|
|
__create_base_kconfgen_command("${sdkconfig}" "${sdkconfig_defaults}")
|
|
endfunction()
|
|
|
|
#[[
|
|
__download_managed_component(COMPONENTS_LIST_FILE <file>
|
|
MANAGED_OUTPUT_FILE <file>
|
|
RESULT <variable>)
|
|
|
|
*COMPONENTS_LIST_FILE[in]*
|
|
|
|
Path to the local components list file
|
|
|
|
*MANAGED_OUTPUT_FILE[in]*
|
|
|
|
Path where managed components CMake file will be written
|
|
|
|
*RESULT[out]*
|
|
|
|
Exit code returned by the manager. 0 success, 10 re-run.
|
|
|
|
Utility function to run the component manager with a specific components
|
|
list and generate managed components output.
|
|
#]]
|
|
function(__download_managed_component)
|
|
set(options)
|
|
set(one_value COMPONENTS_LIST_FILE MANAGED_OUTPUT_FILE RESULT)
|
|
set(multi_value)
|
|
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
|
|
|
if(NOT DEFINED ARG_COMPONENTS_LIST_FILE)
|
|
idf_die("COMPONENTS_LIST_FILE option is required")
|
|
endif()
|
|
if(NOT DEFINED ARG_MANAGED_OUTPUT_FILE)
|
|
idf_die("MANAGED_OUTPUT_FILE option is required")
|
|
endif()
|
|
if(NOT DEFINED ARG_RESULT)
|
|
idf_die("RESULT option is required")
|
|
endif()
|
|
|
|
idf_build_get_property(python PYTHON)
|
|
idf_build_get_property(project_dir PROJECT_DIR)
|
|
idf_build_get_property(component_manager_interface_version IDF_COMPONENT_MANAGER_INTERFACE_VERSION)
|
|
idf_build_get_property(dependencies_lock_file DEPENDENCIES_LOCK)
|
|
idf_build_get_property(sdkconfig_json __SDKCONFIG_JSON)
|
|
|
|
# Invoke the component manager
|
|
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}"
|
|
"prepare_dependencies"
|
|
"--local_components_list_file=${ARG_COMPONENTS_LIST_FILE}"
|
|
"--managed_components_list_file=${ARG_MANAGED_OUTPUT_FILE}"
|
|
RESULT_VARIABLE result
|
|
ERROR_VARIABLE error)
|
|
|
|
if(NOT result EQUAL 0)
|
|
if(result EQUAL 10)
|
|
idf_warn("Component manager requested a re-run: ${error}")
|
|
else()
|
|
idf_die("Component manager failed: ${error}")
|
|
endif()
|
|
endif()
|
|
|
|
set(${ARG_RESULT} ${result} PARENT_SCOPE)
|
|
endfunction()
|
|
|
|
#[[
|
|
__download_component_level_managed_components(RESULT <variable>)
|
|
|
|
*RESULT[out]*
|
|
|
|
Exit code returned by the manager. 0 success, 10 re-run.
|
|
|
|
Download component-level managed components
|
|
#]]
|
|
function(__download_component_level_managed_components)
|
|
set(options)
|
|
set(one_value RESULT)
|
|
set(multi_value)
|
|
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
|
|
|
if(NOT DEFINED ARG_RESULT)
|
|
idf_die("RESULT option is required")
|
|
endif()
|
|
|
|
# Set up temporary files for the manager
|
|
idf_build_get_property(build_dir BUILD_DIR)
|
|
set(managed_components_list_file ${build_dir}/managed_components_list.temp.cmake)
|
|
set(local_components_list_file ${build_dir}/local_components_list.temp.yml)
|
|
|
|
# Build local components list from discovered components
|
|
set(__contents "components:\n")
|
|
idf_build_get_property(component_interfaces COMPONENT_INTERFACES)
|
|
foreach(interface ${component_interfaces})
|
|
__idf_component_get_property_unchecked(name ${interface} COMPONENT_NAME)
|
|
__idf_component_get_property_unchecked(dir ${interface} COMPONENT_DIR)
|
|
set(__contents "${__contents} - name: \"${name}\"\n path: \"${dir}\"\n")
|
|
endforeach()
|
|
file(WRITE ${local_components_list_file} "${__contents}")
|
|
|
|
# Invoke the component manager
|
|
__download_managed_component(COMPONENTS_LIST_FILE "${local_components_list_file}"
|
|
MANAGED_OUTPUT_FILE "${managed_components_list_file}"
|
|
RESULT result)
|
|
|
|
# Ensure the manager produced the list of managed components
|
|
if(result EQUAL 0 AND NOT EXISTS ${managed_components_list_file})
|
|
idf_die("Managed components list file not produced by the component manager: ${managed_components_list_file}")
|
|
endif()
|
|
|
|
# Initialize managed components by including the generated list
|
|
# Include components even if result is 10 (missing kconfig) to allow kconfig regeneration
|
|
if(result EQUAL 0 OR result EQUAL 10)
|
|
include(${managed_components_list_file})
|
|
else()
|
|
idf_warn("Component manager returned unexpected result: ${result}. Managed components will not be included.")
|
|
endif()
|
|
|
|
# Clean up temporary files
|
|
if(NOT DEFINED ENV{IDF_KEEP_CMANAGER_TEMP} OR NOT "$ENV{IDF_KEEP_CMANAGER_TEMP}" STREQUAL "1")
|
|
file(REMOVE ${managed_components_list_file})
|
|
file(REMOVE ${local_components_list_file})
|
|
endif()
|
|
|
|
set(${ARG_RESULT} ${result} PARENT_SCOPE)
|
|
endfunction()
|
|
|
|
#[[
|
|
__component_manager_warn_if_disabled_and_manifests_exist()
|
|
|
|
When the component manager is disabled, warn if any discovered component
|
|
contains an idf_component.yml manifest.
|
|
#]]
|
|
function(__component_manager_warn_if_disabled_and_manifests_exist)
|
|
idf_build_get_property(idf_component_manager IDF_COMPONENT_MANAGER)
|
|
if(idf_component_manager EQUAL 1)
|
|
return()
|
|
endif()
|
|
idf_build_get_property(with_manifests __COMPONENTS_WITH_MANIFESTS)
|
|
if(with_manifests)
|
|
string(REPLACE ";" "\n\t" with_lines "${with_manifests}")
|
|
idf_warn(NOTICE "\"idf_component.yml\" file was found for components:\n\t
|
|
${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()
|