diff --git a/tools/cmakev2/build.cmake b/tools/cmakev2/build.cmake new file mode 100644 index 00000000000..9839027a4c1 --- /dev/null +++ b/tools/cmakev2/build.cmake @@ -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( [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( [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 "$") + 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() diff --git a/tools/cmakev2/component.cmake b/tools/cmakev2/component.cmake new file mode 100644 index 00000000000..3136ef2d394 --- /dev/null +++ b/tools/cmakev2/component.cmake @@ -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( [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( [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 "$") + else() + get_property(value TARGET ${component_interface} PROPERTY ${property}) + endif() + set(${variable} ${value} PARENT_SCOPE) +endfunction() + +#[[ + __get_component_paths(PATHS ... + [EXCLUDE_PATHS ...] + [SOURCE ] + [CHECK] + OUTPUT ) + + :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 + OUTPUT ) + + :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 ```` 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 target: __ + component alias: :: +#]] +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 + OUTPUT ) + + :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 + OUTPUT ) + + :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 + PREFIX + SOURCE ) + + :DIRECTORY[in]: ```` 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: List of components whose properties should be displayed. + + Dump all properties for the components listed in ````. +#]] +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() diff --git a/tools/cmakev2/idf.cmake b/tools/cmakev2/idf.cmake new file mode 100644 index 00000000000..2c1b10588b7 --- /dev/null +++ b/tools/cmakev2/idf.cmake @@ -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. + +#]] diff --git a/tools/cmakev2/utilities.cmake b/tools/cmakev2/utilities.cmake new file mode 100644 index 00000000000..dd678924844 --- /dev/null +++ b/tools/cmakev2/utilities.cmake @@ -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[in]: Message to print. + + Print error ```` 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[in]: Message to print. + + Print warning ````. 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[in]: Message to print. + + Print status ````. 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[in]: Message to print. + + Print debug ````. 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 + OUTPUT ) + + :TARGET[int]: Target name or target alias name. + :OUTPUT[out]: Output variable to store the real target name. + + For a given ````, return the actual target if ```` is an + alias. If ```` is the target itself, return it. If ```` 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 ... + OUTPUT ) + + :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()