mirror of
https://github.com/espressif/esp-idf.git
synced 2026-05-28 16:46:31 +03:00
kconfgen runs while the component manager iterates to convergence. Those passes operate on partial component sets and emit "unknown kconfig symbol" warnings for symbols defined in not-yet-downloaded components — idf-build-apps treats those as build failures. Suppress kconfgen output on the intermediate passes; only the final pass against the converged set emits warnings.
389 lines
17 KiB
CMake
389 lines
17 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 5. Allow overriding via env/CMake.
|
|
__get_default_value(VARIABLE IDF_COMPONENT_MANAGER_INTERFACE_VERSION
|
|
DEFAULT 5
|
|
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)
|
|
# __KCONFGEN_QUIET stays YES (set by the bootstrap path in project.cmake)
|
|
# across intermediate iterations of the loop below, because each
|
|
# iteration's kconfgen pass runs against a partial component set when the
|
|
# component manager exits 10. Warnings from those passes are transient.
|
|
# We only flip the flag to NO on the iteration where the component
|
|
# manager succeeds, so the final kconfgen pass against the complete
|
|
# component set emits genuine warnings.
|
|
|
|
# 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)
|
|
|
|
# Only the final (successful) iteration should emit kconfgen warnings.
|
|
if(cmgr_result EQUAL 0)
|
|
idf_build_set_property(__KCONFGEN_QUIET NO)
|
|
endif()
|
|
|
|
# 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)
|
|
# Invoke the component manager
|
|
execute_process(COMMAND ${python}
|
|
"-m"
|
|
"idf_component_manager.prepare_components"
|
|
"--project_dir=${project_dir}"
|
|
"--lock_path=${dependencies_lock_file}"
|
|
"--interface_version=${component_manager_interface_version}"
|
|
"--use_sdk_json=true"
|
|
"prepare_dependencies"
|
|
"--local_components_list_file=${ARG_COMPONENTS_LIST_FILE}"
|
|
"--build_dir=${build_dir}"
|
|
"--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)
|
|
__idf_component_get_property_unchecked(source ${interface} COMPONENT_SOURCE)
|
|
set(__contents "${__contents} - name: \"${name}\"\n path: \"${dir}\"\n source: \"${source}\"\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(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_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 seeds the component manager with one entry per
|
|
# discovered component, each carrying its __COMPONENT_SOURCE. This is a
|
|
# minimal build system v1-style file consumed by the manager via the
|
|
# shim __component_set_property(), which calls idf_component_set_property().
|
|
# Seeding the whole project ensures handle_project_requirements()'s
|
|
# known_components matches the project-wide list v1 provides, so its
|
|
# _choose_component() can rewrite a manifest-declared namespaced dep
|
|
# (e.g. "lvgl__lvgl") to its locally-shadowing short name ("lvgl") when
|
|
# a local component shadows a managed dependency. Without seeding, the
|
|
# manager would only see the one component being processed and the
|
|
# rewrite would never fire. The manager then modifies this file by
|
|
# appending the component's resolved 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.
|
|
# Iterate COMPONENT_INTERFACES (not COMPONENTS_DISCOVERED) and read each
|
|
# component's properties via __idf_component_get_property_unchecked so the
|
|
# COMPONENT_SOURCE comes from the component's own interface target rather
|
|
# than the alias-aware lookup. After __init_component_interface_cache
|
|
# short-name aliasing fires (e.g. a higher-priority "example__cmp" rebinds
|
|
# the cache entry "cmp" to its own interface), the name-based getter
|
|
# returns the alias target's source -- in that case both "cmp" and
|
|
# "example__cmp" would be seeded as "project_managed_components" and the
|
|
# manager's _override_requirements_by_component_sources would reject the
|
|
# duplicate.
|
|
set(out_file "${build_dir}/component_requires.${component_name}.temp.cmake")
|
|
idf_build_get_property(component_interfaces COMPONENT_INTERFACES)
|
|
set(requires_content "")
|
|
foreach(seed_interface IN LISTS component_interfaces)
|
|
__idf_component_get_property_unchecked(seed_name "${seed_interface}" COMPONENT_NAME)
|
|
__idf_component_get_property_unchecked(seed_source "${seed_interface}" COMPONENT_SOURCE)
|
|
string(APPEND requires_content
|
|
"__component_set_property(___${component_prefix}_${seed_name} __COMPONENT_SOURCE \"${seed_source}\")\n")
|
|
endforeach()
|
|
file(WRITE "${out_file}" "${requires_content}")
|
|
|
|
# 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}"
|
|
"--interface_version=${component_manager_interface_version}"
|
|
"--use_sdk_json=true"
|
|
"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()
|