mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
feat(cmakev2): add basic component initialization
Discover component directories and initialize components within them. This process does not include managed components, which should be added separately at a later stage. To facilitate this, some minimal functionalities are introduced, such as build properties, component properties, and other helper functions. Signed-off-by: Frantisek Hrbata <frantisek.hrbata@espressif.com>
This commit is contained in:
85
tools/cmakev2/build.cmake
Normal file
85
tools/cmakev2/build.cmake
Normal file
@@ -0,0 +1,85 @@
|
||||
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
include(utilities)
|
||||
|
||||
#[[api
|
||||
.. cmakev2:function:: idf_build_set_property
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
idf_build_set_property(<property> <value> [APPEND])
|
||||
|
||||
:property[in]: Property name.
|
||||
:value[in]: Property value.
|
||||
:APPEND: Append the value to the property's current value instead of
|
||||
replacing it.
|
||||
|
||||
Set the value of the specified property related to the ESP-IDF build. The
|
||||
property is also added to the internal list of build properties if it isn't
|
||||
already there.
|
||||
#]]
|
||||
function(idf_build_set_property property value)
|
||||
set(options APPEND)
|
||||
set(one_value)
|
||||
set(multi_value)
|
||||
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
||||
|
||||
get_property(properties TARGET idf_build_properties PROPERTY BUILD_PROPERTIES)
|
||||
if(NOT property IN_LIST properties)
|
||||
list(APPEND properties "${property}")
|
||||
set_property(TARGET idf_build_properties PROPERTY BUILD_PROPERTIES "${properties}")
|
||||
endif()
|
||||
if(ARG_APPEND)
|
||||
set_property(TARGET idf_build_properties APPEND PROPERTY "${property}" "${value}")
|
||||
else()
|
||||
set_property(TARGET idf_build_properties PROPERTY "${property}" "${value}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
#[[api
|
||||
.. cmakev2:function:: idf_build_get_property
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
idf_build_get_property(<var> <property> [GENERATOR_EXPRESSION])
|
||||
|
||||
:variable[out]: Variable to store the value in.
|
||||
:property[in]: Property name to get the value of.
|
||||
:GENERATOR_EXPRESSION: Obtain the generator expression for the property
|
||||
rather than the actual value.
|
||||
|
||||
Get the value of the specified property related to the ESP-IDF build.
|
||||
#]]
|
||||
function(idf_build_get_property variable property)
|
||||
set(options GENERATOR_EXPRESSION)
|
||||
set(one_value)
|
||||
set(multi_value)
|
||||
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
||||
|
||||
if("${property}" STREQUAL BUILD_COMPONENTS)
|
||||
idf_die("Build property 'BUILD_COMPONENTS' is not supported")
|
||||
endif()
|
||||
if(ARG_GENERATOR_EXPRESSION)
|
||||
set(value "$<TARGET_PROPERTY:idf_build_properties,${property}>")
|
||||
else()
|
||||
get_property(value TARGET idf_build_properties PROPERTY ${property})
|
||||
endif()
|
||||
set(${variable} ${value} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
#[[
|
||||
__dump_build_properties()
|
||||
|
||||
Dump all build properties.
|
||||
#]]
|
||||
function(__dump_build_properties)
|
||||
idf_build_get_property(properties BUILD_PROPERTIES)
|
||||
idf_msg("build properties: ${properties}")
|
||||
foreach(property IN LISTS properties)
|
||||
idf_build_get_property(value ${property})
|
||||
idf_msg(" ${property}: ${value}")
|
||||
endforeach()
|
||||
endfunction()
|
||||
432
tools/cmakev2/component.cmake
Normal file
432
tools/cmakev2/component.cmake
Normal file
@@ -0,0 +1,432 @@
|
||||
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
include(utilities)
|
||||
include(build)
|
||||
|
||||
#[[api
|
||||
.. cmakev2:function:: idf_component_set_property
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
idf_component_set_property(<property> <value> [APPEND])
|
||||
|
||||
:property[in]: Property name.
|
||||
:value[in]: Property value.
|
||||
:APPEND: Append the value to the property's current value instead of
|
||||
replacing it.
|
||||
|
||||
Set the value of the specified component property. The property is also
|
||||
added to the internal list of component properties if it isn't already
|
||||
there.
|
||||
#]]
|
||||
function(idf_component_set_property component property value)
|
||||
set(options APPEND)
|
||||
set(one_value)
|
||||
set(multi_value)
|
||||
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
||||
|
||||
__get_component_interface_or_die(COMPONENT "${component}" OUTPUT component_interface)
|
||||
get_property(properties TARGET ${component_interface} PROPERTY COMPONENT_PROPERTIES)
|
||||
if(NOT property IN_LIST properties)
|
||||
list(APPEND properties "${property}")
|
||||
set_property(TARGET ${component_interface} PROPERTY COMPONENT_PROPERTIES "${properties}")
|
||||
endif()
|
||||
if(ARG_APPEND)
|
||||
set_property(TARGET ${component_interface} APPEND PROPERTY ${property} "${value}")
|
||||
else()
|
||||
set_property(TARGET ${component_interface} PROPERTY ${property} "${value}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
#[[api
|
||||
.. cmakev2:function:: idf_component_get_property
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
idf_component_get_property(<variable> <property> [GENERATOR_EXPRESSION])
|
||||
|
||||
:variable[out]: Variable to store the value in.
|
||||
:property[in]: Property name to get the value of.
|
||||
:GENERATOR_EXPRESSION: Obtain the generator expression for the property
|
||||
rather than the actual value.
|
||||
|
||||
Retrieve the value of the specified component property.
|
||||
#]]
|
||||
function(idf_component_get_property variable component property)
|
||||
set(options GENERATOR_EXPRESSION)
|
||||
set(one_value)
|
||||
set(multi_value)
|
||||
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
||||
|
||||
__get_component_interface_or_die(COMPONENT "${component}" OUTPUT component_interface)
|
||||
if(ARG_GENERATOR_EXPRESSION)
|
||||
set(value "$<TARGET_PROPERTY:${component_interface},${property}>")
|
||||
else()
|
||||
get_property(value TARGET ${component_interface} PROPERTY ${property})
|
||||
endif()
|
||||
set(${variable} ${value} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
#[[
|
||||
__get_component_paths(PATHS <path>...
|
||||
[EXCLUDE_PATHS <path>...]
|
||||
[SOURCE <source>]
|
||||
[CHECK]
|
||||
OUTPUT <var>)
|
||||
|
||||
:PATHS[in]: List of paths to search for component directories.
|
||||
:EXCLUDE_PATHS[in,opt]: Optional list of paths to exclude from the search of
|
||||
component directories.
|
||||
:SOURCE[in,opt]: Source of the ``PATHS``. If provided, it will be included
|
||||
in the error message when ``CHECK`` is specified.
|
||||
:CHECK[in,opt]: Verify whether the paths listed in "PATHS" exist. If any
|
||||
path is missing, abort the build process.
|
||||
:OUTPUT[out]: Output variable to store the list of found component
|
||||
directories.
|
||||
|
||||
Search for component directories in the specified ``PATHS``, excluding those
|
||||
in ``EXCLUDE_PATHS``, and store the list of absolute component paths in the
|
||||
``OUTPUT`` variable. If ``CHECK`` is specified, ensure that the paths listed
|
||||
in ``PATHS`` exist, and stop the build process if they do not.
|
||||
#]]
|
||||
function(__get_component_paths)
|
||||
set(options CHECK)
|
||||
set(one_value SOURCE OUTPUT)
|
||||
set(multi_value PATHS EXCLUDE_PATHS)
|
||||
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
||||
|
||||
if(NOT DEFINED ARG_PATHS)
|
||||
idf_die("PATHS option is required")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED ARG_OUTPUT)
|
||||
idf_die("OUTPUT option is required")
|
||||
endif()
|
||||
|
||||
set(include_paths "")
|
||||
set(exclude_paths "")
|
||||
__get_absolute_paths(PATHS "${ARG_PATHS}" OUTPUT include_paths)
|
||||
if(DEFINED ARG_EXCLUDE_PATHS)
|
||||
__get_absolute_paths(PATHS "${ARG_EXCLUDE_PATHS}" OUTPUT exclude_paths)
|
||||
endif()
|
||||
|
||||
if(ARG_CHECK)
|
||||
foreach(path IN LISTS include_paths)
|
||||
if(NOT IS_DIRECTORY "${path}")
|
||||
if(DEFINED ARG_SOURCE)
|
||||
idf_die("Directory specified in '${ARG_SOURCE}' doesn't exist: '${path}'")
|
||||
else()
|
||||
idf_die("Directory doesn't exist: '${path}'")
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
set(paths "${include_paths}")
|
||||
set(component_paths "")
|
||||
|
||||
while(paths)
|
||||
list(POP_FRONT paths path)
|
||||
|
||||
if(NOT IS_DIRECTORY "${path}" OR "${path}" IN_LIST exclude_paths)
|
||||
continue()
|
||||
endif()
|
||||
|
||||
if(EXISTS "${path}/CMakeLists.txt")
|
||||
list(APPEND component_paths "${path}")
|
||||
elseif("${path}" IN_LIST include_paths)
|
||||
file(GLOB dirs "${path}/*")
|
||||
__get_absolute_paths(PATHS "${dirs}" OUTPUT dirs_abs)
|
||||
list(APPEND paths "${dirs_abs}")
|
||||
endif()
|
||||
|
||||
endwhile()
|
||||
|
||||
set(${ARG_OUTPUT} "${component_paths}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
#[[
|
||||
__get_component_interface(COMPONENT <component>
|
||||
OUTPUT <variable>)
|
||||
|
||||
:COMPONENT[int]: Component name, target, target alias or interface.
|
||||
:OUTPUT[out]: 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.
|
||||
|
||||
component interface: <component_prefix>_<component name>
|
||||
component target: _<component_prefix>_<component name>
|
||||
component alias: <component_prefix>::<component name>
|
||||
#]]
|
||||
function(__get_component_interface)
|
||||
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()
|
||||
|
||||
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)
|
||||
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}")
|
||||
else()
|
||||
# The component name might be an alias, so retrieve the actual target
|
||||
# name.
|
||||
__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}")
|
||||
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()
|
||||
|
||||
#[[
|
||||
__get_component_interface_or_die(COMPONENT <component>
|
||||
OUTPUT <variable>)
|
||||
|
||||
:COMPONENT[int]: Component name, target, target alias or interface.
|
||||
:OUTPUT[out]: Output variable to store the component interface.
|
||||
|
||||
A simple wrapper for ``__get_component_interface`` that aborts the build
|
||||
process if the component interface is not found.
|
||||
#]]
|
||||
function(__get_component_interface_or_die)
|
||||
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()
|
||||
|
||||
__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")
|
||||
endif()
|
||||
set(${ARG_OUTPUT} ${component_interface} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
#[[
|
||||
__get_component_priority(SOURCE <source>
|
||||
OUTPUT <variable>)
|
||||
|
||||
:SOURCE[in]: String identifying the component source.
|
||||
:OUTPUT[out]: Output variable to store the component priority.
|
||||
|
||||
Return the priority number of a component, where a higher number indicates a
|
||||
higher priority, based on the given ``source`` string. If the ``source`` is
|
||||
not valid, return ``NOTFOUND``.
|
||||
#]]
|
||||
function(__get_component_priority)
|
||||
set(options)
|
||||
set(one_value SOURCE OUTPUT)
|
||||
set(multi_value)
|
||||
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
||||
|
||||
if(NOT DEFINED ARG_SOURCE)
|
||||
idf_die("SOURCE option is required")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED ARG_OUTPUT)
|
||||
idf_die("OUTPUT option is required")
|
||||
endif()
|
||||
|
||||
if("${ARG_SOURCE}" STREQUAL "project_components")
|
||||
set(priority 3)
|
||||
elseif("${ARG_SOURCE}" STREQUAL "project_extra_components")
|
||||
set(priority 2)
|
||||
elseif("${ARG_SOURCE}" STREQUAL "project_managed_components")
|
||||
set(priority 1)
|
||||
elseif("${ARG_SOURCE}" STREQUAL "idf_components")
|
||||
set(priority 0)
|
||||
else()
|
||||
set(priority NOTFOUND)
|
||||
endif()
|
||||
|
||||
set(${ARG_OUTPUT} "${priority}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
#[[
|
||||
__init_component(DIRECTORY <path>
|
||||
PREFIX <prefix>
|
||||
SOURCE <source>)
|
||||
|
||||
:DIRECTORY[in]: ``<path>`` where the component is located.
|
||||
:PREFIX[in]: Prefix for component target names.
|
||||
:SOURCE[in]: String identifying the component source.
|
||||
|
||||
Initialize the component by creating a component interface target, allowing
|
||||
properties to be attached to it and also add initial component properties.
|
||||
At this stage, the component is not included in the build. The actual
|
||||
component target, named as specified in the `COMPONENT_LIB` property, is
|
||||
created later when ``add_subdirectory`` is called for the component's
|
||||
directory based on the project or other component requirements.
|
||||
|
||||
Components are identified by the directory name they reside in. This means
|
||||
that components with the same name might exist in different directory paths.
|
||||
In such cases, the component with the higher priority is used. Priority is
|
||||
determined by the component's source, as defined in
|
||||
``__get_component_priority``. If a component with a higher priority than an
|
||||
existing one is initialized, its name, targets, and other properties remain
|
||||
the same. Only the directory, priority, and source are updated in the
|
||||
already initialized component.
|
||||
|
||||
The name of the initialized component is added to the
|
||||
``COMPONENTS_DISCOVERED`` build property, and its interface target is added
|
||||
to the ``COMPONENT_INTERFACES`` build property. These two lists are used by
|
||||
``__get_component_interface``, which searches for a component interface
|
||||
based on the component name, alias, or targets, enabling the setting and
|
||||
retrieval of component properties.
|
||||
#]]
|
||||
function(__init_component)
|
||||
set(options)
|
||||
set(one_value DIRECTORY PREFIX SOURCE)
|
||||
set(multi_value)
|
||||
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
||||
|
||||
if(NOT DEFINED ARG_DIRECTORY)
|
||||
idf_die("DIRECTORY option is required")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED ARG_SOURCE)
|
||||
idf_die("SOURCE option is required")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED ARG_PREFIX)
|
||||
idf_die("PREFIX option is required")
|
||||
endif()
|
||||
|
||||
set(component_source "${ARG_SOURCE}")
|
||||
set(component_prefix "${ARG_PREFIX}")
|
||||
get_filename_component(component_directory ${ARG_DIRECTORY} ABSOLUTE)
|
||||
get_filename_component(component_name ${component_directory} NAME)
|
||||
__get_component_priority(SOURCE "${component_source}" OUTPUT component_priority)
|
||||
|
||||
if(NOT EXISTS "${component_directory}/CMakeLists.txt")
|
||||
idf_die("Directory '${component_directory}' does not contain a component")
|
||||
endif()
|
||||
|
||||
if("${component_priority}" STREQUAL "NOTFOUND")
|
||||
idf_die("Unknown component source '${component_source}' "
|
||||
"for directory '${component_directory}'")
|
||||
endif()
|
||||
|
||||
__get_component_interface(COMPONENT "${component_name}" OUTPUT existing_component_interface)
|
||||
if(NOT "${existing_component_interface}" STREQUAL "NOTFOUND")
|
||||
# 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
|
||||
"${existing_component_interface}"
|
||||
COMPONENT_PRIORITY)
|
||||
idf_component_get_property(existing_component_directory
|
||||
"${existing_component_interface}"
|
||||
COMPONENT_DIR)
|
||||
idf_component_get_property(existing_component_source
|
||||
"${existing_component_interface}"
|
||||
COMPONENT_SOURCE)
|
||||
|
||||
if(${component_priority} EQUAL ${existing_component_priority})
|
||||
idf_die("Component directory '${component_directory}' has the same "
|
||||
"priority '${component_source}' as component directory "
|
||||
"'${existing_component_directory}'")
|
||||
|
||||
elseif(${component_priority} LESS ${existing_component_priority})
|
||||
idf_warn("Component directory '${component_directory}' has lower "
|
||||
"priority '${component_source}' than component directory "
|
||||
"'${existing_component_directory}' with priority "
|
||||
"'${existing_component_source}' and will be ignored")
|
||||
else()
|
||||
idf_warn("Component '${component_name}' directory '${component_directory}' "
|
||||
"with higher priority '${component_source}' will be used instead of "
|
||||
"component directory '${existing_component_directory}' "
|
||||
"with lower priority '${existing_component_source}'")
|
||||
# The newly added component has a higher priority than the existing
|
||||
# one. Since the component name and targets are identical, update
|
||||
# the existing component with the new directory and priority.
|
||||
idf_component_set_property("${component_name}" COMPONENT_DIR "${component_directory}")
|
||||
idf_component_set_property("${component_name}" COMPONENT_SOURCE "${component_source}")
|
||||
idf_component_set_property("${component_name}" COMPONENT_PRIORITY ${component_priority})
|
||||
endif()
|
||||
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(component_interface "${component_prefix}_${component_name}")
|
||||
set(component_alias "${component_prefix}::${component_name}")
|
||||
# Real component library target that needs to be created by the component.
|
||||
set(component_target "_${component_interface}")
|
||||
|
||||
# Interface target is used to attach all component properties and is also
|
||||
# used when the component is linked to other targets.
|
||||
add_library("${component_interface}" INTERFACE)
|
||||
add_library("${component_alias}" ALIAS "${component_interface}")
|
||||
|
||||
idf_build_set_property(COMPONENTS_DISCOVERED "${component_name}" APPEND)
|
||||
idf_build_set_property(COMPONENT_INTERFACES "${component_interface}" APPEND)
|
||||
|
||||
idf_component_set_property("${component_name}" COMPONENT_LIB "${component_target}")
|
||||
idf_component_set_property("${component_name}" COMPONENT_NAME "${component_name}")
|
||||
idf_component_set_property("${component_name}" COMPONENT_DIR "${component_directory}")
|
||||
idf_component_set_property("${component_name}" COMPONENT_ALIAS "${component_alias}")
|
||||
idf_component_set_property("${component_name}" COMPONENT_SOURCE "${component_source}")
|
||||
idf_component_set_property("${component_name}" COMPONENT_INTERFACE "${component_interface}")
|
||||
idf_component_set_property("${component_name}" COMPONENT_PRIORITY ${component_priority})
|
||||
endfunction()
|
||||
|
||||
#[[
|
||||
__dump_component_properties(<components>)
|
||||
|
||||
:components: List of components whose properties should be displayed.
|
||||
|
||||
Dump all properties for the components listed in ``<components>``.
|
||||
#]]
|
||||
function(__dump_component_properties components)
|
||||
foreach(component IN LISTS components)
|
||||
idf_component_get_property(properties "${component}" COMPONENT_PROPERTIES)
|
||||
idf_msg("component '${component}' properties: ${properties}")
|
||||
foreach(property IN LISTS properties)
|
||||
idf_component_get_property(value "${component}" "${property}")
|
||||
idf_msg(" ${property}: ${value}")
|
||||
endforeach()
|
||||
endforeach()
|
||||
endfunction()
|
||||
171
tools/cmakev2/idf.cmake
Normal file
171
tools/cmakev2/idf.cmake
Normal file
@@ -0,0 +1,171 @@
|
||||
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
# Update CMAKE_MODULE_PATH to ensure that other build system modules can be
|
||||
# included.
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}" ${CMAKE_MODULE_PATH})
|
||||
|
||||
include(component)
|
||||
include(build)
|
||||
|
||||
#[[
|
||||
__init_idf_path()
|
||||
|
||||
Determine the IDF_PATH value, either from the IDF_PATH environmental
|
||||
variable or based on the location of this file. Also check there is no
|
||||
inconsistency between the two.
|
||||
|
||||
Set the IDF_PATH global variable, environment variable and build property.
|
||||
#]]
|
||||
function(__init_idf_path)
|
||||
get_filename_component(idf_path_infer "${CMAKE_CURRENT_LIST_DIR}/../.." REALPATH)
|
||||
|
||||
if(NOT DEFINED ENV{IDF_PATH})
|
||||
idf_warn("IDF_PATH environment variable not found. "
|
||||
"Setting IDF_PATH to '${idf_path_infer}'.")
|
||||
set(idf_path "${idf_path_infer}")
|
||||
else()
|
||||
get_filename_component(idf_path_env "$ENV{IDF_PATH}" REALPATH)
|
||||
if(NOT "${idf_path_env}" STREQUAL "${idf_path_infer}")
|
||||
idf_warn("IDF_PATH environment variable is different from inferred IDF_PATH. "
|
||||
"Check if your project's top-level CMakeLists.txt includes the right "
|
||||
"CMake files. Environment IDF_PATH will be used for the build: "
|
||||
"'${idf_path_env}'")
|
||||
endif()
|
||||
set(idf_path "${idf_path_env}")
|
||||
endif()
|
||||
|
||||
idf_build_set_property(IDF_PATH "${idf_path}")
|
||||
set(IDF_PATH ${idf_path} PARENT_SCOPE)
|
||||
set(ENV{IDF_PATH} ${idf_path})
|
||||
endfunction()
|
||||
|
||||
#[[
|
||||
__init_components()
|
||||
|
||||
Search for possible component directories categorized by their source, which
|
||||
could be ``idf_components``, ``project_extra_components``, or
|
||||
``project_components``. Components added by the component manager are
|
||||
initialized later as ``project_managed_components`` after the component
|
||||
manager is called.
|
||||
|
||||
The search respects the variables set by the user e.g. in the project's
|
||||
CMakeLists.txt file. These are maintained for backward compatibility.
|
||||
|
||||
:COMPONENT_DIRS: If set, component directories are searched exclusively in
|
||||
the paths provided in ``COMPONENT_DIRS``.
|
||||
:EXTRA_COMPONENT_DIRS: Includes extra paths to search if `COMPONENT_DIRS` is not specified.
|
||||
:EXTRA_COMPONENT_EXCLUDE_DIRS: List of paths to exclude from searching the
|
||||
component directories.
|
||||
|
||||
Each component is initialized for every component directory found.
|
||||
#]]
|
||||
function(__init_components)
|
||||
idf_build_get_property(idf_path IDF_PATH)
|
||||
idf_build_get_property(prefix PREFIX)
|
||||
|
||||
__get_component_paths(PATHS "${idf_path}/components"
|
||||
OUTPUT idf_components)
|
||||
if(COMPONENT_DIRS)
|
||||
# The user explicitly stated the locations to search for components.
|
||||
# For backward compatibility, check that the paths in
|
||||
# COMPONENT_DIRS exist.
|
||||
__get_component_paths(PATHS ${COMPONENT_DIRS}
|
||||
EXCLUDE_PATHS ${EXTRA_COMPONENT_EXCLUDE_DIRS}
|
||||
SOURCE "COMPONENT_DIRS"
|
||||
CHECK
|
||||
OUTPUT project_components)
|
||||
else()
|
||||
__get_component_paths(PATHS "${CMAKE_CURRENT_SOURCE_DIR}/main"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/components"
|
||||
EXCLUDE_PATHS ${EXTRA_COMPONENT_EXCLUDE_DIRS}
|
||||
OUTPUT project_components)
|
||||
if(EXTRA_COMPONENT_DIRS)
|
||||
# For backward compatibility, check that the paths in
|
||||
# EXTRA_COMPONENT_DIRS exist.
|
||||
__get_component_paths(PATHS ${EXTRA_COMPONENT_DIRS}
|
||||
EXCLUDE_PATHS ${EXTRA_COMPONENT_EXCLUDE_DIRS}
|
||||
SOURCE "EXTRA_COMPONENT_DIRS"
|
||||
CHECK
|
||||
OUTPUT project_extra_components)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
foreach(path IN LISTS idf_components)
|
||||
__init_component(DIRECTORY "${path}"
|
||||
PREFIX "${prefix}"
|
||||
SOURCE "idf_components")
|
||||
endforeach()
|
||||
|
||||
foreach(path IN LISTS project_components)
|
||||
__init_component(DIRECTORY "${path}"
|
||||
PREFIX "${prefix}"
|
||||
SOURCE "project_components")
|
||||
endforeach()
|
||||
|
||||
foreach(path IN LISTS project_extra_components)
|
||||
__init_component(DIRECTORY "${path}"
|
||||
PREFIX "${prefix}"
|
||||
SOURCE "project_extra_components")
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
#[[
|
||||
The idf_build_properties interface target is exclusively used to store
|
||||
information about global build properties and is not linked or used in any
|
||||
other way. This is created very early so that all the initialization
|
||||
functions can use it.
|
||||
|
||||
List of build properties
|
||||
|
||||
:IDF_PATH: Path to esp-idf directory.
|
||||
:PREFIX: Prefix used for component target names.
|
||||
:COMPONENTS_DISCOVERED: List of component names identified by the build
|
||||
system. These components are initialized and can
|
||||
have properties attached to them. However, they
|
||||
are not necessarily included in the build through
|
||||
add_subdirectory.
|
||||
:COMPONENT_INTERFACES: This is a list of component interface targets for
|
||||
the components in ``COMPONENTS_DISCOVERED``. It is
|
||||
used when searching for a component, such as by its
|
||||
name, to set or retrieve the component's properties.
|
||||
|
||||
#]]
|
||||
add_library(idf_build_properties INTERFACE)
|
||||
|
||||
# Set build system prefix for component targets.
|
||||
idf_build_set_property(PREFIX "idf")
|
||||
|
||||
# Initialize IDF_PATH and set it as a global and environmental variable, as
|
||||
# well as a build property.
|
||||
__init_idf_path()
|
||||
|
||||
# Discover and initialize components.
|
||||
__init_components()
|
||||
|
||||
#[[ TODO
|
||||
|
||||
Many of the following things are already implemented in PoC !38337, but they
|
||||
need to be reviewed.
|
||||
|
||||
* Set IDF version.
|
||||
* Set and check python interpreter.
|
||||
* Set build target.
|
||||
* Set the toolchain before invoking project().
|
||||
* Enable ccache if requested and available.
|
||||
* Generate initial sdkconfig for component manager.
|
||||
* Call component manager with initial sdkconfig to download requested components
|
||||
and include the generated cmake. See the generated
|
||||
``build/managed_components_list.temp.cmake``. We probably need shim for
|
||||
``idf_build_component`` calling ``__init_component``.
|
||||
* Generate final sdkconfig and include its cmake version.
|
||||
|
||||
There may be additional steps. We should initialize everything necessary before
|
||||
calling the ``project()`` function, as well as any global settings that cannot be
|
||||
modified later.
|
||||
|
||||
#]]
|
||||
165
tools/cmakev2/utilities.cmake
Normal file
165
tools/cmakev2/utilities.cmake
Normal file
@@ -0,0 +1,165 @@
|
||||
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# Note: CMake does not support nested lists. The functions idf_die, idf_warn,
|
||||
# idf_msg, and idf_dbg use ARGV# values because this is the only way to prevent
|
||||
# arguments from being altered by CMake. ARGV and ARGN contain a flattened list
|
||||
# of arguments, making it impossible to determine if any argument was
|
||||
# originally a list.
|
||||
#
|
||||
# Using these functions has a side effect: the actual origin of the message
|
||||
# appears as the first line of the backtrace.
|
||||
|
||||
#[[api
|
||||
.. cmakev2:function:: idf_die
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
idf_die(<msg>...)
|
||||
|
||||
:msg[in]: Message to print.
|
||||
|
||||
Print error ``<msg>`` and abort the build process. Multiple messages are
|
||||
concatenated into a single message with no separator between them.
|
||||
|
||||
#]]
|
||||
function(idf_die)
|
||||
set(joined "")
|
||||
math(EXPR last "${ARGC} - 1")
|
||||
foreach(i RANGE 0 ${last})
|
||||
string(APPEND joined "${ARGV${i}}")
|
||||
endforeach()
|
||||
message(FATAL_ERROR " IDF: ${joined}")
|
||||
endfunction()
|
||||
|
||||
#[[api
|
||||
.. cmakev2:function:: idf_warn
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
idf_warn(<msg>...)
|
||||
|
||||
:msg[in]: Message to print.
|
||||
|
||||
Print warning ``<msg>``. Multiple messages are concatenated into a single
|
||||
message with no separator between them.
|
||||
#]]
|
||||
function(idf_warn)
|
||||
set(joined "")
|
||||
math(EXPR last "${ARGC} - 1")
|
||||
foreach(i RANGE 0 ${last})
|
||||
string(APPEND joined "${ARGV${i}}")
|
||||
endforeach()
|
||||
message(WARNING " IDF: ${joined}")
|
||||
endfunction()
|
||||
|
||||
#[[api
|
||||
.. cmakev2:function:: idf_msg
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
idf_msg(<msg>...)
|
||||
|
||||
:msg[in]: Message to print.
|
||||
|
||||
Print status ``<msg>``. Multiple messages are concatenated into a single
|
||||
message with no separator between them.
|
||||
#]]
|
||||
function(idf_msg)
|
||||
set(joined "")
|
||||
math(EXPR last "${ARGC} - 1")
|
||||
foreach(i RANGE 0 ${last})
|
||||
string(APPEND joined "${ARGV${i}}")
|
||||
endforeach()
|
||||
message(STATUS " IDF: ${joined}")
|
||||
endfunction()
|
||||
|
||||
#[[api
|
||||
.. cmakev2:function:: idf_dbg
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
idf_dbg(<msg>...)
|
||||
|
||||
:msg[in]: Message to print.
|
||||
|
||||
Print debug ``<msg>``. Multiple messages are concatenated into a single
|
||||
message with no separator between them.
|
||||
#]]
|
||||
function(idf_dbg)
|
||||
set(joined "")
|
||||
math(EXPR last "${ARGC} - 1")
|
||||
foreach(i RANGE 0 ${last})
|
||||
string(APPEND joined "${ARGV${i}}")
|
||||
endforeach()
|
||||
message(DEBUG " IDF: ${joined}")
|
||||
endfunction()
|
||||
|
||||
#[[
|
||||
__get_real_target(TARGET <target>
|
||||
OUTPUT <variable>)
|
||||
|
||||
:TARGET[int]: Target name or target alias name.
|
||||
:OUTPUT[out]: Output variable to store the real target name.
|
||||
|
||||
For a given ``<target>``, return the actual target if ``<target>`` is an
|
||||
alias. If ``<target>`` is the target itself, return it. If ``<target>`` is
|
||||
not a target at all, return ``NOTFOUND``.
|
||||
#]]
|
||||
function(__get_real_target)
|
||||
set(options)
|
||||
set(one_value TARGET OUTPUT)
|
||||
set(multi_value)
|
||||
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
||||
|
||||
if(NOT DEFINED ARG_TARGET)
|
||||
idf_die("TARGET option is required")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED ARG_OUTPUT)
|
||||
idf_die("OUTPUT option is required")
|
||||
endif()
|
||||
|
||||
set(real_target NOTFOUND)
|
||||
if(TARGET "${ARG_TARGET}")
|
||||
get_target_property(aliased ${ARG_TARGET} ALIASED_TARGET)
|
||||
if(aliased)
|
||||
set(real_target ${aliased})
|
||||
else()
|
||||
set(real_target ${ARG_TARGET})
|
||||
endif()
|
||||
endif()
|
||||
set(${ARG_OUTPUT} "${real_target}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
#[[
|
||||
__get_absolute_paths(PATHS <path>...
|
||||
OUTPUT <variable>)
|
||||
|
||||
:PATHS[in]: List of paths to convert to absolute paths.
|
||||
:OUTPUT[out]: Output variable to store absolute paths.
|
||||
|
||||
For a given ``PATHS``, return the absolute paths in ``OUTPUT``.
|
||||
#]]
|
||||
function(__get_absolute_paths)
|
||||
set(options)
|
||||
set(one_value OUTPUT)
|
||||
set(multi_value PATHS)
|
||||
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
||||
|
||||
if(NOT DEFINED ARG_PATHS)
|
||||
idf_die("PATHS option is required")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED ARG_OUTPUT)
|
||||
idf_die("OUTPUT option is required")
|
||||
endif()
|
||||
|
||||
set(absolute_paths "")
|
||||
foreach(path IN LISTS ARG_PATHS)
|
||||
get_filename_component(path_abs ${path} ABSOLUTE)
|
||||
list(APPEND absolute_paths "${path_abs}")
|
||||
endforeach()
|
||||
|
||||
set(${ARG_OUTPUT} "${absolute_paths}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
Reference in New Issue
Block a user