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:
Frantisek Hrbata
2025-06-26 17:33:13 +02:00
parent 811e27118d
commit d6bc39ebbd
4 changed files with 853 additions and 0 deletions

85
tools/cmakev2/build.cmake Normal file
View 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()

View 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
View 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.
#]]

View 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()