Merge branch 'feat/cmakev2' into 'master'

feat(cmakev2): introduce cmake-based build system v2

See merge request espressif/esp-idf!42691
This commit is contained in:
Roland Dobai
2025-11-04 02:53:36 +01:00
86 changed files with 8332 additions and 205 deletions
+1
View File
@@ -214,6 +214,7 @@
/tools/ci/ @esp-idf-codeowners/ci
/tools/cmake/ @esp-idf-codeowners/build-config
/tools/cmake/toolchain-*.cmake @esp-idf-codeowners/toolchain
/tools/cmakev2/ @esp-idf-codeowners/build-config
/tools/esp_app_trace/ @esp-idf-codeowners/debugging
/tools/gdb_panic_server.py @esp-idf-codeowners/debugging
/tools/kconfig*/ @esp-idf-codeowners/build-config
+32
View File
@@ -255,6 +255,38 @@ pytest_build_system:
extends: .test_build_system_template
parallel: 3
pytest_buildv2_system:
extends:
- .test_build_system_template
- .rules:labels:buildv2
parallel: 3
script:
- ${IDF_PATH}/tools/ci/test_configure_ci_environment.sh
- cd ${IDF_PATH}/tools/test_build_system
- run_cmd idf-ci gitlab download-known-failure-cases-file ${KNOWN_FAILURE_CASES_FILE_NAME}
- pytest
--buildv2
--cleanup-idf-copy
--parallel-count ${CI_NODE_TOTAL:-1}
--parallel-index ${CI_NODE_INDEX:-1}
--work-dir ${CI_PROJECT_DIR}/test_build_system
--junitxml ${CI_PROJECT_DIR}/XUNIT_RESULT.xml
--ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME}
--
test_non_default_target.py
test_component_manager.py
test_build.py
test_bootloader.py
test_git.py
test_kconfig.py
test_partition.py
test_reproducible_build.py
test_sdkconfig.py
test_versions.py
test_common.py
test_components.py
test_cmake.py
pytest_build_system_macos:
extends:
- .test_build_system_template
+4
View File
@@ -76,3 +76,7 @@
- if-schedule-test-build-system-windows
patterns:
- build_system_win
"labels:buildv2":
labels:
- buildv2
+10
View File
@@ -46,6 +46,7 @@
.patterns-build_system: &patterns-build_system
- "tools/cmake/**/*"
- "tools/cmakev2/**/*"
- "tools/kconfig_new/**/*"
- "tools/idf.py"
- "tools/idf_py_actions/**/*"
@@ -275,6 +276,9 @@
.if-label-build: &if-label-build
if: '$BOT_LABEL_BUILD || $CI_MERGE_REQUEST_LABELS =~ /^(?:[^,\n\r]+,)*build(?:,[^,\n\r]+)*$/i'
.if-label-buildv2: &if-label-buildv2
if: '$BOT_LABEL_BUILDV2 || $CI_MERGE_REQUEST_LABELS =~ /^(?:[^,\n\r]+,)*buildv2(?:,[^,\n\r]+)*$/i'
.if-label-docker: &if-label-docker
if: '$BOT_LABEL_DOCKER || $CI_MERGE_REQUEST_LABELS =~ /^(?:[^,\n\r]+,)*docker(?:,[^,\n\r]+)*$/i'
@@ -363,6 +367,12 @@
- <<: *if-dev-push
changes: *patterns-downloadable-tools
.rules:labels:buildv2:
rules:
- <<: *if-revert-branch
when: never
- <<: *if-label-buildv2
.rules:labels:nvs_coverage:
rules:
- <<: *if-revert-branch
+42
View File
@@ -136,3 +136,45 @@ pytest_build_system_win_minimal_cmake:
- cd ${IDF_PATH}\tools\test_build_system
- idf-ci gitlab download-known-failure-cases-file ${KNOWN_FAILURE_CASES_FILE_NAME}
- pytest -k cmake --junitxml=${CI_PROJECT_DIR}\XUNIT_RESULT.xml --ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME}
pytest_buildv2_system_win:
extends:
- .test_build_system_template_win
- .rules:labels:buildv2
parallel: 2
needs: []
tags: [windows-build, brew]
artifacts:
paths:
- XUNIT_RESULT.xml
- test_build_system
expire_in: 2 days
reports:
junit: XUNIT_RESULT.xml
when: always
script:
- .\install.ps1 --enable-ci
- . .\export.ps1
- python "${SUBMODULE_FETCH_TOOL}" -s "all"
- cd ${IDF_PATH}\tools\test_build_system
- idf-ci gitlab download-known-failure-cases-file ${KNOWN_FAILURE_CASES_FILE_NAME}
- pytest
--buildv2
--parallel-count ${CI_NODE_TOTAL}
--parallel-index ${CI_NODE_INDEX}
--junitxml=${CI_PROJECT_DIR}\XUNIT_RESULT.xml
--ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME}
--
test_non_default_target.py
test_component_manager.py
test_build.py
test_bootloader.py
test_git.py
test_kconfig.py
test_partition.py
test_reproducible_build.py
test_sdkconfig.py
test_versions.py
test_common.py
test_components.py
test_cmake.py
+11
View File
@@ -721,6 +721,17 @@ mainmenu "Espressif IoT Development Framework Configuration"
depends on "${IDF_MINIMAL_BUILD}"
source "$COMPONENT_KCONFIGS_SOURCE_FILE"
menu "Configuration for components not included in the build"
depends on "${IDF_BUILD_V2}"
osource "$COMPONENT_KCONFIGS_EXCLUDED_SOURCE_FILE"
endmenu
endmenu
menu "Configuration for components not included in the build"
depends on "${IDF_BUILD_V2}"
osource "$COMPONENT_KCONFIGS_PROJBUILD_EXCLUDED_SOURCE_FILE"
endmenu
config IDF_EXPERIMENTAL_FEATURES
+11 -7
View File
@@ -35,10 +35,14 @@ set(optional_reqs ulp
esp_hal_mspi
esp_hw_support)
idf_build_get_property(build_components BUILD_COMPONENTS)
foreach(req ${optional_reqs})
if(req IN_LIST build_components)
idf_component_get_property(req_lib ${req} COMPONENT_LIB)
target_link_libraries(${COMPONENT_LIB} PRIVATE ${req_lib})
endif()
endforeach()
if(IDF_BUILD_V2)
idf_component_optional_requires(PRIVATE ${optional_reqs})
else()
idf_build_get_property(build_components BUILD_COMPONENTS)
foreach(req ${optional_reqs})
if(req IN_LIST build_components)
idf_component_get_property(req_lib ${req} COMPONENT_LIB)
target_link_libraries(${COMPONENT_LIB} PRIVATE ${req_lib})
endif()
endforeach()
endif()
+5 -3
View File
@@ -4,8 +4,6 @@ if(${target} STREQUAL "linux")
return() # This component is not supported by the POSIX/Linux simulator
endif()
idf_build_get_property(components_to_build BUILD_COMPONENTS)
set(srcs)
set(include)
set(ld_fragments linker.lf)
@@ -19,8 +17,9 @@ if(CONFIG_ETH_ENABLED)
set(srcs "src/esp_eth.c" "src/phy/esp_eth_phy_802_3.c")
set(include "include")
if(NOT CMAKE_BUILD_EARLY_EXPANSION)
if(NOT CMAKE_BUILD_EARLY_EXPANSION AND NOT IDF_BUILD_V2)
# esp_netif related
idf_build_get_property(components_to_build BUILD_COMPONENTS)
if(esp_netif IN_LIST components_to_build)
list(APPEND srcs "src/esp_eth_netif_glue.c")
endif()
@@ -46,6 +45,9 @@ idf_component_register(SRCS "${srcs}"
PRIV_REQUIRES ${priv_requires})
if(CONFIG_ETH_ENABLED)
if(IDF_BUILD_V2)
target_sources(${COMPONENT_TARGET} PRIVATE "$<$<TARGET_EXISTS:idf::esp_netif>:src/esp_eth_netif_glue.c>")
endif()
if(CONFIG_ETH_USE_SPI_ETHERNET)
idf_component_optional_requires(PUBLIC esp_driver_spi)
endif()
+1
View File
@@ -1,5 +1,6 @@
idf_build_get_property(target IDF_TARGET)
set(includes)
list(APPEND includes "${target}/include")
list(APPEND includes "include")
+9 -1
View File
@@ -77,10 +77,18 @@ else()
list(APPEND ldfragments src/picolibc/libc.lf)
endif()
set(priv_reqs soc spi_flash)
if(IDF_BUILD_V2)
if(CONFIG_VFS_SUPPORT_IO)
list(APPEND priv_reqs vfs)
endif()
endif()
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS platform_include
PRIV_INCLUDE_DIRS priv_include
PRIV_REQUIRES soc spi_flash
PRIV_REQUIRES "${priv_reqs}"
LDFRAGMENTS "${ldfragments}")
# Toolchain libraries require code defined in this component
@@ -1,5 +1,6 @@
idf_build_get_property(target IDF_TARGET)
set(srcs)
if(CONFIG_ESP_CONSOLE_USB_CDC)
list(APPEND srcs "usb_console.c")
+8 -4
View File
@@ -69,9 +69,13 @@ if(CONFIG_HEAP_TRACING)
endforeach()
endif()
if(NOT CMAKE_BUILD_EARLY_EXPANSION)
idf_build_get_property(build_components BUILD_COMPONENTS)
if(freertos IN_LIST build_components)
target_compile_options(${COMPONENT_TARGET} PRIVATE "-DMULTI_HEAP_FREERTOS")
if(IDF_BUILD_V2)
target_compile_options(${COMPONENT_TARGET} PRIVATE "$<$<TARGET_EXISTS:idf::freertos>:-DMULTI_HEAP_FREERTOS>")
else()
if(NOT CMAKE_BUILD_EARLY_EXPANSION)
idf_build_get_property(build_components BUILD_COMPONENTS)
if(freertos IN_LIST build_components)
target_compile_options(${COMPONENT_TARGET} PRIVATE "-DMULTI_HEAP_FREERTOS")
endif()
endif()
endif()
+8 -3
View File
@@ -192,9 +192,14 @@ endif()
# net_sockets.c should only be compiled if BSD socket functions are available.
# Do this by checking if lwip component is included into the build.
if(CONFIG_LWIP_ENABLE)
list(APPEND mbedtls_target_sources "${COMPONENT_DIR}/port/net_sockets.c")
idf_component_get_property(lwip_lib lwip COMPONENT_LIB)
target_link_libraries(${COMPONENT_LIB} ${linkage_type} ${lwip_lib})
if(IDF_BUILD_V2)
target_sources(${COMPONENT_LIB} PRIVATE "$<$<TARGET_EXISTS:idf::lwip>:${COMPONENT_DIR}/port/net_sockets.c>")
target_link_libraries(${COMPONENT_LIB} ${linkage_type} "$<$<TARGET_EXISTS:idf::lwip>:idf::lwip>")
else()
list(APPEND mbedtls_target_sources "${COMPONENT_DIR}/port/net_sockets.c")
idf_component_get_property(lwip_lib lwip COMPONENT_LIB)
target_link_libraries(${COMPONENT_LIB} ${linkage_type} ${lwip_lib})
endif()
endif()
# Add port files to mbedtls targets
+5
View File
@@ -1,3 +1,8 @@
idf_build_get_property(idf_target_arch IDF_TARGET_ARCH)
if(NOT "${idf_target_arch}" STREQUAL "xtensa")
return()
endif()
# Check toolchain is configured properly in cmake
if(CMAKE_C_COMPILER_ID MATCHES "Clang")
# without '--target' option 'clang -dumpmachine' prints default target arch and it might be not Xtensa
+4
View File
@@ -8,10 +8,13 @@
# pylint: disable=undefined-variable
import os.path
import re
import sys
from pathlib import Path
from esp_docs.conf_docs import * # noqa: F403,F401
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'tools', 'cmakev2')))
if os.environ.get('IDF_PATH') is None:
raise RuntimeError('IDF_PATH should be set, run export.sh before building docs')
@@ -416,6 +419,7 @@ extensions += [ # noqa: F405
'esp_docs.esp_extensions.run_doxygen',
'esp_docs.esp_extensions.add_html_zip',
'linuxdoc.rstFlatTable', # https://return42.github.io/linuxdoc/linuxdoc-howto/table-markup.html#flat-table
'esp_docs_cmakev2_extension',
]
# Use wavedrompy as backend, instead of wavedrom-cli
+470
View File
@@ -0,0 +1,470 @@
Build System v2
***************
.. attention::
Build System v2 is currently available as a **Technical Preview** intended for **testing and evaluation**. Features, functionality, and performance are **subject to change without notice**, and **production use is not recommended** at this stage.
ESP-IDF CMake-based build system v2, referred to in this documentation simply as v2 or build system, is a successor to the original CMake-based :doc:`/api-guides/build-system`, referred to as v1. The v2 addresses limitations introduced in the previous version while trying to maintain backward compatibility for components written for v1. The most significant changes include the ability to use Kconfig variables to specify component dependencies, the removal of early component evaluation using CMake script mode, and support for writing components using the native CMake approach. While v2 aims to be as backward compatible with v1 as possible, meaning most components written for v1 should work without modification with v2, there are design differences between v1 and v2 that may require changes in v1 components to work with v2. The incompatibilities are described in :ref:`cmakev2-breaking-changes`.
Creating a New Project
======================
Below is the minimal project's ``CMakeLists.txt`` file, which should suffice for most projects:
.. code-block:: cmake
cmake_minimum_required(VERSION 3.22)
include($ENV{IDF_PATH}/tools/cmakev2/idf.cmake)
project(myProject C CXX ASM)
idf_project_default()
The order of commands in the project's ``CMakeLists.txt`` is important. First, the ``cmake_minimum_required`` command must be called to specify the minimum required version of CMake.
Next, include ``idf.cmake``, which provides the build system functionality. It performs the necessary build system initialization tasks and toolchain configuration, which must be completed before invoking CMakes ``project()`` command.
Once the build system is initialized, the ``project()`` command should be executed. It sets up project related variables, initializes the toolchain for the required languages, and performs other CMake specific initialization steps.
Finally, the :cmakev2:ref:`idf_project_default` function creates the default project executable based on the ``main`` component and its transitive dependencies. It also generates binary images and adds related targets such as ``flash`` and ``menuconfig``.
Creating a New Component
========================
This section explains how to create a new component. The preferred and generally sufficient method is described in :ref:`cmakev2-creating-compatible-component`. However, if you need more control or prefer a native CMake approach without concern for v1 compatibility, see :ref:`cmakev2-creating-v2-component`.
.. important::
Always ensure that all variables used within the component's ``CMakeLists.txt`` are initialized before use. The component might be evaluated in the context of other component where some variables may already be set. A common example is appending to CMake's list without explicitly initializing the list variable first.
.. _cmakev2-creating-compatible-component:
Creating a Component Compatible with v1 and v2
----------------------------------------------
The recommended method for creating a new component is to use the :cmakev2:ref:`idf_component_register` function. This function is provided in v2 to maintain backward compatibility with v1 and remains the preferred approach for creating components that work with both versions. The only difference between the v1 and v2 versions of this function is that the v2 version does not support the ``KCONFIG`` and ``KCONFIG_PROJBUILD`` options when using Kconfig filenames that do not follow the :ref:`cmakev2-standardized-kconfigs`. For components that require functionality beyond what :cmakev2:ref:`idf_component_register` provides, please refer to :ref:`cmakev2-breaking-changes`.
.. code-block:: cmake
:caption: Minimal Component CMakeLists.txt
idf_component_register(SRCS "foo.c" "bar.c"
INCLUDE_DIRS "include"
REQUIRES mbedtls)
.. _cmakev2-creating-v2-component:
Creating a Component for v2 Only
--------------------------------
.. important::
This section describes how to create a component for v2, which is not compatible or usable with v1. To create a component that works with both versions, it is recommended to follow :ref:`cmakev2-creating-compatible-component`.
This section demonstrates how to create a new component using a simple component named ``esp_target_info`` as an example. This component provides the ``print_esp_target_info`` function, which, when invoked, displays basic information about the target.
The layout of the component's directory is organized as follows:
.. code-block:: bash
esp_target_info
├── include
│   └── esp_target_info.h
├── srcs
│   └── esp_target_info.c
├── CMakeLists.txt
├── esp_target_info.ld
└── linker.lf
The ``esp_target_info.h`` header file provides the ``print_esp_target_info`` function declaration as its sole public interface, intended for use by other components.
.. code-block:: c
:caption: include/esp_target_info.h
#ifndef _ESP_TARGET_INFO_
#define _ESP_TARGET_INFO_
void print_esp_target_info(void);
#endif
The ``esp_target_info.c`` file contains the definition for the ``print_esp_target_info`` function. This function outputs the target name based on the configuration ``CONFIG_IDF_TARGET`` variable, the number of CPU cores as obtained from the ``esp_hw_support`` component, the available free heap size from the ``esp_system`` component, and the flash size from the ``spi_flash`` component.
.. code-block:: c
:caption: srcs/esp_target_info.c
#include <stdio.h>
#include <inttypes.h>
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"
void esp_target_chip_info(esp_chip_info_t*);
void print_esp_target_info(void)
{
esp_chip_info_t chip_info;
uint32_t flash_size = 0;
esp_target_chip_info(&chip_info);
esp_flash_get_size(NULL, &flash_size);
printf("target: %s\n", CONFIG_IDF_TARGET);
printf("cpu cores: %d\n", chip_info.cores);
printf("free heap size: %" PRIu32 "\n", esp_get_minimum_free_heap_size());
printf("flash size: %" PRIu32 "B\n", flash_size);
}
The ``esp_target_info.ld`` linker script defines an additional symbol, ``esp_target_chip_info``, which serves as an alias for the ``esp_chip_info`` function. This alias is used in ``esp_target_info.c`` to obtain chip information instead of directly calling the ``esp_chip_info`` function. This serves no real purpose and is merely an example of a working linker script used in the demonstration.
.. code-block:: bash
:caption: esp_target_info.ld
esp_target_chip_info = esp_chip_info;
The ``linker.lf`` is a linker fragment that determines the placement of ``print_esp_target_info`` in IRAM instead of flash. This is merely an example for demonstration purposes. For more details on linker fragments, please refer to :doc:`/api-guides/linker-script-generation`.
.. code-block:: bash
:caption: linker.lf
[mapping:esp_target_info]
archive: libesp_target_info.a
entries:
print_esp_target_info (noflash)
Now, let's describe how the aforementioned sources can be integrated as a component for the build system by defining the component's ``CMakeLists.txt`` file.
Create a static library target with the target name stored in the ``COMPONENT_TARGET`` variable for the ``srcs/esp_target_info.c`` source file, and add the ``include`` directory as the component's directory with public header files.
.. code-block:: cmake
:caption: CMakeLists.txt
:linenos:
:emphasize-lines: 1-7
add_library(${COMPONENT_TARGET} STATIC
"srcs/esp_target_info.c"
)
target_include_directories(${COMPONENT_TARGET} PUBLIC
${CMAKE_CURRENT_LIST_DIR}/include
)
idf_component_include(esp_hw_support)
idf_component_include(spi_flash)
idf_component_include(esp_system)
target_link_libraries(${COMPONENT_TARGET} PRIVATE
idf::esp_hw_support
idf::spi_flash
idf::esp_system
)
idf_component_set_property(${COMPONENT_TARGET} WHOLE_ARCHIVE TRUE)
idf_component_set_property(${COMPONENT_TARGET} LDFRAGMENTS linker.lf APPEND)
idf_component_set_property(${COMPONENT_TARGET} LINKER_SCRIPTS esp_target_info.ld APPEND)
target_link_options(${COMPONENT_TARGET} INTERFACE "SHELL:-u esp_chip_info")
When a component is evaluated by the build system, the ``COMPONENT_TARGET`` variable is assigned a target name that the component is responsible for creating. This target represents the component within the build and is linked to the component interface target managed by the build system. The component interface target is what other components use to declare dependencies on this component. Other components can obtain a components interface target using the :cmakev2:ref:`idf_component_include` function and link to it as needed.
Include all required components and link them to the component's target.
.. code-block:: cmake
:caption: CMakeLists.txt
:linenos:
:emphasize-lines: 9-17
add_library(${COMPONENT_TARGET} STATIC
"srcs/esp_target_info.c"
)
target_include_directories(${COMPONENT_TARGET} PUBLIC
${CMAKE_CURRENT_LIST_DIR}/include
)
idf_component_include(esp_hw_support)
idf_component_include(spi_flash)
idf_component_include(esp_system)
target_link_libraries(${COMPONENT_TARGET} PRIVATE
idf::esp_hw_support
idf::spi_flash
idf::esp_system
)
idf_component_set_property(${COMPONENT_TARGET} WHOLE_ARCHIVE TRUE)
idf_component_set_property(${COMPONENT_TARGET} LDFRAGMENTS linker.lf APPEND)
idf_component_set_property(${COMPONENT_TARGET} LINKER_SCRIPTS esp_target_info.ld APPEND)
target_link_options(${COMPONENT_TARGET} INTERFACE "SHELL:-u esp_chip_info")
.. note::
The v1 automatically adds component dependencies in certain situations. For instance, if the ``main`` component does not specify its dependencies, all components identified by the build system are added as dependencies to the ``main`` component. Additionally, component dependencies specified in ``idf_component.yaml`` for the component manager are also added as dependencies to the component. In contrast, the v2 does not automatically add any dependencies, all component dependencies must be explicitly stated.
The ``esp_target_info`` component uses functionality from other components, namely ``esp_hw_support``, ``esp_system``, and ``spi_flash``, to collect basic information about the target. Therefore, it must declare its dependencies on these components. The component acquires the required component interface targets using the :cmakev2:ref:`idf_component_include` function.
Each component is evaluated exactly once by the :cmakev2:ref:`idf_component_include` function. This function essentially invokes CMakes ``add_subdirectory`` for the specified component directory and links the ``COMPONENT_TARGET`` created by that component with its interface target.
The interface target can then be used by other components to declare dependencies. It can be referenced directly via a predefined alias named ``idf::<component name>``, which provides a convenient and literal way to reference a components interface target, as shown in the example above.
The :cmakev2:ref:`idf_component_include` function also supports an ``INTERFACE`` option that allows storing the components interface target name in a variable. This approach is useful in more complex cases where the target needs to be passed to other CMake functions, stored in lists, or referenced programmatically.
.. code-block:: cmake
:caption: Declaring component dependencies using interface target variable
idf_component_include(spi_flash INTERFACE spi_flash_iface)
target_link_libraries(${COMPONENT_TARGET} PRIVATE
${spi_flash_iface}
)
Component dependencies can be conditionally expressed based on the configuration. When a component's ``CMakeLists.txt`` is evaluated by the build system, the configuration for all discovered components is available. If a component needs to declare a conditional dependency based on a configuration variable, it must ensure that the component is included in the build system first before using it by including the component with the :cmakev2:ref:`idf_component_include` function. For example, if a component has a conditional dependency on the ``vfs`` component based on the ``CONFIG_VFS_SUPPORT_IO`` configuration option being set, it needs to include the ``vfs`` component before linking it to the component target.
.. code-block:: cmake
:caption: Declaring optional component dependency
if(CONFIG_VFS_SUPPORT_IO)
idf_component_include(vfs)
target_link_libraries(${COMPONENT_TARGET} PRIVATE
idf::vfs
)
endif()
.. important::
Always ensure that the required component is included before using it. The presence of certain component configuration options does not guarantee that the component will be included in the project build.
Set miscellaneous component properties. This primarily serves as an example to demonstrate other commonly used features that the component may configure.
.. code-block:: cmake
:caption: CMakeLists.txt
:linenos:
:emphasize-lines: 19-23
add_library(${COMPONENT_TARGET} STATIC
"srcs/esp_target_info.c"
)
target_include_directories(${COMPONENT_TARGET} PUBLIC
${CMAKE_CURRENT_LIST_DIR}/include
)
idf_component_include(esp_hw_support)
idf_component_include(spi_flash)
idf_component_include(esp_system)
target_link_libraries(${COMPONENT_TARGET} PRIVATE
idf::esp_hw_support
idf::spi_flash
idf::esp_system
)
idf_component_set_property(${COMPONENT_TARGET} WHOLE_ARCHIVE TRUE)
idf_component_set_property(${COMPONENT_TARGET} LDFRAGMENTS linker.lf APPEND)
idf_component_set_property(${COMPONENT_TARGET} LINKER_SCRIPTS esp_target_info.ld APPEND)
target_link_options(${COMPONENT_TARGET} INTERFACE "SHELL:-u esp_chip_info")
WHOLE_ARCHIVE:
Include all object files from the component's static library when linked.
LDFRAGMENTS:
Include files from this property to be processed with the :doc:`/api-guides/linker-script-generation`.
LINKER_SCRIPTS:
Include files from this property in the link command line as linker scripts.
Also ensure that the ``esp_chip_info`` function is retained in the final binary even when section garbage collection, ``--gc-sections``, is enabled. This is required because ``esp_target_info.ld`` defines ``esp_target_chip_info`` as an alias for ``esp_chip_info``, and without forcing the linker to include it, the underlying ``esp_chip_info`` function could be discarded as unused.
.. _cmakev2-breaking-changes:
Breaking Changes for v1 Components
==================================
While most of the v1 components should be usable without any modifications in v2, there are some differences between v1 and v2 that might require changes to v1 components. This section outlines the breaking changes that may occur if a v1 component is used with v2 and explains how the component can be modified to work with both v1 and v2.
Updating Component for v2 Compatibility
----------------------------------------------
If a component needs to be evaluated with both v1 and v2, and requires modifications to function with v2, the ``IDF_BUILD_V2`` variable can be used by the component to determine whether it is being evaluated under v1 or v2, and adjust its behavior accordingly. The ``IDF_BUILD_V2`` is set when the component is evaluated with v2. This variable can be used to conditionally adjust only small parts of the existing v1 component, or a completely new ``CMakeLists.txt`` can be created for v2 and conditionally included in the v1 component's ``CMakeLists.txt``. For more information on how to write a v2 component, please see `Creating a New Component`_.
Below is the ``hello_world`` example component with its ``CMakeLists.txt`` adjusted to include and evaluate the separate v2 component ``CMakeLists_v2.txt`` if evaluated under v2.
.. note::
The existing v1 ``hello_world`` component should function without modification in v2. This is just a simple example of how ``IDF_BUILD_V2`` can be used to evaluate different component's ``CMakeLists.txt`` files for v1 and v2.
Adjusted original v1 hello_world example ``CMakeLists.txt``.
.. code-block:: cmake
if(IDF_BUILD_V2)
# Include component CMake code for v2 and return.
include(CMakeLists_v2.txt)
return()
endif()
# Here follows the original component CMake code for v1.
idf_component_register(SRCS "hello_world_main.c"
PRIV_REQUIRES spi_flash
INCLUDE_DIRS "")
The hello_world example ``CMakeLists_v2.txt`` for v2.
.. code-block:: cmake
idf_component_include(spi_flash)
add_library(${COMPONENT_TARGET} STATIC
"hello_world_main.c"
)
target_include_directories(${COMPONENT_TARGET} PUBLIC
"${CMAKE_CURRENT_LIST_DIR}"
)
target_link_libraries(${COMPONENT_TARGET} PRIVATE
idf::spi_flash
)
The ``BUILD_COMPONENTS`` Build Property is Unavailable
------------------------------------------------------
The v1, by design, collects components built by the project in the ``BUILD_COMPONENTS`` build property. This is achieved by evaluating components early using the CMake script mode and collecting components based on their dependencies as provided in :cmakev2:ref:`idf_component_register`. Later, the ``BUILD_COMPONENTS`` are evaluated again using CMake's ``add_subdirectory``. This approach imposes several restrictions. First, components cannot express their dependencies based on Kconfig variables because Kconfig variables are not known during the early evaluation. Second, the CMake script mode does not allow CMake commands that define build targets or actions, which caused confusion about which commands can be used in a component and when they are actually evaluated. The v2 removes this two-stage component evaluation, and hence the ``BUILD_COMPONENTS`` build property doesn't exist in v2. Any attempt to obtain the ``BUILD_COMPONENTS`` build property with :cmakev2:ref:`idf_build_get_property` will result in an error. The ``BUILD_COMPONENTS`` build property was primarily used in v1 by components to discover which other components were being built by the project and to adjust their behavior, for example, by adding additional source files. In v2, the functionality of the ``BUILD_COMPONENTS`` build property can be replaced with the ``$<TARGET_EXISTS:tgt>`` generator expression.
For example, the ``esp_eth`` component is compiled with the additional ``esp_eth_netif_glue.c`` file if the ``esp_netif`` component is also being built in v1 with:
.. code-block:: cmake
idf_build_get_property(components_to_build BUILD_COMPONENTS)
if(esp_netif IN_LIST components_to_build)
list(APPEND srcs "src/esp_eth_netif_glue.c")
endif()
To use the ``esp_eth`` component with v2, the usage of ``BUILD_COMPONENTS`` has to be replaced with:
.. code-block:: cmake
if(IDF_BUILD_V2)
target_sources(${COMPONENT_TARGET} PRIVATE "$<$<TARGET_EXISTS:idf::esp_netif>:src/esp_eth_netif_glue.c>")
else()
idf_build_get_property(components_to_build BUILD_COMPONENTS)
if(esp_netif IN_LIST components_to_build)
list(APPEND srcs "src/esp_eth_netif_glue.c")
endif()
endif()
.. note::
The ``$<TARGET_EXISTS:tgt>`` generator expression should work for both v1 and v2. The ``IDF_BUILD_V2`` is used to demonstrate the different approaches in v1 and v2.
.. _cmakev2-standardized-kconfigs:
Standardized ``Kconfig`` and ``Kconfig.projbuild`` File Names
-------------------------------------------------------------
The v1 allows the use of ``Kconfig`` and ``Kconfig.projbuild`` with custom file names in :cmakev2:ref:`idf_component_register`. This is possible because, in v1, the components participating in the project build are collected during early component evaluation, allowing for the specification of custom names for the Kconfig files. The v2 does not perform early evaluation, and the Kconfig files are collected based on the fixed ``Kconfig`` and ``Kconfig.projbuild`` file names, which must be present in the component's root directory. This can be resolved by renaming the Kconfig files that do not conform to this naming convention or by including them in Kconfig files that comply with this convention.
Component Configuration Visibility
----------------------------------
In v1, the ``sdkconfig`` is generated based on Kconfig files provided by components that are part of the project build. This means that if a component is not included in the project build, for instance, if it is not required as a dependency of another component, its configuration will not be visible or available when components are evaluated. In v2, the ``sdkconfig`` is generated based on Kconfig files for all available components. This means the presence of some configuration options does not guarantee that the component providing this configuration option is participating in the build, unlike in v1.
For example, in v1, the following condition in the ``esp_vfs_console`` component links the ``vfs`` component to the ``esp_vfs_console`` component based on the ``CONFIG_VFS_SUPPORT_IO`` configuration option being set. This works in v1 because the ``CONFIG_VFS_SUPPORT_IO`` configuration option is not available if the ``vfs`` component is not participating in the project build.
.. code-block:: cmake
if(CONFIG_VFS_SUPPORT_IO)
target_link_libraries(${COMPONENT_LIB} PUBLIC idf::vfs)
# Make sure esp_vfs_console_register gets called at startup stage
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u esp_vfs_include_console_register")
endif()
In contrast, in v2, configuration for all discovered components is available. This means that even if a component does not participate in the project build, its configuration will still be accessible. If the ``CONFIG_VFS_SUPPORT_IO`` configuration option is set by default, the condition will evaluate as true in v2, even if the ``vfs`` component is not participating in the project build and the ``idf::vfs`` target does not exist. The ``$<TARGET_EXISTS:tgt>`` generator expression can be used to express such dependencies in v2.
.. code-block:: cmake
if(IDF_BUILD_V2)
target_link_libraries(${COMPONENT_LIB} PUBLIC "$<$<TARGET_EXISTS:idf::vfs>:idf::vfs>")
# Make sure esp_vfs_console_register gets called at startup stage
target_link_libraries(${COMPONENT_LIB} INTERFACE "$<$<TARGET_EXISTS:idf::vfs>:-u esp_vfs_include_console_register>")
else()
if(CONFIG_VFS_SUPPORT_IO)
target_link_libraries(${COMPONENT_LIB} PUBLIC idf::vfs)
# Make sure esp_vfs_console_register gets called at startup stage
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u esp_vfs_include_console_register")
endif()
endif()
.. note::
The ``$<TARGET_EXISTS:tgt>`` generator expression should work for both v1 and v2. The ``IDF_BUILD_V2`` is used to demonstrate the different approaches in v1 and v2.
.. important::
This change applies not only to a component's ``CMakeLists.txt`` but also to its source files. The component's code can no longer assume that the functionality of other components is available simply because their Kconfig variables are set. For example, if the ``CONFIG_VFS_SUPPORT_IO`` configuration variable is set and the component's code depends on the functionality of the ``vfs`` component, it cannot merely check ``CONFIG_VFS_SUPPORT_IO`` in the source code. It must ensure that the ``vfs`` component is included in the project build and that the component has a declared dependency on the ``vfs`` component in its ``CMakeLists.txt``.
Recursive Evaluation of Components
----------------------------------
In v1, the components participating in the project build are collected in the ``BUILD_COMPONENTS`` build property during the early evaluation phase, prior to their evaluation with ``add_subdirectory``. This means that v1 is aware of all components participating in the project build and their dependencies before CMake evaluates them with the ``add_subdirectory`` call. This allows v1 to evaluate components in a non-recursive manner and within a relatively predictable environment with minimal variables set. In contrast, v2 does not perform early component evaluation and components are added based on the requirements observed during their evaluation. This means a component can be evaluated recursively within the scope of another component's variables if it is a dependency of that component. In other words, if component A requires component B, then when B is evaluated, it can access the variables of A. Hence, it's important to ensure that all variables used by the component are properly initialized before usage. A typical problem involves CMake lists and the ``APPEND`` operation.
.. code-block:: cmake
# Wrong
list(APPEND srcs main.c)
# Correct
set(srcs)
list(APPEND srcs main.c)
The ``project_include.cmake`` Files are Included in a Non-Specific Order
------------------------------------------------------------------------
In v1, the ``project_include.cmake`` files from components are included in the order specified by the ``BUILD_COMPONENTS`` build property. The components in ``BUILD_COMPONENTS`` are sorted based on their requirements, as determined during early evaluation. This means a given component's ``project_include.cmake`` is included only after all ``project_include.cmake`` files for its dependencies have been included. This holds true as long as there is no cyclic dependency between components. In contrast, v2 does not perform early evaluation of components, and ``BUILD_COMPONENTS`` does not exist. Therefore, in v2, the ``project_include.cmake`` files are included for all discovered components, not just those participating in the project build, and they are included in the order in which the components are discovered by v2. As a result, cross-file functionality at the global scope between ``project_include.cmake`` files can no longer be relied upon in v2.
.. note::
It is still possible to call functions or macros defined in another ``project_include.cmake`` file, provided they are invoked within a CMake function or another non-global scope. Only global-scope interactions are unreliable in v2.
Strict Component Precedence
---------------------------
The v2 strictly adheres to the component precedence for components with the same name, as described in :ref:`cmake-components-same-name`. While v1 allows components discovered in directories specified with the ``EXTRA_COMPONENT_DIRS`` variable to be overridden by `Local Directory Dependencies`_ specified in the ``idf_component.yml`` manifest file, this is no longer possible in v2.
API Reference
=============
This section contains an automatically generated API reference from the build
system v2 CMake source code.
.. _cmakev2_variables:
Variables
---------
.. _cmakev2_functions:
Functions
---------
.. _cmakev2_macros:
Macros
------
.. cmakev2:include:: ../../../tools/cmakev2/build.cmake
.. cmakev2:include:: ../../../tools/cmakev2/compat.cmake
.. cmakev2:include:: ../../../tools/cmakev2/component.cmake
.. cmakev2:include:: ../../../tools/cmakev2/idf.cmake
.. cmakev2:include:: ../../../tools/cmakev2/kconfig.cmake
.. cmakev2:include:: ../../../tools/cmakev2/ldgen.cmake
.. cmakev2:include:: ../../../tools/cmakev2/manager.cmake
.. cmakev2:include:: ../../../tools/cmakev2/project.cmake
.. cmakev2:include:: ../../../tools/cmakev2/utilities.cmake
.. _Local Directory Dependencies: https://docs.espressif.com/projects/idf-component-manager/en/latest/reference/manifest_file.html#local-directory-dependencies
+1
View File
@@ -13,6 +13,7 @@ API Guides
:SOC_BLE_MESH_SUPPORTED: esp-ble-mesh/ble-mesh-index
bootloader
build-system
build-system-v2
:SOC_SUPPORT_COEXISTENCE: coexist
c
cplusplus
@@ -0,0 +1 @@
.. include:: ../../en/api-guides/build-system-v2.rst
+1
View File
@@ -13,6 +13,7 @@ API 指南
:SOC_BLE_MESH_SUPPORTED: esp-ble-mesh/ble-mesh-index
bootloader
build-system
build-system-v2
:SOC_SUPPORT_COEXISTENCE: coexist
c
cplusplus
+15
View File
@@ -0,0 +1,15 @@
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
# Remove duplicates from a string containing compilation flags
function(remove_duplicated_flags FLAGS UNIQFLAGS)
set(FLAGS_LIST "${FLAGS}")
# Convert the given flags, as a string, into a CMake list type
separate_arguments(FLAGS_LIST)
# Remove all the duplicated flags
list(REMOVE_DUPLICATES FLAGS_LIST)
# Convert the list back to a string
string(REPLACE ";" " " FLAGS_LIST "${FLAGS_LIST}")
# Return that string to the caller
set(${UNIQFLAGS} "${FLAGS_LIST}" PARENT_SCOPE)
endfunction()
+1 -1
View File
@@ -1,4 +1,4 @@
include($ENV{IDF_PATH}/tools/cmake/utilities.cmake)
include($ENV{IDF_PATH}/tools/cmake/deduplicate_flags.cmake)
set(CMAKE_SYSTEM_NAME Generic)
+1 -1
View File
@@ -1,4 +1,4 @@
include($ENV{IDF_PATH}/tools/cmake/utilities.cmake)
include($ENV{IDF_PATH}/tools/cmake/deduplicate_flags.cmake)
set(CMAKE_SYSTEM_NAME Generic)
+1 -1
View File
@@ -1,4 +1,4 @@
include($ENV{IDF_PATH}/tools/cmake/utilities.cmake)
include($ENV{IDF_PATH}/tools/cmake/deduplicate_flags.cmake)
set(CMAKE_SYSTEM_NAME Generic)
+1 -1
View File
@@ -1,4 +1,4 @@
include($ENV{IDF_PATH}/tools/cmake/utilities.cmake)
include($ENV{IDF_PATH}/tools/cmake/deduplicate_flags.cmake)
set(CMAKE_SYSTEM_NAME Generic)
+1 -1
View File
@@ -1,4 +1,4 @@
include($ENV{IDF_PATH}/tools/cmake/utilities.cmake)
include($ENV{IDF_PATH}/tools/cmake/deduplicate_flags.cmake)
set(CMAKE_SYSTEM_NAME Generic)
+1 -1
View File
@@ -1,4 +1,4 @@
include($ENV{IDF_PATH}/tools/cmake/utilities.cmake)
include($ENV{IDF_PATH}/tools/cmake/deduplicate_flags.cmake)
set(CMAKE_SYSTEM_NAME Generic)
+1 -1
View File
@@ -1,4 +1,4 @@
include($ENV{IDF_PATH}/tools/cmake/utilities.cmake)
include($ENV{IDF_PATH}/tools/cmake/deduplicate_flags.cmake)
set(CMAKE_SYSTEM_NAME Generic)
+1 -1
View File
@@ -1,4 +1,4 @@
include($ENV{IDF_PATH}/tools/cmake/utilities.cmake)
include($ENV{IDF_PATH}/tools/cmake/deduplicate_flags.cmake)
set(CMAKE_SYSTEM_NAME Generic)
+1 -1
View File
@@ -1,4 +1,4 @@
include($ENV{IDF_PATH}/tools/cmake/utilities.cmake)
include($ENV{IDF_PATH}/tools/cmake/deduplicate_flags.cmake)
set(CMAKE_SYSTEM_NAME Generic)
+1 -1
View File
@@ -1,4 +1,4 @@
include($ENV{IDF_PATH}/tools/cmake/utilities.cmake)
include($ENV{IDF_PATH}/tools/cmake/deduplicate_flags.cmake)
set(CMAKE_SYSTEM_NAME Generic)
+1 -1
View File
@@ -1,4 +1,4 @@
include($ENV{IDF_PATH}/tools/cmake/utilities.cmake)
include($ENV{IDF_PATH}/tools/cmake/deduplicate_flags.cmake)
set(CMAKE_SYSTEM_NAME Generic)
+1 -1
View File
@@ -1,4 +1,4 @@
include($ENV{IDF_PATH}/tools/cmake/utilities.cmake)
include($ENV{IDF_PATH}/tools/cmake/deduplicate_flags.cmake)
set(CMAKE_SYSTEM_NAME Generic)
+1 -1
View File
@@ -1,4 +1,4 @@
include($ENV{IDF_PATH}/tools/cmake/utilities.cmake)
include($ENV{IDF_PATH}/tools/cmake/deduplicate_flags.cmake)
set(CMAKE_SYSTEM_NAME Generic)
+1 -1
View File
@@ -1,4 +1,4 @@
include($ENV{IDF_PATH}/tools/cmake/utilities.cmake)
include($ENV{IDF_PATH}/tools/cmake/deduplicate_flags.cmake)
set(CMAKE_SYSTEM_NAME Generic)
+1 -1
View File
@@ -1,4 +1,4 @@
include($ENV{IDF_PATH}/tools/cmake/utilities.cmake)
include($ENV{IDF_PATH}/tools/cmake/deduplicate_flags.cmake)
set(CMAKE_SYSTEM_NAME Generic)
+1 -1
View File
@@ -1,4 +1,4 @@
include($ENV{IDF_PATH}/tools/cmake/utilities.cmake)
include($ENV{IDF_PATH}/tools/cmake/deduplicate_flags.cmake)
set(CMAKE_SYSTEM_NAME Generic)
+1 -1
View File
@@ -1,4 +1,4 @@
include($ENV{IDF_PATH}/tools/cmake/utilities.cmake)
include($ENV{IDF_PATH}/tools/cmake/deduplicate_flags.cmake)
set(CMAKE_SYSTEM_NAME Generic)
+1 -1
View File
@@ -1,4 +1,4 @@
include($ENV{IDF_PATH}/tools/cmake/utilities.cmake)
include($ENV{IDF_PATH}/tools/cmake/deduplicate_flags.cmake)
set(CMAKE_SYSTEM_NAME Generic)
+1 -1
View File
@@ -1,4 +1,4 @@
include($ENV{IDF_PATH}/tools/cmake/utilities.cmake)
include($ENV{IDF_PATH}/tools/cmake/deduplicate_flags.cmake)
set(CMAKE_SYSTEM_NAME Generic)
+1 -1
View File
@@ -1,4 +1,4 @@
include($ENV{IDF_PATH}/tools/cmake/utilities.cmake)
include($ENV{IDF_PATH}/tools/cmake/deduplicate_flags.cmake)
set(CMAKE_SYSTEM_NAME Generic)
+1 -1
View File
@@ -1,4 +1,4 @@
include($ENV{IDF_PATH}/tools/cmake/utilities.cmake)
include($ENV{IDF_PATH}/tools/cmake/deduplicate_flags.cmake)
set(CMAKE_SYSTEM_NAME Generic)
+1 -1
View File
@@ -1,4 +1,4 @@
include($ENV{IDF_PATH}/tools/cmake/utilities.cmake)
include($ENV{IDF_PATH}/tools/cmake/deduplicate_flags.cmake)
set(CMAKE_SYSTEM_NAME Generic)
+1 -1
View File
@@ -1,4 +1,4 @@
include($ENV{IDF_PATH}/tools/cmake/utilities.cmake)
include($ENV{IDF_PATH}/tools/cmake/deduplicate_flags.cmake)
set(CMAKE_SYSTEM_NAME Generic)
+2 -14
View File
@@ -1,3 +1,5 @@
include(${CMAKE_CURRENT_LIST_DIR}/deduplicate_flags.cmake)
# set_default
#
# Define a variable to a default value if otherwise unset.
@@ -457,17 +459,3 @@ function(add_deprecated_target_alias old_target new_target)
)
add_dependencies(${old_target} ${new_target})
endfunction()
# Remove duplicates from a string containing compilation flags
function(remove_duplicated_flags FLAGS UNIQFLAGS)
set(FLAGS_LIST "${FLAGS}")
# Convert the given flags, as a string, into a CMake list type
separate_arguments(FLAGS_LIST)
# Remove all the duplicated flags
list(REMOVE_DUPLICATES FLAGS_LIST)
# Convert the list back to a string
string(REPLACE ";" " " FLAGS_LIST "${FLAGS_LIST}")
# Return that string to the caller
set(${UNIQFLAGS} "${FLAGS_LIST}" PARENT_SCOPE)
endfunction()
File diff suppressed because it is too large Load Diff
+552
View File
@@ -0,0 +1,552 @@
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
include_guard(GLOBAL)
#[[
check_expected_tool_version()
Function to verify if the tool is using the expected version and issue a
warning if it is not.
#]]
function(check_expected_tool_version tool_name tool_path)
set(tool_version_warning "Check Getting Started documentation or proceed at own risk.\n")
set(tool_version_error "Check Getting Started documentation if the error continues.\n"
"You can override this error and proceed with build by defining the IDF_MAINTAINER environment variable.\n")
set(fixing_hint "Please try to run 'idf.py fullclean' to solve it.\n")
idf_build_get_property(python PYTHON)
idf_build_get_property(idf_path IDF_PATH)
set(ENV{IDF_TOOLS_VERSION_HELPER} "1")
# Use idf_tools.py to check if tool version is supported
execute_process(
COMMAND ${python} "${idf_path}/tools/idf_tools.py"
"check-tool-supported" "--tool-name" "${tool_name}"
"--exec-path" "${tool_path}"
OUTPUT_VARIABLE is_version_supported
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET)
if(is_version_supported STREQUAL "False")
# Version is not supported. Need to get supported versions list to print them to user
execute_process(
COMMAND ${python} "${idf_path}/tools/idf_tools.py"
"get-tool-supported-versions" "--tool-name" "${tool_name}"
OUTPUT_VARIABLE tool_supported_versions
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET)
# IDF maintainers can build projects with not supported versions with just a warning
if($ENV{IDF_MAINTAINER})
set(message_mode "WARNING")
else()
set(message_mode "FATAL_ERROR")
endif()
idf_msg(${message_mode} "\n"
"Tool doesn't match supported version from list "
"${tool_supported_versions}: ${tool_path}\n"
${fixing_hint})
elseif(NOT is_version_supported STREQUAL "True")
idf_warn("Can not get version for tool: ${tool_path}\n" ${tool_version_warning})
endif()
unset(ENV{IDF_TOOLS_VERSION_HELPER})
endfunction()
#[[
__get_component_sources([SRCS <src>...]
[SRC_DIRS <dir>...]
[EXCLUDE_SRCS <exclude>...]
OUTPUT <var>)
*SRCS[in,opt]*
Optional list of sources.
*SRC_DIRS[in,opt]*
Option list of source directories.
*EXCLUDE_SRCS[in,opt]*
Optional list of sources to exclude.
*OUTPUT[out]*
Output variable to store the list of component sources.
This helper function gathers component sources from the arguments specified
in either ``SRCS`` or ``SRC_DIRS``. If both are provided, the ``SRC_DIRS``
option is disregarded. The sources are collected as absolute paths relative
to the component's directory. If ``EXCLUDE_SRCS`` is specified, the source
files listed in it are excluded. The final list of component sources is
returned in the ``OUTPUT`` variable.
The ``COMPONENT_NAME`` and ``COMPONENT_DIR`` variables are provided by the
build system when ``idf_component_register`` is called.
#]]
function(__get_component_sources)
set(options)
set(one_value OUTPUT)
set(multi_value SRCS SRC_DIRS EXCLUDE_SRCS)
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
if(NOT DEFINED ARG_OUTPUT)
idf_die("OUTPUT option is required")
endif()
if(DEFINED ARG_SRCS AND DEFINED ARG_SRC_DIRS)
idf_warn("SRCS and SRC_DIRS are both specified for '${COMPONENT_NAME}' "
"in '${COMPONENT_DIR}'. Ignoring SRC_DIRS.")
unset(ARG_SRC_DIRS)
endif()
set(sources)
if(ARG_SRCS)
__get_absolute_paths(PATHS "${ARG_SRCS}" BASE_DIR "${COMPONENT_DIR}" OUTPUT sources)
elseif(ARG_SRC_DIRS)
__get_absolute_paths(PATHS "${ARG_SRC_DIRS}" BASE_DIR "${COMPONENT_DIR}" OUTPUT dirs)
foreach(dir IN LISTS dirs)
if(NOT IS_DIRECTORY "${dir}")
idf_die("SRC_DIRS entry '${dir}' does not exist for component "
"'${COMPONENT_NAME}' in '${COMPONENT_DIR}'.")
endif()
file(GLOB dir_sources "${dir}/*.c" "${dir}/*.cpp" "${dir}/*.S")
list(SORT dir_sources)
if(NOT dir_sources)
idf_warn("No source files found for SRC_DIRS entry '${dir}' for "
"'${COMPONENT_NAME}' in '${COMPONENT_DIR}'.")
continue()
endif()
list(APPEND sources "${dir_sources}")
endforeach()
endif()
if(ARG_EXCLUDE_SRCS)
__get_absolute_paths(PATHS "${ARG_EXCLUDE_SRCS}" BASE_DIR "${COMPONENT_DIR}" OUTPUT exclude_srcs)
foreach(src IN LISTS exclude_srcs)
list(REMOVE_ITEM sources "${src}")
endforeach()
endif()
list(REMOVE_DUPLICATES sources)
set(${ARG_OUTPUT} "${sources}" PARENT_SCOPE)
endfunction()
#[[
.. cmakev2:function:: target_linker_script
.. code-block:: cmake
target_linker_script(<target> <deptype> <scriptfile>...
[PROCESS <output>])
*target[in]*
The component target to which linker script files should be added.
*deptype[in]*
This option is obsolete and maintained solely for backward
compatibility.
*scriptfile[in]*
Specifies the linker script file ``scriptfile`` to be added to the
link. Multiple files can be specified.
*PROCESS[in,opt]*
Specifies the ``output`` linker script, which is generated from the
``linkerscript`` template. The ``linkerscript`` is processed with ldgen
to produce the ``output``.
This function adds one or more linker scripts to the specified component
target, incorporating the linker script into the linking process.
If the ``PROCESS`` option is specified, the last ``scriptfile`` listed is
processed using the ldgen command, and the generated ``output`` file is
used as the linker script during the linking process. This implies that
with the ``PROCESS`` option, it is logical to provide only a single
``scriptfile`` as a template.
#]]
function(target_linker_script target deptype scriptfiles)
# The linker script files, templates, and their output filenames are stored
# only as component properties. The script files are generated and added to
# the library link interface in the idf_build_library function.
set(options)
set(one_value PROCESS)
set(multi_value)
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
foreach(scriptfile ${scriptfiles})
get_filename_component(scriptfile "${scriptfile}" ABSOLUTE)
idf_msg("Adding linker script ${scriptfile}")
if(ARG_PROCESS)
get_filename_component(output "${ARG_PROCESS}" ABSOLUTE)
idf_component_set_property("${target}" LINKER_SCRIPTS_TEMPLATE "${scriptfile}" APPEND)
idf_component_set_property("${target}" LINKER_SCRIPTS_GENERATED "${output}" APPEND)
else()
idf_component_set_property("${target}" LINKER_SCRIPTS ${scriptfile} APPEND)
endif()
endforeach()
endfunction()
#[[
.. cmakev2:function:: idf_component_optional_requires
.. code-block:: cmake
idf_component_optional_requires(<type> <component>...)
*type[in]*
Type of dependency: PRIVATE, PUBLIC or INTERFACE
*component[in]*
The component name that should be added as a dependency to the
evaluated component. It may be provided multiple times.
Add a dependency on a specific component only if it is included in the
build.
#]]
function(idf_component_optional_requires req_type)
set(optional_reqs ${ARGN})
foreach(req ${optional_reqs})
__get_component_interface(COMPONENT "${req}" OUTPUT req_interface)
if("${req_interface}" STREQUAL "NOTFOUND")
continue()
endif()
idf_component_get_property(req_alias "${req}" COMPONENT_ALIAS)
# The component alias is created only after the component is included,
# meaning the add_subdirectory command for it has been called. This can
# be used to detect if a component has already been added to the build.
target_link_libraries(${COMPONENT_TARGET} ${req_type} "$<$<TARGET_EXISTS:${req_alias}>:${req_interface}>")
endforeach()
endfunction()
#[[
__init_common_components()
Identify the commonly required components based on the target and
architecture, and store them in the __COMPONENT_REQUIRES_COMMON build
property. Their interfaces are stored in the __COMMON_COMPONENT_INTERFACES
build property. The commonly required component interfaces are
automatically linked to each cmakev1 component added through the
idf_component_register function.
This function is called from the idf_component_register function and is
evaluated only once per project, as ensured by the
__COMMON_COMPONENTS_INITIALIZED build property. The cmakev2 components are
expected to properly specify all their dependencies, rather than relying on
common components to be automatically linked to them. Therefore, the common
components are relevant only within the context of cmakev1 components.
#]]
function(__init_common_components)
idf_build_get_property(common_components_initialized __COMMON_COMPONENTS_INITIALIZED)
if(common_components_initialized)
return()
endif()
idf_build_get_property(idf_target IDF_TARGET)
idf_build_get_property(idf_target_arch IDF_TARGET_ARCH)
# Define common components that are included as dependencies for each
# component.
if("${idf_target}" STREQUAL "linux")
set(requires_common freertos esp_hw_support heap log soc hal esp_rom esp_common esp_system linux)
else()
set(requires_common cxx esp_libc freertos esp_hw_support heap log soc hal esp_rom esp_common
esp_system ${idf_target_arch})
endif()
idf_build_set_property(__COMPONENT_REQUIRES_COMMON "${requires_common}")
# Set the common component interfaces first, before including them, so the
# idf_component_include function can see the complete list of common
# component interfaces.
set(common_component_interfaces "")
foreach(component_name IN LISTS requires_common)
idf_component_get_property(component_interface "${component_name}" COMPONENT_INTERFACE)
list(APPEND common_component_interfaces "${component_interface}")
endforeach()
idf_build_set_property(__COMMON_COMPONENT_INTERFACES "${common_component_interfaces}")
foreach(component_name IN LISTS requires_common)
idf_component_include("${component_name}")
endforeach()
idf_build_set_property(__COMMON_COMPONENTS_INITIALIZED YES)
endfunction()
#[[api
.. cmakev2:function:: idf_component_register
.. code-block:: cmake
idf_component_register([SRCS <file>..]
[SRC_DIRS <dir>...]
[EXCLUDE_SRCS <file>...]
[INCLUDE_DIRS <dir>...]
[PRIV_INCLUDE_DIRS <dir>...]
[LDFRAGMENTS <file>...]
[REQUIRES <component>...]
[PRIV_REQUIRES <component>...]
[REQUIRED_IDF_TARGETS <target>...]
[EMBED_FILES <file>...]
[EMBED_TXTFILES <file>...]
[WHOLE_ARCHIVE])
*SRCS[in,opt]*
List of source files for the component.
*SRC_DIRS[in,opt]*
List of source directories to search for source files (.c, .cpp, .S);
ignored when SRCS is specified.
*EXCLUDE_SRCS[in,opt]*
List of source files to exclude from the designated source directories.
*INCLUDE_DIRS[in,opt]*
List of public include directories for the created component library.
*PRIV_INCLUDE_DIRS[in,opt]*
List of private include directories for the newly created component
library.
*LDFRAGMENTS[in,opt]*
List of linker script fragments for the component.
*REQUIRES[in,opt]*
List of publicly required components based on usage requirements.
*PRIV_REQUIRES[in,opt]*
List of privately required components based on usage requirements.
*REQUIRED_IDF_TARGETS[in,opt]*
List of IDF build targets supported exclusively by the component.
*EMBED_FILES[in,opt]*
List of binary files to embed with the component.
*EMBED_TXTFILES[in,opt]*
List of text files to embed with the component.
*WHOLE_ARCHIVE[in,opt]*
Link the component as --whole-archive.
Register a new component with the build system using the provided options.
This function also automatically links all commonly required and managed
components to the component's target.
#]]
function(idf_component_register)
set(options WHOLE_ARCHIVE)
set(one_value KCONFIG KCONFIG_PROJBUILD)
set(multi_value SRCS SRC_DIRS EXCLUDE_SRCS
INCLUDE_DIRS PRIV_INCLUDE_DIRS LDFRAGMENTS REQUIRES
PRIV_REQUIRES REQUIRED_IDF_TARGETS EMBED_FILES EMBED_TXTFILES)
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
# Initialize and include commonly required components.
__init_common_components()
if(ARG_REQUIRED_IDF_TARGETS)
idf_build_get_property(idf_target IDF_TARGET)
if(NOT idf_target IN_LIST ARG_REQUIRED_IDF_TARGETS)
idf_die("Component ${COMPONENT_NAME} only supports targets: ${ARG_REQUIRED_IDF_TARGETS}")
endif()
endif()
if(ARG_KCONFIG)
get_filename_component(filename "${ARG_KCONFIG}" NAME)
string(TOLOWER "${filename}" filename)
if(NOT "${filename}" STREQUAL "kconfig")
idf_die("The component '${COMPONENT_NAME}' in the '${COMPONENT_DIR}' directory "
"is specifying '${ARG_KCONFIG}' as the 'KCONFIG' option, which is not "
"the default 'Kconfig' filename.")
endif()
endif()
if(ARG_KCONFIG_PROJBUILD)
get_filename_component(filename "${ARG_KCONFIG_PROJBUILD}" NAME)
string(TOLOWER "${filename}" filename)
if(NOT "${filename}" STREQUAL "kconfig.projbuild")
idf_die("The component '${COMPONENT_NAME}' in the '${COMPONENT_DIR}' directory "
"is specifying '${ARG_KCONFIG_PROJBUILD}' as the 'KCONFIG_PROJBUILD' option, "
"which is not the default 'Kconfig.projbuild' filename.")
endif()
endif()
__get_component_sources(SRCS "${ARG_SRCS}"
SRC_DIRS "${ARG_SRC_DIRS}"
EXCLUDE_SRCS "${ARG_EXCLUDE_SRCS}"
OUTPUT sources)
idf_build_get_property(include_directories INCLUDE_DIRECTORIES GENERATOR_EXPRESSION)
include_directories("${include_directories}")
idf_build_get_property(compile_definitions COMPILE_DEFINITIONS GENERATOR_EXPRESSION)
add_compile_definitions("${compile_definitions}")
__get_compile_options(OUTPUT compile_options)
add_compile_options("${compile_options}")
idf_build_get_property(common_component_interfaces __COMMON_COMPONENT_INTERFACES)
idf_component_get_property(component_interface "${COMPONENT_NAME}" COMPONENT_INTERFACE)
list(REMOVE_ITEM common_component_interfaces ${component_interface})
link_libraries(${common_component_interfaces})
__get_absolute_paths(PATHS "${ARG_INCLUDE_DIRS}" BASE_DIR "${COMPONENT_DIR}" OUTPUT include_dirs)
__get_absolute_paths(PATHS "${ARG_PRIV_INCLUDE_DIRS}" BASE_DIR "${COMPONENT_DIR}" OUTPUT priv_include_dirs)
foreach(dir IN LISTS include_dirs priv_include_dirs)
if(NOT IS_DIRECTORY ${dir})
idf_die("The component '${COMPONENT_NAME}' in the '${COMPONENT_DIR}' directory "
"is specifying '${dir}' as its include directory, but the directory does "
"not exists.")
endif()
endforeach()
if(sources OR ARG_EMBED_FILES OR ARG_EMBED_TXTFILES)
add_library("${COMPONENT_TARGET}" STATIC "${sources}")
foreach(include_dir IN LISTS include_dirs)
target_include_directories("${COMPONENT_TARGET}" PUBLIC "${include_dir}")
endforeach()
foreach(include_dir IN LISTS priv_include_dirs)
target_include_directories("${COMPONENT_TARGET}" PRIVATE "${include_dir}")
endforeach()
set_target_properties(${COMPONENT_TARGET} PROPERTIES OUTPUT_NAME ${COMPONENT_NAME} LINKER_LANGUAGE C)
set(component_type LIBRARY)
else()
add_library("${COMPONENT_TARGET}" INTERFACE)
foreach(include_dir IN LISTS include_dirs)
target_include_directories("${COMPONENT_TARGET}" INTERFACE "${include_dir}")
endforeach()
set(component_type CONFIG_ONLY)
endif()
foreach(req IN LISTS ARG_REQUIRES)
idf_component_include("${req}")
idf_component_get_property(req_interface "${req}" COMPONENT_INTERFACE)
if(${component_type} STREQUAL LIBRARY)
target_link_libraries("${COMPONENT_TARGET}" PUBLIC "${req_interface}")
else()
target_link_libraries("${COMPONENT_TARGET}" INTERFACE "${req_interface}")
endif()
endforeach()
foreach(req IN LISTS ARG_PRIV_REQUIRES)
idf_component_include("${req}")
idf_component_get_property(req_interface "${req}" COMPONENT_INTERFACE)
if(${component_type} STREQUAL CONFIG_ONLY)
continue()
endif()
target_link_libraries("${COMPONENT_TARGET}" PRIVATE "${req_interface}")
endforeach()
# Signal to idf_component_include that this component was included via the
# backward compatible idf_component_register function.
idf_component_set_property("${COMPONENT_NAME}" COMPONENT_FORMAT CMAKEV1)
# The handling of WHOLE_ARCHIVE linkage is managed within the
# idf_component_include function.
idf_component_set_property("${COMPONENT_NAME}" WHOLE_ARCHIVE "${ARG_WHOLE_ARCHIVE}")
idf_component_set_property("${COMPONENT_NAME}" SRCS "${sources}")
idf_component_set_property("${COMPONENT_NAME}" INCLUDE_DIRS "${ARG_INCLUDE_DIRS}")
idf_component_set_property("${COMPONENT_NAME}" PRIV_INCLUDE_DIRS "${ARG_PRIV_INCLUDE_DIRS}")
# The addition of ldgen fragment files is managed by the
# idf_component_include function.
idf_component_set_property("${COMPONENT_NAME}" LDFRAGMENTS "${ARG_LDFRAGMENTS}")
# Embedded files are managed in the idf_component_include function.
idf_component_set_property("${COMPONENT_NAME}" EMBED_FILES "${ARG_EMBED_FILES}")
idf_component_set_property("${COMPONENT_NAME}" EMBED_TXTFILES "${ARG_EMBED_TXTFILES}")
idf_component_set_property("${COMPONENT_NAME}" REQUIRES "${ARG_REQUIRES}")
idf_component_set_property("${COMPONENT_NAME}" PRIV_REQUIRES "${ARG_PRIV_REQUIRES}")
idf_component_set_property("${COMPONENT_NAME}" REQUIRED_IDF_TARGETS "${ARG_REQUIRED_IDF_TARGETS}")
idf_component_set_property("${COMPONENT_NAME}" COMPONENT_TYPE "${component_type}")
endfunction()
#[[
.. cmakev2:function:: idf_build_component
.. code-block:: cmake
idf_build_component(<component_dir> [<component_source>])
*component_dir[in]*
Directory path of the component.
*component_source[in,opt]*
Source of the component. One of:
* ``idf_components``
* ``project_managed_components``
* ``project_extra_components``
* ``project_components``
Defaults to ``project_components``.
Compatibility shim used by generated files (e.g. managed_components_list)
that initializes the component in build system v2 by delegating to
``__init_component``.
#]]
function(idf_build_component component_dir)
if(${ARGC} EQUAL 1)
set(component_source "project_components")
else()
set(component_source ${ARGV1})
endif()
# Get the component name and check if it's already initialized
get_filename_component(component_name "${component_dir}" NAME)
__get_component_interface(COMPONENT "${component_name}" OUTPUT existing_interface)
if(existing_interface)
# Component already exists, check if it's from the same directory and source
idf_component_get_property(existing_dir "${existing_interface}" COMPONENT_DIR)
idf_component_get_property(existing_source "${existing_interface}" COMPONENT_SOURCE)
# If same directory and same source, skip re-initialization
# This handles the case where component manager may output the same component
# multiple times due to path separator issues on Windows.
# TODO: Remove this workaround once idf-component-manager is fixed (IDF-14260)
if("${component_dir}" STREQUAL "${existing_dir}" AND
"${component_source}" STREQUAL "${existing_source}")
# Component already initialized from the same location, skip duplicate
return()
endif()
# Different directory or source, let __init_component handle the priority logic
endif()
idf_build_get_property(component_prefix PREFIX)
__init_component(DIRECTORY "${component_dir}"
PREFIX "${component_prefix}"
SOURCE "${component_source}")
endfunction()
File diff suppressed because it is too large Load Diff
+172
View File
@@ -0,0 +1,172 @@
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import re
from docutils import nodes # type: ignore
from docutils.statemachine import StringList # type: ignore
from sphinx import addnodes
from sphinx.addnodes import pending_xref
from sphinx.application import Sphinx
from sphinx.builders import Builder
from sphinx.directives import ObjDescT
from sphinx.directives import ObjectDescription
from sphinx.domains import Domain
from sphinx.environment import BuildEnvironment
from sphinx.roles import XRefRole
from sphinx.util.docutils import SphinxDirective
from sphinx.util.nodes import make_refnode
from sphinx.util.nodes import nested_parse_with_titles
RST_COMMENT_RE = re.compile(r'(?sm)^#\[\[api\n(.*?)\n#\]\]')
class CMakeV2IncludeDirective(SphinxDirective):
required_arguments = 1
def run(self) -> list:
env = self.env
rel_filename, filename = env.relfn2path(self.arguments[0])
# Track the file as a dependency so Sphinx rebuilds if it changes.
env.note_dependency(rel_filename)
# Read CMakeLists.txt file content.
with open(filename, encoding='utf-8') as f:
raw_content = f.read()
# Extract all RST comments from the file.
rst_comments = RST_COMMENT_RE.findall(raw_content)
parsed_nodes = []
for rst_comment in rst_comments:
# Temporary node to hold parsed comment.
node = nodes.section()
node.document = self.state.document
string_list = StringList(rst_comment.splitlines(), source='<embedded cmakev2>')
nested_parse_with_titles(self.state, string_list, node)
parsed_nodes.extend(node.children)
if not hasattr(env, 'cmakev2_comment_nodes'):
env.cmakev2_comment_nodes = {}
env.cmakev2_comment_nodes.setdefault(env.docname, []).extend(parsed_nodes)
return []
class CMakeV2Description(ObjectDescription):
def handle_signature(self, sig: str, signode: addnodes.desc_signature) -> ObjDescT:
signode += addnodes.desc_name(text=sig)
return sig
def add_target_and_index(self, name: ObjDescT, sig: str, signode: addnodes.desc_signature) -> None:
# Register object target only (no index)
labelid = f'cmakev2-{self.cmakev2_type}-{sig}'
signode['ids'].append(labelid)
self.env.domaindata['cmakev2']['xrefs'][sig] = (self.env.docname, labelid)
def run(self) -> list[nodes.Node]:
index, node = super().run()
# Add CMakeV2 custom attributes used when function, macro, and variable
# nodes are added to the doctree and sorted.
node['cmakev2-type'] = self.cmakev2_type
node['cmakev2-name'] = self.arguments[0]
return [node]
class CMakeV2VariableDirective(CMakeV2Description):
cmakev2_type = 'variable'
class CMakeV2FunctionDirective(CMakeV2Description):
cmakev2_type = 'function'
class CMakeV2MacroDirective(CMakeV2Description):
cmakev2_type = 'macro'
class CMakeV2Domain(Domain):
name = 'cmakev2'
label = 'ESP-IDF build system v2'
roles = {
'ref': XRefRole(),
}
directives = {
'variable': CMakeV2VariableDirective,
'function': CMakeV2FunctionDirective,
'macro': CMakeV2MacroDirective,
'include': CMakeV2IncludeDirective,
}
initial_data: dict = {
'xrefs': {},
}
def resolve_xref(
self,
env: BuildEnvironment,
fromdocname: str,
builder: Builder,
typ: str,
target: str,
node: pending_xref,
contnode: nodes.Element,
) -> nodes.Element | None:
xref = self.data['xrefs'].get(target)
if xref:
todocname, labelid = xref
# Extract clean text from contnode and wrap in non-literal node
text = contnode.astext()
newnode = nodes.emphasis(text, text)
return make_refnode(builder, fromdocname, todocname, labelid, newnode)
return None
def insert_cmakev2_comment_nodes(app: Sphinx, doctree: nodes.document) -> None:
env = app.builder.env
# Get nodes parsed and created by CMakeV2IncludeDirective.
pending = getattr(env, 'cmakev2_comment_nodes', {}).get(env.docname, [])
# Split nodes parsed in CMakeV2IncludeDirective into buckets based on their
# type.
buckets: dict = {'function': [], 'macro': [], 'variable': []}
for node in pending:
buckets[node['cmakev2-type']].append(node)
# Sort functions, macros, and variables based on their names.
buckets_sorted = {}
for bucket_name, bucket_list in buckets.items():
buckets_sorted[bucket_name] = sorted(bucket_list, key=lambda x: x['cmakev2-name'])
# Traverse through the doctree to locate the target section labels (such as
# _cmakev2_variables) and insert the nodes sorted into the appropriate
# section.
for section in doctree.traverse(nodes.section):
if 'cmakev2-variables' in section['ids']:
section.extend(buckets_sorted['variable'])
elif 'cmakev2-functions' in section['ids']:
section.extend(buckets_sorted['function'])
elif 'cmakev2-macros' in section['ids']:
section.extend(buckets_sorted['macro'])
def setup(app: Sphinx) -> dict:
app.add_domain(CMakeV2Domain)
# The nodes generated with CMakeV2IncludeDirective need to be placed into the
# proper sections (functions, macros, variables), but the section labels
# are not known during the directive run() method. Place the nodes into
# section in the doctree-read event.
app.connect('doctree-read', insert_cmakev2_comment_nodes)
return {
'version': '0.1',
'parallel_read_safe': True,
'parallel_write_safe': True,
}
+608
View File
@@ -0,0 +1,608 @@
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
include_guard(GLOBAL)
cmake_minimum_required(VERSION 3.22)
# Update the CMAKE_MODULE_PATH to ensure that additional cmakev2 build system
# modules can be included. The third_party directory from cmakev1 is also
# included for third-party modules shared with cmakev1.
set(CMAKE_MODULE_PATH
"${CMAKE_CURRENT_LIST_DIR}"
"${CMAKE_CURRENT_LIST_DIR}/../cmake/third_party"
${CMAKE_MODULE_PATH})
# The version.cmake file contains the IDF_VERSION variables, which are the same
# for both cmakev1 and cmakev2.
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/version.cmake)
# The gdbinit.cmake file from cmakev1 contains a single function,
# __generate_gdbinit, which is used in the generation of
# project_description.json.
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/gdbinit.cmake)
# The openocd.cmake file from cmakev1 contains a single function,
# __get_openocd_options, which is used in the generation of
# project_description.json.
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/openocd.cmake)
include(component)
include(build)
include(kconfig)
include(project)
include(manager)
include(compat)
include(ldgen)
include(GetGitRevisionDescription)
# For backward compatibility, since externalproject_add is used by
# project_include.cmake in the bootloader component. The ExternalProject
# should probably be included there instead.
include(ExternalProject)
#[[
__init_build_version()
Set the global variables IDF_BUILD_V2, IDF_BUILD_VER and IDF_BUILD_VER_TAG,
as well as the build properties and environmental variables.
#]]
function(__init_build_version)
set(IDF_BUILD_V2 y PARENT_SCOPE)
set(IDF_BUILD_VER 2 PARENT_SCOPE)
set(IDF_BUILD_VER_TAG "v2" PARENT_SCOPE)
idf_build_set_property(IDF_BUILD_V2 y)
idf_build_set_property(IDF_BUILD_VER 2)
idf_build_set_property(IDF_BUILD_VER_TAG "v2")
set(ENV{IDF_BUILD_V2} y)
set(ENV{IDF_BUILD_VER} 2)
set(ENV{IDF_BUILD_VER_TAG} "v2")
endfunction()
#[[
__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_git()
Determine the executable.
Set the GIT build property.
#]]
function(__init_git)
find_package(Git)
if(NOT GIT_FOUND)
idf_build_set_property(GIT NOTFOUND)
idf_warn("Git executable not found.")
return()
endif()
idf_build_set_property(GIT "${GIT_EXECUTABLE}")
endfunction()
#[[
__init_idf_version()
Determine the IDF version from the version.txt file. If it is not present,
use git-describe. If both previous attempts fail, use the IDF_VERSION from
the environment variables as a fallback.
Set IDF_VER build property.
#]]
function(__init_idf_version)
idf_build_get_property(idf_path IDF_PATH)
idf_build_get_property(git GIT)
if(EXISTS "${idf_path}/version.txt")
file(STRINGS "${idf_path}/version.txt" idf_ver)
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${idf_path}/version.txt")
else()
# Try to get the version using git-describe.
git_describe(idf_ver "${idf_path}" "--match=v*.*")
if(NOT idf_ver)
# The Git describe command failed unexpectedly, so the version is
# set to IDF_VERSION as specified in version.cmake.
set(idf_ver "$ENV{IDF_VERSION}")
endif()
endif()
# Trim IDF_VER to the necessary 32 characters.
string(SUBSTRING "${idf_ver}" 0 31 idf_ver)
idf_build_set_property(IDF_VER ${idf_ver})
endfunction()
#[[
__init_python()
Determine Python interpreter, either from the PYTHON CMake cache variable
or environmental variable or default it to "python".
If the PYTHON_DEPS_CHECKED CMake cache variable is not set, check if all
Python packages dependencies are satisfied. For instance, if a tool calling
CMake has already performed this check, it doesn't need to be repeated.
Set the global PYTHON variable, environment variable and build property.
#]]
function(__init_python)
__get_default_value(VARIABLE PYTHON
DEFAULT "python"
OUTPUT python)
file(TO_CMAKE_PATH ${python} python)
idf_build_set_property(PYTHON "${python}")
set(PYTHON "${python}" PARENT_SCOPE)
if(PYTHON_DEPS_CHECKED)
idf_dbg("Python dependencies have already been verified.")
return()
endif()
idf_build_get_property(idf_path IDF_PATH)
idf_msg("Checking Python dependencies...")
execute_process(
COMMAND "${python}" "${idf_path}/tools/idf_tools.py" "check-python-dependencies"
RESULT_VARIABLE result
)
if(result EQUAL 1)
# The function check_python_dependencies returns an error code of 1 if
# it fails.
idf_die("Some Python dependencies must be installed. Check above message for details.")
elseif(NOT result EQUAL 0)
# This means that check_python_dependencies.py failed to run entirely,
# and the result should be an error message.
idf_die("Failed to run Python dependency check. Python: ${python}, Error: ${result}")
endif()
endfunction()
#[[
__init_idf_target()
Determine the IDF_TARGET value from the IDF_TARGET environment variable,
the CMake cache variable, or the sdkconfig files. If none of these are set,
use the default esp32 target. Ensure there are no inconsistencies in the
IDF_TARGET values set in different locations.
Set the IDF_TARGET as a global variable, in the CMake cache, as an
environment variable, and as a build property.
#]]
function(__init_idf_target)
set(sdkconfig_target "")
set(target "")
set(sdkconfig_file "")
idf_build_get_property(sdkconfig SDKCONFIG)
idf_build_get_property(sdkconfig_defaults SDKCONFIG_DEFAULTS)
foreach(config ${sdkconfig} ${sdkconfig_defaults})
idf_dbg("Searching for target in '${config}'")
__get_sdkconfig_option(OPTION CONFIG_IDF_TARGET
SDKCONFIG "${config}"
OUTPUT sdkconfig_target)
if(sdkconfig_target)
set(sdkconfig_file "${config}")
break()
endif()
endforeach()
__get_default_value(VARIABLE IDF_TARGET
DEFAULT NOTFOUND
OUTPUT target)
if(NOT target)
if(sdkconfig_target)
idf_msg("IDF_TARGET is not set, guessed '${sdkconfig_target}' "
"from sdkconfig '${sdkconfig_file}'")
set(target "${sdkconfig_target}")
else()
idf_msg("IDF_TARGET not set, using default target: esp32")
set(target "esp32")
endif()
endif()
# Verify that the chosen target aligns with the CMake cache.
set(cache_target $CACHE{IDF_TARGET})
if(cache_target)
if(NOT "${cache_target}" STREQUAL "${target}")
idf_die("IDF_TARGET '${cache_target}' in CMake cache does not match "
"currently selected IDF_TARGET '${target}'. "
"To change the target, clear the build directory and sdkconfig file, "
"and build the project again.")
endif()
endif()
# Verify that the chosen target aligns with the sdkconfig.
if(sdkconfig_target AND "${sdkconfig_file}" STREQUAL "${sdkconfig}")
if("$ENV{_IDF_PY_SET_TARGET_ACTION}" STREQUAL "1")
idf_dbg("The target consistency check for the target specified in ${sdkconfig} "
"was skipped because the set-target action is being executed.")
elseif(NOT "${sdkconfig_target}" STREQUAL "${target}")
idf_die("Target '${sdkconfig_target}' in sdkconfig '${sdkconfig}' "
"does not match currently selected IDF_TARGET '${target}'. "
"To change the target, clear the build directory and sdkconfig file, "
"and build the project again.")
endif()
endif()
idf_build_set_property(IDF_TARGET "${target}")
set(ENV{IDF_TARGET} ${target})
set(IDF_TARGET ${target} CACHE STRING "IDF Build Target")
endfunction()
#[[
__init_toolchain()
Determine the IDF_TOOLCHAIN value from the IDF_TOOLCHAIN environment
variable or the CMake cache variable. If none of these are set, use the
default gcc toolchain. Ensure there are no inconsistencies in the
IDF_TOOLCHAIN values set in different locations. Also ensure that the
CMAKE_TOOLCHAIN_FILE is set to the correct file according to the current
IDF_TARGET.
Set the IDF_TOOLCHAIN and IDF_TOOLCHAIN_FILE build properties. Also,
configure the IDF_TOOLCHAIN CMake cache variable and set the
CMAKE_TOOLCHAIN_FILE global variable.
#]]
function(__init_toolchain)
set(cache_toolchain $CACHE{IDF_TOOLCHAIN})
set(cache_toolchain_file $CACHE{CMAKE_TOOLCHAIN_FILE})
idf_build_get_property(idf_path IDF_PATH)
idf_build_get_property(idf_target IDF_TARGET)
__get_default_value(VARIABLE IDF_TOOLCHAIN
DEFAULT gcc
OUTPUT toolchain)
if(cache_toolchain)
if(NOT "${cache_toolchain}" STREQUAL "${toolchain}")
idf_die("IDF_TOOLCHAIN '${cache_toolchain}' in CMake cache does not match "
"currently selected IDF_TOOLCHAIN '${toolchain}'. To change "
"the toolchain, clear the build directory and sdkconfig file, "
"and build the project again.")
endif()
endif()
if("${toolchain}" STREQUAL "clang")
set(toolchain_type "clang-")
endif()
# Check that the selected target is consistent with the toolchain file in
# the CMake cache.
if(cache_toolchain_file)
string(FIND "${cache_toolchain_file}" "-${toolchain_type}${idf_target}.cmake" found)
if(${found} EQUAL -1)
get_filename_component(cache_toolchain_file_stem "${cache_toolchain_file}" NAME_WE)
idf_die("CMAKE_TOOLCHAIN_FILE '${cache_toolchain_file_stem}' "
"does not match currently selected IDF_TARGET '${idf_target}'. "
"To change the target, clear the build directory and sdkconfig file, "
"and build the project again.")
endif()
endif()
set(toolchain_file "${idf_path}/tools/cmake/toolchain-${toolchain_type}${idf_target}.cmake")
if(NOT EXISTS ${toolchain_file})
idf_die("Toolchain file ${toolchain_file} not found")
endif()
set(IDF_TOOLCHAIN ${toolchain} CACHE STRING "IDF Build Toolchain Type")
set(CMAKE_TOOLCHAIN_FILE "${toolchain_file}" PARENT_SCOPE)
idf_build_set_property(IDF_TOOLCHAIN "${toolchain}")
idf_build_set_property(IDF_TOOLCHAIN_FILE "${toolchain_file}")
endfunction()
#[[
__init_ccache()
Enable ccache if requested through CCACHE_ENABLE.
#]]
function(__init_ccache)
if(NOT CCACHE_ENABLE)
return()
endif()
find_program(CCACHE_FOUND ccache)
if(CCACHE_FOUND)
idf_msg("ccache will be used for faster recompilation")
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
else()
idf_warn("enabled ccache in build but ccache program not found")
endif()
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()
#[[
__init_submodules()
Initialize submodules that are not yet initialized, and issue a warning for
submodules that do not match the recorded hash in the git tree.
#]]
function(__init_submodules)
idf_build_get_property(idf_path IDF_PATH)
idf_build_get_property(git GIT)
# For internal use: Skip the submodule check if running on GitLab CI
# and the job is configured to not clone submodules.
if("$ENV{IDF_SKIP_CHECK_SUBMODULES}" STREQUAL "1")
idf_msg("skip submodule check on internal CI")
return()
endif()
if(NOT git)
idf_warn("Git executable was not found. Git submodule checks will not be executed. "
"If you have any build issues at all, start by adding git executable to "
"the PATH and rerun cmake to not see this warning again.")
return()
endif()
execute_process(
COMMAND ${git} submodule status
WORKING_DIRECTORY ${idf_path}
OUTPUT_VARIABLE status
ERROR_VARIABLE stderr
RESULT_VARIABLE rv
)
if(rv)
idf_warn("Git submodule status command failed(${rv}): ${stderr}"
"Git submodule checks will not be performed. ")
return()
endif()
__split(STRING "${status}"
OUTPUT lines
REMOVE_EMPTY)
# The output of the git submodule status command is not guaranteed to be
# stable. It may be necessary to check the GIT_VERSION_STRING and make
# adjustments in the future.
foreach(line IN LISTS lines)
string(REGEX MATCH "(.)[0-9a-f]+ ([^\( ]+) ?" _ignored "${line}")
set(status "${CMAKE_MATCH_1}")
set(submodule_path "${CMAKE_MATCH_2}")
if("${status}" STREQUAL "-") # missing submodule
idf_msg("Initialising new submodule ${submodule_path}...")
execute_process(
COMMAND ${git} submodule update --init --recursive ${submodule_path}
WORKING_DIRECTORY ${idf_path}
ERROR_VARIABLE stderr
RESULT_VARIABLE rv
)
if(rv)
idf_die("Git submodule '${submodule_path}' init failed(${rv}): ${stderr}")
endif()
elseif(NOT "${status}" STREQUAL " ")
idf_warn("Git submodule ${submodule_path} is out of date. "
"Run the following command to fix: "
"git submodule update --init --recursive")
endif()
# Ensure CMake is rerun if the submodule's .git file is modified or
# altered, such as in the case of an accidental deinitialization.
get_filename_component(submodule_abs_path ${submodule_path} ABSOLUTE BASE_DIR ${idf_path})
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${submodule_abs_path}/.git)
# If the HEAD file in the submodule's directory changes (i.e., if there
# are commit changes), it will at least display the 'out of date'
# warning.
set(submodule_head "${idf_path}/.git/modules/${submodule_path}/HEAD")
if(EXISTS "${submodule_head}")
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${submodule_head})
endif()
endforeach()
endfunction()
#[[
__init_idf_target_arch()
Set the IDF_TARGET_ARCH value based on the sdkconfig. This means it must be
initialized after the sdkconfig is generated and its CMake version is
included.
#]]
function(__init_idf_target_arch)
if(CONFIG_IDF_TARGET_ARCH_XTENSA)
idf_build_set_property(IDF_TARGET_ARCH "xtensa")
elseif(CONFIG_IDF_TARGET_ARCH_RISCV)
idf_build_set_property(IDF_TARGET_ARCH "riscv")
else()
# Currently, no architecture is specified for Linux host builds.
idf_build_set_property(IDF_TARGET_ARCH "")
endif()
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.
COMPONENTS_INCLUDED
This is a list of component names that were included in the build,
meaning their CMakeLists.txt files were processed with an
add_subdirectory call. Each component is evaluated exactly once, and
this list serves as a record of which components have already been
evaluated. Although each component can only be evaluated once, it can
be used in multiple idf_component_include calls. If a component is
requested to be included a second time, this list is checked. If the
component is already included, the idf_component_include function
simply returns, as there is nothing further to do except add a new
alias target if requested.
#]]
add_library(idf_build_properties INTERFACE)
# The __idf_component_interface_cache target is used to maintain internal
# mappings between component identifiers, such as component name or alias, and
# the component interface target, which is the primary target for the
# component.
add_library(__idf_component_interface_cache INTERFACE)
# Set build system prefix for component targets.
idf_build_set_property(PREFIX "idf")
# Set project directory property.
idf_build_set_property(PROJECT_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
# Set build directory property.
idf_build_set_property(BUILD_DIR "${CMAKE_BINARY_DIR}")
# Enable the generation of compile_commands.json.
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Initialize build system version.
__init_build_version()
# Initialize IDF_PATH and set it as a global and environmental variable, as
# well as a build property.
__init_idf_path()
# Determine git executable and set GIT build property.
__init_git()
# Initialize git submodules.
__init_submodules()
# Initialize the IDF_VER build property.
__init_idf_version()
# Determine the Python interpreter and check package dependencies if necessary.
__init_python()
# Initialize Kconfig system infrastructure.
__init_kconfig()
# Set IDF_TARGET.
__init_idf_target()
# Set IDF_TOOLCHAIN, IDF_TOOLCHAIN_FILE and CMAKE_TOOLCHAIN_FILE.
__init_toolchain()
# Enable ccache if requested.
__init_ccache()
#[[
At this point, the build system infrastructure is ready.
Project-specific operations (component discovery, Kconfig generation,
component manager, etc.) are handled in idf_project_init() after the
project() call.
#]]
+836
View File
@@ -0,0 +1,836 @@
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
# Kconfig utilities for ESP-IDF CMake build system v2
# This module provides functions for Kconfig processing and sdkconfig generation
include_guard(GLOBAL)
include(utilities)
include(build)
include(component)
#[[
__init_kconfig()
Initialize the Kconfig system infrastructure for the build. This function
performs the Kconfig initialization and setup by:
1. Setting up SDKCONFIG and SDKCONFIG_DEFAULTS build properties
2. Setting up ESP-IDF root Kconfig files and config directory
3. Creating the default config directory
This should be called after component discovery but before component
manager.
Note: Regular component Kconfig files are collected during component
discovery.
#]]
function(__init_kconfig)
idf_build_get_property(idf_path IDF_PATH)
# Initialize SDKCONFIG and SDKCONFIG_DEFAULTS build properties using environment
# variables, CMake cache variables, or default values.
if(EXISTS "${CMAKE_SOURCE_DIR}/sdkconfig.defaults")
set(sdkconfig_defaults "${CMAKE_SOURCE_DIR}/sdkconfig.defaults")
else()
set(sdkconfig_defaults "")
endif()
__get_default_value(VARIABLE SDKCONFIG
DEFAULT "${CMAKE_SOURCE_DIR}/sdkconfig"
OUTPUT sdkconfig)
__get_default_value(VARIABLE SDKCONFIG_DEFAULTS
DEFAULT "${sdkconfig_defaults}"
OUTPUT sdkconfig_defaults)
__get_absolute_paths(PATHS "${sdkconfig}" OUTPUT sdkconfig)
__get_absolute_paths(PATHS "${sdkconfig_defaults}" OUTPUT sdkconfig_defaults)
set(sdkconfig_defaults_checked "")
foreach(sdkconfig_default ${sdkconfig_defaults})
if(NOT EXISTS "${sdkconfig_default}")
idf_die("SDKCONFIG_DEFAULTS '${sdkconfig_default}' does not exist.")
endif()
list(APPEND sdkconfig_defaults_checked ${sdkconfig_default})
endforeach()
idf_build_set_property(SDKCONFIG "${sdkconfig}")
idf_build_set_property(SDKCONFIG_DEFAULTS "${sdkconfig_defaults_checked}")
# Setup ESP-IDF root Kconfig and sdkconfig.rename files.
idf_build_set_property(__ROOT_KCONFIG "${idf_path}/Kconfig")
idf_build_set_property(__ROOT_SDKCONFIG_RENAME "${idf_path}/sdkconfig.rename")
# Setup and create the default config directory.
idf_build_get_property(build_dir BUILD_DIR)
set(config_dir "${build_dir}/config")
file(MAKE_DIRECTORY "${config_dir}")
idf_build_set_property(CONFIG_DIR "${config_dir}")
endfunction()
#[[
__generate_sdkconfig()
This function performs the complete Kconfig generation process:
1. Collect Kconfig files from discovered components
2. Collect Kconfig files from bootloader components
3. Set up the Kconfig environment with all collected files
4. Generate all output files (sdkconfig.h, sdkconfig.cmake, etc.)
#]]
function(__generate_sdkconfig)
idf_msg("Generating sdkconfig configuration...")
# Collect Kconfig files from discovered components
__consolidate_component_kconfig_files()
# Collect Kconfig files from bootloader components
__collect_kconfig_files_from_bootloader_components()
# Prepare Kconfig environment using collected files
__setup_kconfig_environment()
# Generate Kconfig outputs
__generate_kconfig_outputs()
idf_msg("Generated sdkconfig configuration")
endfunction()
# =============================================================================
# KCONFIG COLLECTION FUNCTIONS
# =============================================================================
#[[
__consolidate_component_kconfig_files()
Consolidate Kconfig files from discovered components into global build
properties. This scans the COMPONENTS_DISCOVERED build property and for
each component, retrieves its Kconfig files from component properties and
adds them to the global __KCONFIGS, __KCONFIG_PROJBUILDS, and
__SDKCONFIG_RENAMES build properties.
#]]
function(__consolidate_component_kconfig_files)
idf_build_get_property(components_discovered COMPONENTS_DISCOVERED)
if(NOT components_discovered)
idf_die("No components discovered. This must be run after component discovery.")
endif()
# Clean the build properties before adding new ones. This ensures that
# we don't have duplicate Kconfig files in the build properties.
idf_build_set_property(__KCONFIGS "")
idf_build_set_property(__KCONFIG_PROJBUILDS "")
idf_build_set_property(__SDKCONFIG_RENAMES "")
# Iterate through all discovered components and consolidate their Kconfig files
foreach(component_name IN LISTS components_discovered)
# Get Kconfig files from component properties
idf_component_get_property(component_kconfig "${component_name}" __KCONFIG)
idf_component_get_property(component_projbuild "${component_name}" __KCONFIG_PROJBUILD)
idf_component_get_property(component_rename "${component_name}" __SDKCONFIG_RENAME)
if(component_kconfig)
idf_build_set_property(__KCONFIGS "${component_kconfig}" APPEND)
endif()
if(component_projbuild)
idf_build_set_property(__KCONFIG_PROJBUILDS "${component_projbuild}" APPEND)
endif()
if(component_rename)
idf_build_set_property(__SDKCONFIG_RENAMES "${component_rename}" APPEND)
endif()
endforeach()
endfunction()
#[[
__collect_kconfig_files_from_bootloader_components()
Collect Kconfig files from bootloader components and store them in build
properties. Bootloader components are located in the bootloader_components
directory of the project. This function only runs if the bootloader
component is discovered.
#]]
function(__collect_kconfig_files_from_bootloader_components)
# Check if bootloader component is discovered - only then collect bootloader components
idf_build_get_property(components_discovered COMPONENTS_DISCOVERED)
if(NOT "bootloader" IN_LIST components_discovered)
return()
endif()
idf_build_get_property(idf_target IDF_TARGET)
idf_build_get_property(project_dir PROJECT_DIR)
# Find bootloader component directories
__get_component_paths(PATHS "${project_dir}/bootloader_components"
OUTPUT bootloader_component_dirs)
foreach(bootloader_component_dir ${bootloader_component_dirs})
__collect_kconfig_files_from_directory("${bootloader_component_dir}" "${idf_target}"
bootloader_kconfig bootloader_projbuild bootloader_rename)
# Set build properties. Bootloader components are only evaluated for Kconfig files at this stage.
if(bootloader_kconfig)
idf_build_set_property(__KCONFIGS "${bootloader_kconfig}" APPEND)
endif()
if(bootloader_projbuild)
idf_build_set_property(__KCONFIG_PROJBUILDS "${bootloader_projbuild}" APPEND)
endif()
if(bootloader_rename)
idf_build_set_property(__SDKCONFIG_RENAMES "${bootloader_rename}" APPEND)
endif()
endforeach()
endfunction()
#[[
__collect_kconfig_files_from_directory(directory target out_kconfigs out_projbuilds out_renames)
Collect Kconfig files from a single directory.
*directory[in]*
Path to the directory to collect Kconfig files from.
*target[in]*
Target name for target-specific files.
*out_kconfigs[out]*
List of Kconfig files.
*out_projbuilds[out]*
List of projbuild files.
*out_renames[out]*
List of rename files.
#]]
function(__collect_kconfig_files_from_directory directory target out_kconfigs out_projbuilds out_renames)
file(GLOB all_files "${directory}/*")
set(kconfig_files "")
set(projbuild_files "")
set(rename_files "")
foreach(file IN LISTS all_files)
get_filename_component(filename "${file}" NAME)
string(TOLOWER "${filename}" filename_lower)
# Check for Kconfig file
if(filename_lower STREQUAL "kconfig")
list(APPEND kconfig_files "${file}")
if(NOT filename STREQUAL "Kconfig")
idf_warn("${filename} file should be named 'Kconfig' (uppercase K, rest lowercase).
Full path to the file: ${file}")
endif()
# Check for Kconfig.projbuild file
elseif(filename_lower STREQUAL "kconfig.projbuild")
list(APPEND projbuild_files "${file}")
if(NOT filename STREQUAL "Kconfig.projbuild")
idf_warn("${filename} file should be named 'Kconfig.projbuild' (uppercase K, rest lowercase).
Full path to the file: ${file}")
endif()
endif()
# Check for sdkconfig.rename files
if(filename_lower STREQUAL "sdkconfig.rename")
list(APPEND rename_files "${file}")
endif()
# Look for target-specific sdkconfig.rename files
if(target)
if(filename_lower STREQUAL "sdkconfig.rename.${target}")
list(APPEND rename_files "${file}")
endif()
endif()
endforeach()
list(SORT kconfig_files)
list(SORT projbuild_files)
list(SORT rename_files)
set(${out_kconfigs} "${kconfig_files}" PARENT_SCOPE)
set(${out_projbuilds} "${projbuild_files}" PARENT_SCOPE)
set(${out_renames} "${rename_files}" PARENT_SCOPE)
endfunction()
# =============================================================================
# KCONFIG ENVIRONMENT FUNCTIONS
# =============================================================================
#[[
__setup_kconfig_environment()
Setup the Kconfig environment for kconfgen. This function creates the
environment and prepares Kconfig source files.
#]]
function(__setup_kconfig_environment)
# Create the config.env file, which contains all environment variables for the python script
idf_build_get_property(build_dir BUILD_DIR)
set(config_env_path "${build_dir}/config.env")
__create_config_env_file("${config_env_path}")
# Store environment path in build properties (used by kconfgen)
idf_build_set_property(__CONFIG_ENV_PATH "${config_env_path}")
# Now prepare Kconfig source files using the prepare_kconfig_files.py script
idf_build_get_property(python PYTHON)
idf_build_get_property(idf_path IDF_PATH)
set(prepare_cmd ${python} "${idf_path}/tools/kconfig_new/prepare_kconfig_files.py"
--list-separator=semicolon
--env-file "${config_env_path}")
idf_build_set_property(__PREPARE_KCONFIG_CMD "${prepare_cmd}")
idf_dbg("Preparing Kconfig source files: ${prepare_cmd}")
execute_process(
COMMAND ${prepare_cmd}
RESULT_VARIABLE prepare_result
)
if(prepare_result)
idf_die("Failed to prepare Kconfig source files: ${prepare_result}")
endif()
endfunction()
#[[
__create_executable_config_env_file(executable)
*executable[in]*
Executable target for which to generate ``config.env`` file.
Generate the ``config.env`` file for the specified ``executable``. The
configuration file will be stored in the build directory, within a
directory named after the ``executable`` target name. The ``kconfigs*.in``
files, which are generated by ``prepare_kconfig_files.py``, will also be
stored in this directory.
This function primarily prepares the arguments for the
``__create_config_env_file`` function based on the components linked to the
``executable``, ensuring that component Kconfig files are stored in the
appropriate ``kconfigs*.in`` files, depending on whether the component is
linked to the executable.
The directory where the generated files are stored is added to the
``executable`` ``CONFIG_ENV_DIR`` property.
#]]
function(__create_executable_config_env_file executable)
get_target_property(config_env_dir "${executable}" CONFIG_ENV_DIR)
if(config_env_dir)
return()
endif()
__get_executable_library_or_die(TARGET "${executable}" OUTPUT library)
idf_library_get_property(components_linked "${library}" LIBRARY_COMPONENTS_LINKED)
set(kconfigs "")
set(kconfigs_projbuild "")
set(kconfigs_excluded "")
set(kconfigs_projbuild_excluded "")
idf_build_get_property(components_discovered COMPONENTS_DISCOVERED)
foreach(component_name IN LISTS components_discovered)
idf_component_get_property(component_kconfig "${component_name}" __KCONFIG)
idf_component_get_property(component_projbuild "${component_name}" __KCONFIG_PROJBUILD)
if(component_kconfig)
if("${component_name}" IN_LIST components_linked)
list(APPEND kconfigs "${component_kconfig}")
else()
list(APPEND kconfigs_excluded "${component_kconfig}")
endif()
endif()
if(component_projbuild)
if("${component_name}" IN_LIST components_linked)
list(APPEND kconfigs_projbuild "${component_projbuild}")
else()
list(APPEND kconfigs_projbuild_excluded "${component_projbuild}")
endif()
endif()
endforeach()
string(REGEX REPLACE "[^A-Za-z0-9_]" "_" executable_sanitized "${executable}")
idf_build_get_property(build_dir BUILD_DIR)
set(kconfigs_dir "${build_dir}/${executable_sanitized}")
set(kconfigs_path "${kconfigs_dir}/kconfigs.in")
set(kconfigs_projbuild_path "${kconfigs_dir}/kconfigs_projbuild.in")
set(kconfigs_excluded_path "${kconfigs_dir}/kconfigs_excluded.in")
set(kconfigs_projbuild_excluded_path "${kconfigs_dir}/kconfigs_projbuild_excluded.in")
set(config_env_path "${kconfigs_dir}/config.env")
__create_config_env_file("${config_env_path}"
KCONFIGS "${kconfigs}"
KCONFIGS_PROJBUILD "${kconfigs_projbuild}"
KCONFIGS_EXCLUDED "${kconfigs_excluded}"
KCONFIGS_PROJBUILD_EXCLUDED "${kconfigs_projbuild_excluded}")
set_target_properties("${executable}" PROPERTIES CONFIG_ENV_DIR "${kconfigs_dir}")
endfunction()
#[[
__create_config_env_file(config_env_path
[KCONFIGS <kconfig>...]
[KCONFIGS_PROJBUILD <projbuild>...]
[KCONFIGS_EXCLUDED <kconfig>...]
[KCONFIGS_PROJBUILD_EXCLUDED <projbuild>...])
*config_env_path[in]*
Path for the generated configuration environment file.
*KCONFIGS[in,opt]*
Kconfig file or list of Kconfig files to be sourced in the
``kconfigs.in`` file. Can be used multiple times. If not provided, the
Kconfig files stored in the ``__KCONFIGS`` build property are used.
*KCONFIGS_PROJBUILD[in,opt]*
Kconfig file or list of Kconfig files to be sourced in the
``kconfigs_projbuild.in`` file. Can be used multiple times. If not
provided, the Kconfig files stored in the ``__KCONFIGS`` build property
are used.
*KCONFIGS_EXCLUDED[in,opt]*
Kconfig file or list of Kconfig files to be sourced in the
``kconfigs_excluded.in`` file. Can be used multiple times.
*KCONFIGS_PROJBUILD_EXCLUDED[in,opt]*
Kconfig file or list of Kconfig files to be sourced in the
``kconfigs_projbuild_excluded.in`` file. Can be used multiple times.
Generate a configuration file for the ``prepare_kconfig_files.py`` script
at ``config_env_path``. This file includes, among other details, the paths
where ``prepare_kconfig_files.py`` should generate the ``kconfigs*.in``
files. These are Kconfig files, sourced in the main Kconfig file through
environmental variables, and contain a list of component configurations
that should be sourced. The ``kconfigs*.in`` file paths in the generated
configuration file are located in the same directory as the configuration
file itself. This means that ``prepare_kconfig_files.py`` will produce the
``kconfigs*.in`` files in the same directory where the configuration file
created by this function is generated.
#]]
function(__create_config_env_file config_env_path)
set(options)
set(one_value)
set(multi_value KCONFIGS KCONFIGS_PROJBUILD KCONFIGS_EXCLUDED KCONFIGS_PROJBUILD_EXCLUDED)
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
# Get all necessary build properties
idf_build_get_property(idf_path IDF_PATH)
idf_build_get_property(build_dir BUILD_DIR)
idf_build_get_property(kconfigs __KCONFIGS)
idf_build_get_property(projbuilds __KCONFIG_PROJBUILDS)
idf_build_get_property(renames __SDKCONFIG_RENAMES)
idf_build_get_property(target IDF_TARGET)
idf_build_get_property(toolchain IDF_TOOLCHAIN)
idf_build_get_property(sdkconfig SDKCONFIG)
if(ARG_KCONFIGS)
set(kconfigs "${ARG_KCONFIGS}")
endif()
if(ARG_KCONFIGS_PROJBUILD)
set(projbuilds "${ARG_KCONFIGS_PROJBUILD}")
endif()
set(kconfigs_excluded "")
if(ARG_KCONFIGS_EXCLUDED)
set(kconfigs_excluded "${ARG_KCONFIGS_EXCLUDED}")
endif()
set(kconfigs_projbuild_excluded "")
if(ARG_KCONFIGS_PROJBUILD_EXCLUDED)
set(kconfigs_projbuild_excluded "${ARG_KCONFIGS_PROJBUILD_EXCLUDED}")
endif()
# Get IDF version info
__get_sdkconfig_option(OPTION CONFIG_IDF_INIT_VERSION
SDKCONFIG "${sdkconfig}"
OUTPUT idf_init_version)
if(NOT idf_init_version)
set(idf_init_version "$ENV{IDF_VERSION}")
endif()
set(ENV{IDF_INIT_VERSION} "${idf_init_version}")
idf_build_set_property(__IDF_INIT_VERSION "${idf_init_version}")
# Get IDF_ENV_FPGA from the environment
set(idf_env_fpga "$ENV{IDF_ENV_FPGA}")
if(NOT idf_env_fpga)
set(idf_env_fpga "")
endif()
idf_build_set_property(__IDF_ENV_FPGA "${idf_env_fpga}")
# Get the config.env.in template path
set(template_path "${idf_path}/tools/kconfig_new/config_buildv2.env.in")
if(NOT EXISTS "${template_path}")
idf_die("Kconfig environment template file not found at ${template_path}")
endif()
get_filename_component(config_env_dir "${config_env_path}" DIRECTORY)
# Set up variables for the config.env.in template
set(kconfigs "${kconfigs}")
set(kconfig_projbuilds "${projbuilds}")
set(kconfigs_excluded "${kconfigs_excluded}")
set(kconfigs_projbuild_excluded "${kconfigs_projbuild_excluded}")
set(sdkconfig_renames "${renames}")
set(idf_target "${target}")
set(idf_toolchain "${toolchain}")
set(idf_path "${idf_path}")
set(kconfigs_path "${config_env_dir}/kconfigs.in")
set(kconfigs_projbuild_path "${config_env_dir}/kconfigs_projbuild.in")
set(kconfigs_excluded_path "${config_env_dir}/kconfigs_excluded.in")
set(kconfigs_projbuild_excluded_path "${config_env_dir}/kconfigs_projbuild_excluded.in")
# Generate the config.env file from the config.env.in template
configure_file("${template_path}" "${config_env_path}")
idf_dbg("Created config environment file: ${config_env_path}")
endfunction()
# =============================================================================
# KCONFIG OUTPUT GENERATION FUNCTIONS
# =============================================================================
#[[
__generate_kconfig_outputs()
Generate all Kconfig output files.
Generates: sdkconfig.h, sdkconfig.cmake, sdkconfig.json, kconfig_menus.json
Must be called after __setup_kconfig_environment().
#]]
function(__generate_kconfig_outputs)
# Get inputs from build properties
idf_build_get_property(sdkconfig SDKCONFIG)
idf_build_get_property(sdkconfig_defaults SDKCONFIG_DEFAULTS)
idf_msg("Project sdkconfig file ${sdkconfig}")
# Set up output paths
idf_build_get_property(config_dir CONFIG_DIR)
if(NOT config_dir)
idf_die("Kconfig directory not created. Call __init_kconfig() first.")
endif()
set(sdkconfig_header "${config_dir}/sdkconfig.h")
set(sdkconfig_cmake "${config_dir}/sdkconfig.cmake")
set(sdkconfig_json "${config_dir}/sdkconfig.json")
# Create and store base kconfgen command for this generation and target reuse
__create_base_kconfgen_command("${sdkconfig}" "${sdkconfig_defaults}")
# Create and store common kconfgen outputs command
set(kconfgen_outputs_cmd
--output header "${sdkconfig_header}"
--output cmake "${sdkconfig_cmake}"
--output json "${sdkconfig_json}"
--output config "${sdkconfig}"
)
idf_build_set_property(__KCONFGEN_OUTPUTS_CMD "${kconfgen_outputs_cmd}")
# Generate Kconfig outputs using kconfgen
__run_kconfgen()
# Add the generated config header to build specifications
idf_build_set_property(INCLUDE_DIRECTORIES "${config_dir}" APPEND)
# Set up file dependencies for CMake reconfiguration
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${sdkconfig}")
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${sdkconfig_header}")
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${sdkconfig_cmake}")
# Add dependency on kconfgen tool
idf_build_get_property(idf_path IDF_PATH)
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${idf_path}/tools/kconfig_new/confgen.py")
# Set up clean files
set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" APPEND PROPERTY
ADDITIONAL_CLEAN_FILES "${sdkconfig_header}" "${sdkconfig_cmake}")
# Store output paths in build properties
idf_build_set_property(__SDKCONFIG_HEADER "${sdkconfig_header}")
idf_build_set_property(__SDKCONFIG_CMAKE "${sdkconfig_cmake}")
idf_build_set_property(__SDKCONFIG_JSON "${sdkconfig_json}")
idf_build_set_property(__SDKCONFIG_JSON_MENUS "${sdkconfig_json_menus}")
idf_msg("Generated Kconfig outputs in ${config_dir}")
endfunction()
#[[
__create_base_kconfgen_command(sdkconfig sdkconfig_defaults)
*sdkconfig[in]*
Path to sdkconfig file.
*sdkconfig_defaults[in]*
List of sdkconfig defaults files.
Create the base kconfgen command and store it as a build property for
reuse.
#]]
function(__create_base_kconfgen_command sdkconfig sdkconfig_defaults)
# Get all necessary properties for base command
idf_build_get_property(python PYTHON)
idf_build_get_property(root_kconfig __ROOT_KCONFIG)
idf_build_get_property(root_sdkconfig_rename __ROOT_SDKCONFIG_RENAME)
idf_build_get_property(target IDF_TARGET)
# Set up defaults arguments
set(defaults_args "")
if(sdkconfig_defaults)
foreach(default_file IN LISTS sdkconfig_defaults)
list(APPEND defaults_args "--defaults" "${default_file}")
if(EXISTS "${default_file}.${target}")
list(APPEND defaults_args "--defaults" "${default_file}.${target}")
endif()
endforeach()
endif()
# Create base kconfgen command
set(base_kconfgen_cmd ${python} -m kconfgen
--list-separator=semicolon
--kconfig "${root_kconfig}"
--sdkconfig-rename "${root_sdkconfig_rename}"
--config "${sdkconfig}"
${defaults_args}
--env "IDF_BUILD_V2=y")
# Store base command as a build property
idf_build_set_property(__BASE_KCONFGEN_CMD "${base_kconfgen_cmd}")
endfunction()
#[[
__run_kconfgen()
Run kconfgen and generate all output files using the stored base command.
Assumes that the base command is already created by
__create_base_kconfgen_command().
#]]
function(__run_kconfgen)
idf_build_get_property(base_kconfgen_cmd __BASE_KCONFGEN_CMD)
idf_build_get_property(kconfgen_outputs_cmd __KCONFGEN_OUTPUTS_CMD)
idf_build_get_property(sdkconfig SDKCONFIG)
idf_build_get_property(config_env_path __CONFIG_ENV_PATH)
# Create full command with output file paths
set(kconfgen_cmd ${base_kconfgen_cmd} ${kconfgen_outputs_cmd})
idf_dbg("Running kconfgen: ${kconfgen_cmd}")
execute_process(
COMMAND ${kconfgen_cmd}
--env-file "${config_env_path}"
RESULT_VARIABLE kconfgen_result
)
if(kconfgen_result)
idf_die("Failed to run kconfgen: ${kconfgen_result}")
endif()
endfunction()
# =============================================================================
# KCONFIG TARGETS FUNCTIONS
# =============================================================================
#[[
.. cmakev2:function:: idf_create_menuconfig
.. code-block:: cmake
idf_create_menuconfig(<executable>
TARGET <target>)
*executable[in]*
Executable target for which to create the menuconfig target.
*TARGET[in]*
Name of the menuconfig target to be created.
Create a menuconfig target with the name specified by the ``TARGET``
option for an ``executable``.
#]]
function(idf_create_menuconfig executable)
set(options)
set(one_value TARGET)
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(TARGET "${ARG_TARGET}")
idf_die("TARGET '${ARG_TARGET}' for menuconfig already exists")
endif()
idf_build_get_property(python PYTHON)
idf_build_get_property(idf_path IDF_PATH)
idf_build_get_property(kconfgen_cmd __BASE_KCONFGEN_CMD)
idf_build_get_property(sdkconfig SDKCONFIG)
idf_build_get_property(root_kconfig __ROOT_KCONFIG)
idf_build_get_property(build_dir BUILD_DIR)
idf_build_get_property(target IDF_TARGET)
idf_build_get_property(toolchain IDF_TOOLCHAIN)
idf_build_get_property(idf_init_version __IDF_INIT_VERSION)
idf_build_get_property(idf_env_fpga __IDF_ENV_FPGA)
idf_build_get_property(kconfgen_outputs_cmd __KCONFGEN_OUTPUTS_CMD)
# Newer versions of esp-idf-kconfig renamed menuconfig to esp_menuconfig
# Order matters here, we want to use esp_menuconfig if it is available
execute_process(
COMMAND "${python}" -c "import esp_menuconfig"
RESULT_VARIABLE ESP_MENUCONFIG_AVAILABLE
OUTPUT_QUIET ERROR_QUIET
)
if(ESP_MENUCONFIG_AVAILABLE EQUAL 0)
set(MENUCONFIG_CMD "${python}" -m esp_menuconfig)
else()
set(MENUCONFIG_CMD "${python}" -m menuconfig)
endif()
__create_executable_config_env_file("${executable}")
get_target_property(config_env_dir "${executable}" CONFIG_ENV_DIR)
add_custom_target("${ARG_TARGET}"
# Prepare Kconfig source files
COMMAND ${python} "${idf_path}/tools/kconfig_new/prepare_kconfig_files.py"
--list-separator=semicolon
--env-file "${config_env_dir}/config.env"
# Generate config with current settings
COMMAND ${kconfgen_cmd}
--env "IDF_TARGET=${target}"
--env "IDF_TOOLCHAIN=${toolchain}"
--env "IDF_ENV_FPGA=${idf_env_fpga}"
--env "IDF_INIT_VERSION=${idf_init_version}"
--dont-write-deprecated
${kconfgen_outputs_cmd}
--env-file "${config_env_dir}/config.env"
# Check terminal capabilities
COMMAND ${python} "${idf_path}/tools/check_term.py"
# Run menuconfig
COMMAND ${CMAKE_COMMAND} -E env
"COMPONENT_KCONFIGS_SOURCE_FILE=${config_env_dir}/kconfigs.in"
"COMPONENT_KCONFIGS_PROJBUILD_SOURCE_FILE=${config_env_dir}/kconfigs_projbuild.in"
"COMPONENT_KCONFIGS_EXCLUDED_SOURCE_FILE=${config_env_dir}/kconfigs_excluded.in"
"COMPONENT_KCONFIGS_PROJBUILD_EXCLUDED_SOURCE_FILE=${config_env_dir}/kconfigs_projbuild_excluded.in"
"KCONFIG_CONFIG=${sdkconfig}"
"IDF_TARGET=${target}"
"IDF_TOOLCHAIN=${toolchain}"
"IDF_ENV_FPGA=${idf_env_fpga}"
"IDF_INIT_VERSION=${idf_init_version}"
"IDF_BUILD_V2=y"
${MENUCONFIG_CMD} "${root_kconfig}"
# Post-menuconfig: insert deprecated options for backward compatibility
COMMAND ${kconfgen_cmd}
--env "IDF_TARGET=${target}"
--env "IDF_TOOLCHAIN=${toolchain}"
--env "IDF_ENV_FPGA=${idf_env_fpga}"
--env "IDF_INIT_VERSION=${idf_init_version}"
${kconfgen_outputs_cmd}
--env-file "${config_env_dir}/config.env"
USES_TERMINAL
COMMENT "Running menuconfig..."
)
endfunction()
#[[
.. cmakev2:function:: idf_create_confserver
.. code-block:: cmake
idf_create_confserver(<executable>
TARGET <target>)
*executable[in]*
Executable target for which to create the confserver target.
*TARGET[in]*
Name of the confserver target to be created.
Create a confserver target with the name specified by the ``TARGET``
option for an ``executable``.
#]]
function(idf_create_confserver executable)
set(options)
set(one_value TARGET)
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()
idf_build_get_property(python PYTHON)
idf_build_get_property(sdkconfig SDKCONFIG)
idf_build_get_property(root_kconfig __ROOT_KCONFIG)
idf_build_get_property(root_sdkconfig_rename __ROOT_SDKCONFIG_RENAME)
idf_build_get_property(base_kconfgen_cmd __BASE_KCONFGEN_CMD)
idf_build_get_property(config_dir CONFIG_DIR)
__create_executable_config_env_file("${executable}")
get_target_property(config_env_dir "${executable}" CONFIG_ENV_DIR)
add_custom_target("${ARG_TARGET}"
# Prepare Kconfig source files
COMMAND ${python} "${idf_path}/tools/kconfig_new/prepare_kconfig_files.py"
--list-separator=semicolon
--env-file "${config_env_dir}/config.env"
# Generate kconfig_menus.json
COMMAND ${base_kconfgen_cmd}
--env-file "${config_env_dir}/config.env"
--output json_menus "${config_dir}/kconfig_menus.json"
# Run confserver
COMMAND ${python} -m kconfserver
--env-file "${config_env_dir}/config.env"
--kconfig "${root_kconfig}"
--sdkconfig-rename "${root_sdkconfig_rename}"
--config "${sdkconfig}"
VERBATIM
USES_TERMINAL
COMMENT "Running confserver..."
)
endfunction()
#[[
.. cmakev2:function:: idf_create_save_defconfig
.. code-block:: cmake
idf_create_save_defconfig()
Create the save-defconfig target.
#]]
function(idf_create_save_defconfig)
idf_build_get_property(prepare_cmd __PREPARE_KCONFIG_CMD)
idf_build_get_property(config_env_path __CONFIG_ENV_PATH)
idf_build_get_property(kconfgen_cmd __BASE_KCONFGEN_CMD)
add_custom_target(save-defconfig
# Prepare Kconfig source files
COMMAND ${prepare_cmd}
# Generate save-defconfig
COMMAND ${kconfgen_cmd}
--dont-write-deprecated
--output savedefconfig "${CMAKE_SOURCE_DIR}/sdkconfig.defaults"
--env-file "${config_env_path}"
USES_TERMINAL
COMMENT "Saving defconfig..."
VERBATIM
)
endfunction()
+102
View File
@@ -0,0 +1,102 @@
include_guard(GLOBAL)
#[[
__ldgen_process_template(LIBRARY <target>
TEMPLATE <template>
OUTPUT <output>
[SUFFIX <suffix>])
*TARGET[in]*
A library interface target that already has the properties
__LDGEN_LIBRARIES, __LDGEN_FRAGMENT_FILES, and __LDGEN_DEPENDS set.
These properties include libraries and fragment files for components
linked to the library interface.
*TEMPLATE[in]*
Linker script template to process.
*OUTPUT[in]*
The output filename where the generated linker script will be saved.
*SUFFIX[in,opt]*
Optional suffix for the generated files containing the list of linked
library archives.
Pass the linker script ``TEMPLATE`` to the linker script generation tool,
ldgen, for processing. The processed linker script is stored in the file
specified by the ``OUTPUT`` option.
This function can be called multiple times to generate multiple linker
scripts from a single template. For example, a component like esp_system
may be linked to multiple library interface targets, each with a different
set of components. This results in different sets of fragment files and
library archives. The suffix is used to ensure that the files listing the
archives are not overwritten.
#]]
function(__ldgen_process_template)
set(options)
set(one_value LIBRARY TEMPLATE SUFFIX OUTPUT)
set(multi_value)
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
if(NOT DEFINED ARG_LIBRARY)
idf_die("LIBRARY option is required")
endif()
if(NOT DEFINED ARG_TEMPLATE)
idf_die("TEMPLATE option is required")
endif()
if(NOT DEFINED ARG_OUTPUT)
idf_die("OUTPUT option is required")
endif()
idf_build_get_property(idf_path IDF_PATH)
idf_build_get_property(build_dir BUILD_DIR)
idf_build_get_property(sdkconfig SDKCONFIG)
idf_build_get_property(root_kconfig __ROOT_KCONFIG)
idf_build_get_property(python PYTHON)
idf_build_get_property(config_env_path __CONFIG_ENV_PATH)
idf_library_get_property(ldgen_libraries "${ARG_LIBRARY}" __LDGEN_LIBRARIES)
idf_library_get_property(ldgen_fragment_files "${ARG_LIBRARY}" __LDGEN_FRAGMENT_FILES)
idf_library_get_property(ldgen_depends "${ARG_LIBRARY}" __LDGEN_DEPENDS)
list(JOIN ldgen_libraries "\n" ldgen_libraries)
file(WRITE "${build_dir}/ldgen_libraries.in${ARG_SUFFIX}" "${ldgen_libraries}")
file(GENERATE OUTPUT "${build_dir}/ldgen_libraries${ARG_SUFFIX}"
INPUT "${build_dir}/ldgen_libraries.in${ARG_SUFFIX}")
set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
APPEND PROPERTY ADDITIONAL_CLEAN_FILES
"${build_dir}/ldgen_libraries.in${ARG_SUFFIX}"
"${build_dir}/ldgen_libraries${ARG_SUFFIX}")
if($ENV{LDGEN_CHECK_MAPPING})
set(ldgen_check "--check-mapping"
"--check-mapping-exceptions" "${idf_path}/tools/ci/check_ldgen_mapping_exceptions.txt")
idf_msg("Mapping check enabled in ldgen")
endif()
add_custom_command(
OUTPUT "${ARG_OUTPUT}"
COMMAND ${python} "${idf_path}/tools/ldgen/ldgen.py"
--config "${sdkconfig}"
--fragments-list "${ldgen_fragment_files}"
--input "${ARG_TEMPLATE}"
--output "${ARG_OUTPUT}"
--kconfig "${root_kconfig}"
--env-file "${config_env_path}"
--env "IDF_BUILD_V2=y"
--libraries-file "${build_dir}/ldgen_libraries${ARG_SUFFIX}"
--objdump "${CMAKE_OBJDUMP}"
${ldgen_check}
DEPENDS ${ARG_TEMPLATE} ${ldgen_fragment_files} ${ldgen_depends} ${sdkconfig}
VERBATIM
)
endfunction()
+357
View File
@@ -0,0 +1,357 @@
# 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)
endif()
# Set IDF_COMPONENT_MANAGER_INTERFACE_VERSION.
# Defaults to 4. Allow overriding via env/CMake.
__get_default_value(VARIABLE IDF_COMPONENT_MANAGER_INTERFACE_VERSION
DEFAULT 4
OUTPUT cmgr_iface)
idf_build_set_property(IDF_COMPONENT_MANAGER_INTERFACE_VERSION ${cmgr_iface})
# Set DEPENDENCIES_LOCK if set by the user. Otherwise, use the
# project directory and IDF_TARGET to determine the lock file path.
# Note: This deviates from the build system v1 behavior where we allow
# users to specify the lock file path via idf_build_set_property.
idf_build_get_property(deps_lock_file DEPENDENCIES_LOCK)
__get_default_value(VARIABLE DEPENDENCIES_LOCK
DEFAULT "${deps_lock_file}"
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. Initialize the component manager.
2. Run the component manager for all discovered components.
3. Re-collect Kconfig and regenerate sdkconfig with managed components included.
4. If the component manager run failed, error out.
#]]
function(__fetch_components_from_registry)
# Initialize the component manager.
__init_component_manager()
# Iteratively run the component manager and Kconfig until stable or error out.
set(__cmgr_round 0)
while(TRUE)
math(EXPR __cmgr_round "${__cmgr_round} + 1")
idf_msg("Component manager round ${__cmgr_round}...")
# Run the component manager for all discovered components
__download_component_level_managed_components(RESULT cmgr_result)
# Re-collect Kconfig and regenerate sdkconfig with managed components included
__generate_sdkconfig()
# If component manager run failed, use the failure result
if(cmgr_result EQUAL 0)
# If manager is disabled but manifests were detected, issue a warning
__component_manager_warn_if_disabled_and_manifests_exist()
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()
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(idf_component_manager IDF_COMPONENT_MANAGER)
if(NOT idf_component_manager EQUAL 1)
set(${ARG_RESULT} 0 PARENT_SCOPE)
return()
endif()
idf_build_get_property(python PYTHON)
idf_build_get_property(project_dir PROJECT_DIR)
idf_build_get_property(component_manager_interface_version IDF_COMPONENT_MANAGER_INTERFACE_VERSION)
idf_build_get_property(dependencies_lock_file DEPENDENCIES_LOCK)
idf_build_get_property(sdkconfig_json __SDKCONFIG_JSON)
# Invoke the component manager
execute_process(COMMAND ${python}
"-m"
"idf_component_manager.prepare_components"
"--project_dir=${project_dir}"
"--lock_path=${dependencies_lock_file}"
"--sdkconfig_json_file=${sdkconfig_json}"
"--interface_version=${component_manager_interface_version}"
"prepare_dependencies"
"--local_components_list_file=${ARG_COMPONENTS_LIST_FILE}"
"--managed_components_list_file=${ARG_MANAGED_OUTPUT_FILE}"
RESULT_VARIABLE result
ERROR_VARIABLE error)
if(NOT result EQUAL 0)
if(result EQUAL 10)
idf_warn("Component manager requested a re-run: ${error}")
else()
idf_die("Component manager failed: ${error}")
endif()
endif()
set(${ARG_RESULT} ${result} PARENT_SCOPE)
endfunction()
#[[
__download_component_level_managed_components(RESULT <variable>)
*RESULT[out]*
Exit code returned by the manager. 0 success, 10 re-run.
Download component-level managed components
#]]
function(__download_component_level_managed_components)
set(options)
set(one_value RESULT)
set(multi_value)
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
if(NOT DEFINED ARG_RESULT)
idf_die("RESULT option is required")
endif()
# Set up temporary files for the manager
idf_build_get_property(build_dir BUILD_DIR)
set(managed_components_list_file ${build_dir}/managed_components_list.temp.cmake)
set(local_components_list_file ${build_dir}/local_components_list.temp.yml)
# Build local components list from discovered components
set(__contents "components:\n")
idf_build_get_property(component_names COMPONENTS_DISCOVERED)
foreach(name ${component_names})
idf_component_get_property(dir ${name} COMPONENT_DIR)
set(__contents "${__contents} - name: \"${name}\"\n path: \"${dir}\"\n")
endforeach()
file(WRITE ${local_components_list_file} "${__contents}")
# Invoke the component manager
__download_managed_component(COMPONENTS_LIST_FILE "${local_components_list_file}"
MANAGED_OUTPUT_FILE "${managed_components_list_file}"
RESULT result)
# Ensure the manager produced the list of managed components
if(result EQUAL 0 AND NOT EXISTS ${managed_components_list_file})
idf_die("Managed components list file not produced by the component manager: ${managed_components_list_file}")
endif()
# Initialize managed components by including the generated list
# Include components even if result is 10 (missing kconfig) to allow kconfig regeneration
if(result EQUAL 0 OR result EQUAL 10)
include(${managed_components_list_file})
else()
idf_warn("Component manager returned unexpected result: ${result}. Managed components will not be included.")
endif()
# Clean up temporary files
if(NOT DEFINED ENV{IDF_KEEP_CMANAGER_TEMP} OR NOT "$ENV{IDF_KEEP_CMANAGER_TEMP}" STREQUAL "1")
file(REMOVE ${managed_components_list_file})
file(REMOVE ${local_components_list_file})
endif()
set(${ARG_RESULT} ${result} PARENT_SCOPE)
endfunction()
#[[
__component_manager_warn_if_disabled_and_manifests_exist()
When the component manager is disabled, warn if any discovered component
contains an idf_component.yml manifest.
#]]
function(__component_manager_warn_if_disabled_and_manifests_exist)
idf_build_get_property(idf_component_manager IDF_COMPONENT_MANAGER)
if(idf_component_manager EQUAL 1)
return()
endif()
idf_build_get_property(with_manifests __COMPONENTS_WITH_MANIFESTS)
if(with_manifests)
string(REPLACE ";" "\n\t" with_lines "${with_manifests}")
idf_warn(NOTICE "\"idf_component.yml\" file was found for components:\n\t
${with_lines}\nHowever, the component manager is not enabled.")
endif()
endfunction()
#[[
__component_set_property(target property value)
Shim for setting component properties, primarily for use by the component
manager in build system v2. This function only processes dependency-related
properties(MANAGED_REQUIRES and MANAGED_PRIV_REQUIRES) produced by the
component manager's injection file. Other properties are ignored to avoid
interfering with the cmakev2 build flow. Target names with triple
underscores are normalized.
#]]
function(__component_set_property target property value)
# If the target has 3 underscores, remove all of them and normalize the target
# This shim is only intended to process dependency-related properties produced
# by the component manager injection file. Ignore unrelated properties to avoid
# clobbering configuration already set by the cmakev2 build flow.
string(REPLACE "___" "" target "${target}")
# We only consume MANAGED_REQUIRES and MANAGED_PRIV_REQUIRES from the component manager.
# The manager's REQUIRES/PRIV_REQUIRES output contains both original and resolved names,
# which we don't want. We'll handle name resolution locally using our utility functions.
if(property STREQUAL "MANAGED_REQUIRES")
# Set the managed property for tracking
idf_component_set_property("${target}" "${property}" "${value}")
# Also append to the regular REQUIRES property
idf_component_set_property("${target}" REQUIRES "${value}" APPEND)
elseif(property STREQUAL "MANAGED_PRIV_REQUIRES")
# Set the managed property for tracking
idf_component_set_property("${target}" "${property}" "${value}")
# Also append to the regular PRIV_REQUIRES property
idf_component_set_property("${target}" PRIV_REQUIRES "${value}" APPEND)
else()
# Ignore REQUIRES, PRIV_REQUIRES, and other properties like INCLUDE_DIRS,
# __COMPONENT_SOURCE, __COMPONENT_REGISTERED, etc.
endif()
endfunction()
#[[
__inject_requirements_for_component_from_manager(<component_name>)
Managed dependency injection for a single component in build system v2.
Calls the Component Manager to compute manifest-derived dependencies and
updates the component's MANAGED_* properties.
#]]
function(__inject_requirements_for_component_from_manager component_name)
# Skip if already injected
idf_component_get_property(already_injected "${component_name}" __MANAGED_INJECTED)
if(already_injected)
return()
endif()
idf_dbg("Injecting requirements for component '${component_name}' from the component manager")
idf_build_get_property(python PYTHON)
idf_build_get_property(project_dir PROJECT_DIR)
idf_build_get_property(build_dir BUILD_DIR)
idf_build_get_property(dependencies_lock_file DEPENDENCIES_LOCK)
idf_build_get_property(sdkconfig_json __SDKCONFIG_JSON)
idf_build_get_property(component_manager_interface_version IDF_COMPONENT_MANAGER_INTERFACE_VERSION)
idf_build_get_property(idf_path IDF_PATH)
idf_build_get_property(component_prefix PREFIX)
idf_component_get_property(component_source "${component_name}" COMPONENT_SOURCE)
idf_component_get_property(component_dir "${component_name}" COMPONENT_DIR)
# The component manager will inject requirements for this component. To do this, it needs to files:
#
# 1. An input file which states the component's source type. This is a minimal build system v1-style file
# which contains the component's source type. To make the component manager happy, we create a file with
# shim __component_set_property(), which calls idf_component_set_property(). The component manager will
# modify this file by adding the component's requirements. TODO: Improve this.
# 2. A file which lists the components with manifests. This file is created by the component manager,
# and is deleted after the component manager is done. This works for build system v1 where we provide
# a global list of components with manifests. However, for build system v2, we need to provide this file
# for each component. Hence, we create this file and place it in the build directory.
set(out_file "${build_dir}/component_requires.${component_name}.temp.cmake")
set(cmgr_target "___${component_prefix}_${component_name}")
# We only provide component source to the component manager
file(WRITE "${out_file}" "__component_set_property(${cmgr_target} __COMPONENT_SOURCE \"${component_source}\")\n")
# Create components_with_manifests_list.temp file with only this component if it has a manifest
set(components_with_manifests_file "${build_dir}/components_with_manifests_list.temp")
if(EXISTS "${component_dir}/idf_component.yml")
file(WRITE "${components_with_manifests_file}" "${component_dir}\n")
else()
file(WRITE "${components_with_manifests_file}" "")
endif()
# Call component manager to inject requirements
execute_process(COMMAND ${python}
"-m"
"idf_component_manager.prepare_components"
"--project_dir=${project_dir}"
"--lock_path=${dependencies_lock_file}"
"--sdkconfig_json_file=${sdkconfig_json}"
"--interface_version=${component_manager_interface_version}"
"inject_requirements"
"--idf_path=${idf_path}"
"--build_dir=${build_dir}"
"--component_requires_file=${out_file}"
RESULT_VARIABLE result
ERROR_VARIABLE error)
if(NOT result EQUAL 0)
idf_die("Component manager requirements injection failed for '${component_name}': ${error}")
endif()
# Include the component manager's output
if(EXISTS "${out_file}")
include("${out_file}")
endif()
# Clean up temporary files
if(NOT DEFINED ENV{IDF_KEEP_CMANAGER_TEMP} OR NOT "$ENV{IDF_KEEP_CMANAGER_TEMP}" STREQUAL "1")
file(REMOVE "${out_file}")
file(REMOVE "${components_with_manifests_file}")
endif()
idf_component_set_property("${component_name}" __MANAGED_INJECTED YES)
endfunction()
+776
View File
@@ -0,0 +1,776 @@
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
include_guard(GLOBAL)
#[[
__init_project_name()
Initialize the PROJECT_NAME build property, using CMAKE_PROJECT_NAME as a
fallback.
#]]
function(__init_project_name)
__get_default_value(VARIABLE PROJECT_NAME
DEFAULT "${CMAKE_PROJECT_NAME}"
OUTPUT project_name)
idf_build_set_property(PROJECT_NAME "${project_name}")
endfunction()
#[[
__init_project_version()
Initialize the PROJECT_VER build property based on the following
precedence.
1. The PROJECT_VER environment or CMake variable.
2. The version.txt file located in the top-level project directory.
3. The VERSION argument, if provided, in the project() macro.
4. The output of git describe if the project is within a Git repository.
5. Defaults to 1 if none of the above conditions are met.
The value of PROJECT_VER will be overridden later if
CONFIG_APP_PROJECT_VER_FROM_CONFIG is defined. For more details, refer to
components/esp_app_format/CMakeLists.txt.
#]]
function(__init_project_version)
idf_build_get_property(project_dir PROJECT_DIR)
# 1. The PROJECT_VER environment or CMake variable.
__get_default_value(VARIABLE PROJECT_VER
DEFAULT NOTFOUND
OUTPUT project_ver)
if(project_ver)
idf_build_set_property(PROJECT_VER "${project_ver}")
return()
endif()
# 2. The version.txt file located in the top-level project directory.
if(EXISTS "${project_dir}/version.txt")
file(STRINGS "${project_dir}/version.txt" project_ver)
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${project_dir}/version.txt")
idf_build_set_property(PROJECT_VER "${project_ver}")
return()
endif()
# 3. The VERSION argument, if provided, in the project() macro.
if(PROJECT_VERSION)
idf_build_set_property(PROJECT_VER "${PROJECT_VERSION}")
return()
endif()
# 4. The output of git describe if the project is within a Git repository.
git_describe(project_ver "${project_dir}")
if(project_ver)
idf_build_set_property(PROJECT_VER "${project_ver}")
return()
endif()
# 5. Defaults to 1 if none of the above conditions are met.
idf_build_set_property(PROJECT_VER 1)
endfunction()
#[[
__init_project_configuration()
Configure the build settings in one location, incorporating preset
compilation flags, definitions, and settings based on sdkconfig.
#]]
function(__init_project_configuration)
set(compile_definitions)
set(compile_options)
set(c_compile_options)
set(cxx_compile_options)
set(asm_compile_options)
set(link_options)
idf_build_get_property(idf_ver IDF_VER)
idf_build_get_property(idf_target IDF_TARGET)
idf_build_get_property(components_discovered COMPONENTS_DISCOVERED)
idf_build_get_property(build_dir BUILD_DIR)
idf_build_get_property(project_dir PROJECT_DIR)
idf_build_get_property(project_name PROJECT_NAME)
list(APPEND compile_definitions "_GLIBCXX_USE_POSIX_SEMAPHORE" # These two lines enable libstd++ to use
"_GLIBCXX_HAVE_POSIX_SEMAPHORE" # posix-semaphores from components/pthread
"_GNU_SOURCE")
list(APPEND compile_definitions "ESP_PLATFORM")
list(APPEND compile_definitions "IDF_VER=\"${idf_ver}\"")
list(APPEND compile_options "-ffunction-sections"
"-fdata-sections"
# warning-related flags
"-Wall"
"-Wno-error=unused-function"
"-Wno-error=unused-variable"
"-Wno-error=unused-but-set-variable"
"-Wno-error=deprecated-declarations"
"-Wextra"
"-Wno-error=extra"
"-Wno-unused-parameter"
"-Wno-sign-compare"
# ignore multiple enum conversion warnings since gcc 11
# TODO: IDF-5163
"-Wno-enum-conversion"
# Default is dwarf-5 since GCC 11, fallback to dwarf-4 because of binary size
# TODO: IDF-5160
"-gdwarf-4"
# always generate debug symbols (even in release mode, these don't
# go into the final binary so have no impact on size
"-ggdb")
if(CONFIG_COMPILER_DISABLE_DEFAULT_ERRORS AND NOT CMAKE_C_COMPILER_ID MATCHES "Clang")
list(APPEND compile_options "-Werror=all")
else()
list(APPEND compile_options "-Werror")
endif()
if(IDF_TARGET STREQUAL "linux")
# Building for Linux target, fall back to an older version of the standard
# if the preferred one is not supported by the compiler.
set(preferred_c_versions gnu17 gnu11 gnu99)
set(ver_found FALSE)
foreach(c_version ${preferred_c_versions})
check_c_compiler_flag("-std=${c_version}" ver_${c_version}_supported)
if(ver_${c_version}_supported)
set(c_std ${c_version})
set(ver_found TRUE)
break()
endif()
endforeach()
if(NOT ver_found)
idf_die("Failed to set C language standard to one of the supported versions: "
"${preferred_c_versions}. Please upgrade the host compiler.")
endif()
set(preferred_cxx_versions gnu++2b gnu++20 gnu++2a gnu++17 gnu++14)
set(ver_found FALSE)
foreach(cxx_version ${preferred_cxx_versions})
check_cxx_compiler_flag("-std=${cxx_version}" ver_${cxx_version}_supported)
if(ver_${cxx_version}_supported)
set(cxx_std ${cxx_version})
set(ver_found TRUE)
break()
endif()
endforeach()
if(NOT ver_found)
idf_die("Failed to set C++ language standard to one of the supported versions: "
"${preferred_cxx_versions}. Please upgrade the host compiler.")
endif()
list(APPEND c_compile_options "-std=${c_std}")
list(APPEND cxx_compile_options "-std=${cxx_std}")
else()
# Building for chip targets: we use a known version of the toolchain.
# Use latest supported versions.
# For Linux target -std settings, refer to the __linux_build_set_lang_version
# function, which must be called after project().
# Please update docs/en/api-guides/c.rst, docs/en/api-guides/cplusplus.rst and
# tools/test_apps/system/cxx_build_test/main/test_cxx_standard.cpp when changing this.
list(APPEND c_compile_options "-std=gnu17")
list(APPEND cxx_compile_options "-std=gnu++2b")
endif()
if(CONFIG_COMPILER_OPTIMIZATION_SIZE)
if(CMAKE_C_COMPILER_ID MATCHES "Clang")
list(APPEND compile_options "-Oz")
else()
list(APPEND compile_options "-Os")
endif()
if(CMAKE_C_COMPILER_ID MATCHES "GNU")
list(APPEND compile_options "-freorder-blocks")
endif()
elseif(CONFIG_COMPILER_OPTIMIZATION_DEBUG)
list(APPEND compile_options "-Og")
if(CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CONFIG_IDF_TARGET_LINUX)
list(APPEND compile_options "-fno-shrink-wrap") # Disable shrink-wrapping to reduce binary size
endif()
elseif(CONFIG_COMPILER_OPTIMIZATION_NONE)
list(APPEND compile_options "-O0")
elseif(CONFIG_COMPILER_OPTIMIZATION_PERF)
list(APPEND compile_options "-O2")
endif()
if(CONFIG_COMPILER_CXX_EXCEPTIONS)
list(APPEND cxx_compile_options "-fexceptions")
else()
list(APPEND cxx_compile_options "-fno-exceptions")
endif()
if(CONFIG_COMPILER_CXX_RTTI)
list(APPEND cxx_compile_options "-frtti")
else()
list(APPEND cxx_compile_options "-fno-rtti")
list(APPEND link_options "-fno-rtti") # used to invoke correct multilib variant (no-rtti) during linking
endif()
if(CONFIG_COMPILER_SAVE_RESTORE_LIBCALLS)
list(APPEND compile_options "-msave-restore")
endif()
if(CMAKE_C_COMPILER_ID MATCHES "GNU")
list(APPEND c_compile_options "-Wno-old-style-declaration")
endif()
# Clang finds some warnings in IDF code which GCC doesn't.
# All these warnings should be fixed before Clang is presented
# as a toolchain choice for users.
if(CMAKE_C_COMPILER_ID MATCHES "Clang")
# Clang checks Doxygen comments for being in sync with function prototype.
# There are some inconsistencies, especially in ROM headers.
list(APPEND compile_options "-Wno-documentation")
# GCC allows repeated typedefs when the source and target types are the same.
# Clang doesn't allow this. This occurs in many components due to forward
# declarations.
list(APPEND compile_options "-Wno-typedef-redefinition")
# This issue is seemingly related to newlib's char type functions.
# Fix is not clear yet.
list(APPEND compile_options "-Wno-char-subscripts")
# Clang seems to notice format string issues which GCC doesn't.
list(APPEND compile_options "-Wno-format-security")
# Some pointer checks in mDNS component check addresses which can't be NULL
list(APPEND compile_options "-Wno-tautological-pointer-compare")
# Similar to the above, in tcp_transport
list(APPEND compile_options "-Wno-pointer-bool-conversion")
# mbedTLS md5.c triggers this warning in md5_test_buf (false positive)
list(APPEND compile_options "-Wno-string-concatenation")
# multiple cases of implicit conversions between unrelated enum types
list(APPEND compile_options "-Wno-enum-conversion")
# When IRAM_ATTR is specified both in function declaration and definition,
# it produces different section names, since section names include __COUNTER__.
# Occurs in multiple places.
list(APPEND compile_options "-Wno-section")
# Multiple cases of attributes unknown to clang, for example
# __attribute__((optimize("-O3")))
list(APPEND compile_options "-Wno-unknown-attributes")
# Disable Clang warnings for atomic operations with access size
# more then 4 bytes
list(APPEND compile_options "-Wno-atomic-alignment")
# several warnings in wpa_supplicant component
list(APPEND compile_options "-Wno-unused-but-set-variable")
# Clang also produces many -Wunused-function warnings which GCC doesn't.
list(APPEND compile_options "-Wno-unused-function")
# many warnings in bluedroid code
# warning: field 'hdr' with variable sized type 'BT_HDR' not at the end of a struct or class is a GNU extension
list(APPEND compile_options "-Wno-gnu-variable-sized-type-not-at-end")
# several warnings in bluedroid code
list(APPEND compile_options "-Wno-constant-logical-operand")
# warning: '_Static_assert' with no message is a C2x extension
list(APPEND compile_options "-Wno-c2x-extensions")
# warning on xMPU_SETTINGS for esp32s2 has size 0 for C and 1 for C++
list(APPEND compile_options "-Wno-extern-c-compat")
if(NOT (CONFIG_IDF_TARGET_LINUX AND CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin"))
# warning: implicit truncation from 'int' to a one-bit wide bit-field changes value from 1 to -1
list(APPEND compile_options "-Wno-single-bit-bitfield-constant-conversion")
endif()
endif()
# More warnings may exist in unit tests and example projects.
if(CONFIG_COMPILER_WARN_WRITE_STRINGS)
list(APPEND compile_options "-Wwrite-strings")
endif()
if(CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE)
list(APPEND compile_definitions "-DNDEBUG")
endif()
if(CONFIG_COMPILER_NO_MERGE_CONSTANTS)
list(APPEND compile_options "-fno-merge-constants")
endif()
if(CONFIG_COMPILER_STACK_CHECK_MODE_NORM)
list(APPEND compile_options "-fstack-protector")
elseif(CONFIG_COMPILER_STACK_CHECK_MODE_STRONG)
list(APPEND compile_options "-fstack-protector-strong")
elseif(CONFIG_COMPILER_STACK_CHECK_MODE_ALL)
list(APPEND compile_options "-fstack-protector-all")
endif()
if(CONFIG_COMPILER_DUMP_RTL_FILES)
list(APPEND compile_options "-fdump-rtl-expand")
endif()
if(CMAKE_C_COMPILER_ID MATCHES "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER 15.0)
list(APPEND c_compile_options "-fzero-init-padding-bits=all" "-fno-malloc-dce")
endif()
if(CONFIG_COMPILER_DISABLE_GCC12_WARNINGS)
list(APPEND compile_options "-Wno-address"
"-Wno-use-after-free")
endif()
if(CONFIG_COMPILER_DISABLE_GCC13_WARNINGS)
list(APPEND compile_options "-Wno-xor-used-as-pow")
list(APPEND c_compile_options "-Wno-enum-int-mismatch")
list(APPEND cxx_compile_options "-Wno-self-move"
"-Wno-dangling-reference")
endif()
if(CONFIG_COMPILER_DISABLE_GCC14_WARNINGS)
list(APPEND compile_options "-Wno-calloc-transposed-args")
endif()
if(CONFIG_COMPILER_DISABLE_GCC15_WARNINGS)
list(APPEND c_compile_options "-Wno-unterminated-string-initialization")
list(APPEND c_compile_options "-Wno-header-guard")
list(APPEND cxx_compile_options "-Wno-self-move")
list(APPEND cxx_compile_options "-Wno-template-body")
list(APPEND cxx_compile_options "-Wno-dangling-reference")
list(APPEND cxx_compile_options "-Wno-defaulted-function-deleted")
endif()
# GCC-specific options
if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
list(APPEND compile_options "-fstrict-volatile-bitfields")
if(CONFIG_COMPILER_STATIC_ANALYZER)
list(APPEND compile_options "-fanalyzer")
endif()
endif()
if(CONFIG_ESP_SYSTEM_USE_EH_FRAME)
list(APPEND compile_options "-fasynchronous-unwind-tables")
list(APPEND link_options "-Wl,--eh-frame-hdr")
endif()
if(CONFIG_ESP_SYSTEM_USE_FRAME_POINTER)
list(APPEND compile_options "-fno-omit-frame-pointer")
if(CMAKE_C_COMPILER_ID MATCHES "GNU")
list(APPEND compile_options "-mno-omit-leaf-frame-pointer")
endif()
endif()
list(APPEND link_options "-fno-lto")
if(CONFIG_IDF_TARGET_LINUX AND CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
# Not all versions of the MacOS linker support the -warn_commons flag.
# ld version 1053.12 (and above) have been tested to support it.
# Hence, we extract the version string from the linker output
# before including the flag.
# Get the ld version, capturing both stdout and stderr
execute_process(
COMMAND ${CMAKE_LINKER} -v
OUTPUT_VARIABLE LD_VERSION_OUTPUT
ERROR_VARIABLE LD_VERSION_ERROR
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
)
# Combine stdout and stderr
set(LD_VERSION_OUTPUT "${LD_VERSION_OUTPUT}\n${LD_VERSION_ERROR}")
# Extract the version string
string(REGEX MATCH "PROJECT:(ld|dyld)-([0-9]+)\\.([0-9]+)" LD_VERSION_MATCH "${LD_VERSION_OUTPUT}")
set(LD_VERSION_MAJOR_MINOR "${CMAKE_MATCH_2}.${CMAKE_MATCH_3}")
idf_msg("Linker Version: ${LD_VERSION_MAJOR_MINOR}")
# Compare the version with 1053.12
if(LD_VERSION_MAJOR_MINOR VERSION_GREATER_EQUAL "1053.12")
list(APPEND link_options "-Wl,-warn_commons")
endif()
list(APPEND link_options "-Wl,-dead_strip")
else()
list(APPEND link_options "-Wl,--gc-sections")
list(APPEND link_options "-Wl,--warn-common")
endif()
# SMP FreeRTOS user provided minimal idle hook. This allows the user to provide
# their own copy of vApplicationPassiveIdleHook()
if(CONFIG_FREERTOS_USE_PASSIVE_IDLE_HOOK)
list(APPEND link_options "-Wl,--wrap=vApplicationPassiveIdleHook")
endif()
# Placing jump tables in flash would cause issues with code that required
# to be placed in IRAM
list(APPEND compile_options "-fno-jump-tables")
if(CMAKE_C_COMPILER_ID MATCHES "GNU")
# This flag is GCC-specific.
# Not clear yet if some other flag should be used for Clang.
list(APPEND compile_options "-fno-tree-switch-conversion")
endif()
if(CMAKE_C_COMPILER_ID MATCHES "Clang")
list(APPEND compile_options "-fno-use-cxa-atexit") # TODO IDF-10934
else()
list(APPEND cxx_compile_options "-fuse-cxa-atexit")
endif()
if(COMPILER_RT_LIB_NAME)
list(APPEND link_options "-rtlib=${CONFIG_COMPILER_RT_LIB_NAME}")
endif()
if(CONFIG_LIBC_PICOLIBC)
list(APPEND c_compile_options "-specs=picolibc.specs")
list(APPEND cxx_compile_options "-specs=picolibcpp.specs")
list(APPEND link_options "-specs=picolibc.specs")
endif()
if(CMAKE_C_COMPILER_ID MATCHES "GNU")
set(mapfile "${build_dir}/${project_name}.map")
set(target_upper "${idf_target}")
string(TOUPPER ${target_upper} target_upper)
# Add cross-reference table to the map file
list(APPEND link_options "-Wl,--cref")
# Add this symbol as a hint for esp_idf_size to guess the target name
list(APPEND link_options "-Wl,--defsym=IDF_TARGET_${target_upper}=0")
# Enable map file output
list(APPEND link_options "-Wl,--Map=${mapfile}")
# Check if linker supports --no-warn-rwx-segments
execute_process(COMMAND ${CMAKE_LINKER} "--no-warn-rwx-segments" "--version"
RESULT_VARIABLE result
OUTPUT_QUIET
ERROR_QUIET)
if(${result} EQUAL 0)
# Do not print RWX segment warnings
list(APPEND link_options "-Wl,--no-warn-rwx-segments")
endif()
if(CONFIG_COMPILER_ORPHAN_SECTIONS_WARNING)
# Print warnings if orphan sections are found
list(APPEND link_options "-Wl,--orphan-handling=warn")
endif()
endif()
# Prepares the list of compiler flags for remapping various paths to fixed
# names. This is used when reproducible builds are required. This function
# also creates a gdbinit file for the debugger to remap the substituted
# paths back to the real paths in the filesystem.
set(gdbinit_dir ${build_dir}/gdbinit)
set(gdbinit_path "${gdbinit_dir}/prefix_map")
if(CONFIG_COMPILER_HIDE_PATHS_MACROS)
list(APPEND compile_options "-fmacro-prefix-map=${CMAKE_SOURCE_DIR}=.")
list(APPEND compile_options "-fmacro-prefix-map=${idf_path}=/IDF")
endif()
if(CONFIG_APP_REPRODUCIBLE_BUILD)
list(APPEND compile_options "-fdebug-prefix-map=${idf_path}=/IDF")
list(APPEND compile_options "-fdebug-prefix-map=${project_dir}=/IDF_PROJECT")
list(APPEND compile_options "-fdebug-prefix-map=${build_dir}=/IDF_BUILD")
# Generate mapping for component paths
set(gdbinit_file_lines)
foreach(component_name ${components_discovered})
idf_component_get_property(component_dir ${component_name} COMPONENT_DIR)
string(TOUPPER ${component_name} component_name_uppercase)
set(substituted_path "/COMPONENT_${component_name_uppercase}_DIR")
list(APPEND compile_options "-fdebug-prefix-map=${component_dir}=${substituted_path}")
string(APPEND gdbinit_file_lines "set substitute-path ${substituted_path} ${component_dir}\n")
endforeach()
# Mapping for toolchain path
execute_process(
COMMAND ${CMAKE_C_COMPILER} -print-sysroot
OUTPUT_VARIABLE compiler_sysroot
)
if(compiler_sysroot STREQUAL "")
idf_die("Failed to determine toolchain sysroot")
endif()
string(STRIP "${compiler_sysroot}" compiler_sysroot)
get_filename_component(compiler_sysroot "${compiler_sysroot}/.." REALPATH)
list(APPEND compile_options "-fdebug-prefix-map=${compiler_sysroot}=/TOOLCHAIN")
string(APPEND gdbinit_file_lines "set substitute-path /TOOLCHAIN ${compiler_sysroot}\n")
file(WRITE "${build_dir}/prefix_map_gdbinit" "${gdbinit_file_lines}") # TODO IDF-11667
idf_build_set_property(DEBUG_PREFIX_MAP_GDBINIT "${gdbinit_path}")
else()
set(gdbinit_file_lines "# There is no prefix map defined for the project.\n")
endif()
# Write prefix_map_gdbinit file even it is empty.
file(MAKE_DIRECTORY ${gdbinit_dir})
file(WRITE "${gdbinit_path}" "${gdbinit_file_lines}")
idf_build_set_property(GDBINIT_FILES_PREFIX_MAP "${gdbinit_path}")
idf_build_set_property(COMPILE_OPTIONS "${compile_options}" APPEND)
idf_build_set_property(C_COMPILE_OPTIONS "${c_compile_options}" APPEND)
idf_build_set_property(CXX_COMPILE_OPTIONS "${cxx_compile_options}" APPEND)
idf_build_set_property(ASM_COMPILE_OPTIONS "${asm_compile_options}" APPEND)
idf_build_set_property(COMPILE_DEFINITIONS "${compile_definitions}" APPEND)
idf_build_set_property(LINK_OPTIONS "${link_options}" APPEND)
# Set the LINKER_TYPE build property. Different linkers may have varying
# options, so it's important to identify the linker type to configure the
# options correctly. Currently, LINKER_TYPE is used to set the appropriate
# linker options for linking the entire archive, which differs between the
# GNU and Apple linkers when building on the host.
if(CONFIG_IDF_TARGET_LINUX AND CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
# Compiling for the host, and the host is macOS, so the linker is Darwin LD.
# Note, when adding support for Clang and LLD based toolchain this check will
# need to be modified.
set(linker_type "Darwin")
else()
set(linker_type "GNU")
endif()
idf_build_set_property(LINKER_TYPE "${linker_type}")
endfunction()
#[[
__create_project_flash_targets()
Add placeholder flash targets to the build. This is used by components to
declare dependencies on the flash target.
#]]
function(__create_project_flash_targets)
if(NOT TARGET flash)
add_custom_target(flash)
endif()
# When flash encryption is enabled, a corresponding 'encrypted-flash' target will be created.
idf_build_get_property(sdkconfig SDKCONFIG)
__get_sdkconfig_option(OPTION CONFIG_SECURE_FLASH_ENCRYPTION_MODE_DEVELOPMENT
SDKCONFIG "${sdkconfig}"
OUTPUT sdkconfig_target)
if(encrypted_flash_enabled AND NOT TARGET encrypted-flash)
add_custom_target(encrypted-flash)
endif()
endfunction()
#[[
__init_project_flash_targets()
If binary generation is enabled, initialize the esptool component and
enable the generation of esptool flash argument files for the flash and
encrypted-flash targets. Note that this is done after including the
project_include.cmake files, as we need the functions defined in the
esptool_py component.
#]]
function(__init_project_flash_targets)
if(CONFIG_APP_BUILD_GENERATE_BINARIES)
idf_component_get_property(main_args esptool_py FLASH_ARGS)
idf_component_get_property(sub_args esptool_py FLASH_SUB_ARGS)
esptool_py_flash_target(flash "${main_args}" "${sub_args}")
endif()
endfunction()
#[[
.. cmakev2:macro:: idf_project_init
.. code-block:: cmake
idf_project_init()
Initialize settings that need to be configured after the ``project()``
function is called. This must occur after the ``project()`` function and
before any other build system functions. It initializes the
``PROJECT_NAME`` and ``PROJECT_VER`` build properties, as well as all
default C, CXX, and ASM compile options, link options, and compile
definitions.
This macro also includes ``project_include.cmake`` files for the discovered
components, as these files define project-wide functionality that needs to
be available before any component's ``CMakeLists.txt`` is evaluated. The
``project_include.cmake`` files should be evaluated in the global scope.
Therefore, this is defined as a macro and should be called only from the
global scope or from within another macro.
#]]
macro(idf_project_init)
idf_build_get_property(project_initialized __PROJECT_INITIALIZED)
if(NOT project_initialized)
# Ensure this function is executed only once throughout the entire
# project.
# Set PROJECT_NAME build property
__init_project_name()
# Set PROJECT_VER build property
__init_project_version()
# Initialize all compilation options and defines.
__init_project_configuration()
# Create global flash targets.
__create_project_flash_targets()
# Discover and initialize components
__init_components()
# Generate initial sdkconfig with discovered components
__generate_sdkconfig()
# Initialize the component manager and fetch components in a loop
__fetch_components_from_registry()
# Include sdkconfig.cmake
idf_build_get_property(sdkconfig_cmake __SDKCONFIG_CMAKE)
if(NOT EXISTS "${sdkconfig_cmake}")
idf_die("sdkconfig.cmake file not found.")
endif()
include("${sdkconfig_cmake}")
# Initialize the target architecture based on the configuration
# Ensure this is done after including the sdkconfig.
__init_idf_target_arch()
# Include all project_include.cmake files for the components that have
# been discovered.
idf_build_get_property(component_names COMPONENTS_DISCOVERED)
foreach(component_name IN LISTS component_names)
idf_component_get_property(project_include ${component_name} __PROJECT_INCLUDE)
idf_component_get_property(component_dir ${component_name} COMPONENT_DIR)
if(project_include)
set(COMPONENT_NAME ${component_name})
set(COMPONENT_DIR ${component_dir})
# The use of COMPONENT_PATH is deprecated in cmakev1. Users
# are encouraged to use COMPONENT_DIR instead.
set(COMPONENT_PATH ${component_dir})
idf_dbg("Including ${project_include}")
include("${project_include}")
unset(COMPONENT_NAME)
unset(COMPONENT_DIR)
unset(COMPONENT_PATH)
endif()
endforeach()
# Initialize global flash targets.
__init_project_flash_targets()
# If explicitly requested, include all components by calling
# `add_subdirectory` for every discovered component. The default
# behavior is to include only the components based on the requirements.
__get_default_value(VARIABLE IDF_INCLUDE_ALL_COMPONENTS
DEFAULT NO
OUTPUT include_all_components)
if(include_all_components)
idf_msg("Including all discovered components")
foreach(component_name IN LISTS component_names)
idf_component_include("${component_name}")
endforeach()
endif()
idf_build_set_property(__PROJECT_INITIALIZED YES)
endif()
unset(project_initialized)
endmacro()
#[[
.. cmakev2:function:: idf_build_generate_flasher_args
.. code-block:: cmake
idf_build_generate_flasher_args()
Generate the flasher_args.json file for the global flash target for tools
that require it.
#]]
function(idf_build_generate_flasher_args)
# The variables listed below are used to configure the template
# flasher_args.json.in. Some of these variables, such as flash mode, size,
# and frequency, are set as properties of the esptool_py component.
idf_build_get_property(target IDF_TARGET)
set(ESPTOOLPY_CHIP "${target}")
set(ESPTOOLPY_BEFORE "${CONFIG_ESPTOOLPY_BEFORE}")
set(ESPTOOLPY_AFTER "${CONFIG_ESPTOOLPY_AFTER}")
if(CONFIG_ESPTOOLPY_NO_STUB)
set(ESPTOOLPY_WITH_STUB false)
else()
set(ESPTOOLPY_WITH_STUB true)
endif()
if(CONFIG_SECURE_BOOT OR CONFIG_SECURE_FLASH_ENC_ENABLED)
# If security enabled then override post flash option
set(ESPTOOLPY_AFTER "no-reset")
endif()
idf_component_get_property(ESPFLASHMODE esptool_py ESPFLASHMODE)
idf_component_get_property(ESPFLASHFREQ esptool_py ESPFLASHFREQ)
idf_component_get_property(ESPFLASHSIZE esptool_py ESPFLASHSIZE)
idf_component_get_property(esptool_py_dir esptool_py COMPONENT_DIR)
# Generate flasher args files
idf_build_get_property(build_dir BUILD_DIR)
file(READ "${esptool_py_dir}/flasher_args.json.in" flasher_args_content)
string(CONFIGURE "${flasher_args_content}" flasher_args_content)
# We need to create a flasher_args.json.in to create the final flasher_args.json
# because CMake only resolves generator expressions in the file_generate command
# with the INPUT keyword during the generation phase.
file_generate("${build_dir}/flasher_args.json.in"
CONTENT "${flasher_args_content}")
file_generate("${build_dir}/flasher_args.json"
INPUT "${build_dir}/flasher_args.json.in")
endfunction()
#[[api
.. cmakev2:macro:: idf_project_default
.. code-block:: cmake
idf_project_default()
Create a default project executable based on the main component and its
transitive dependencies. The executable name is derived from the
``PROJECT_NAME`` variable, which by default uses the ``CMAKE_PROJECT_NAME``
value specified in the CMake's ``project()`` call.
Generate the binary image for the executable, signed or unsigned based on
the configuration, and add flash targets for it.
#]]
macro(idf_project_default)
idf_project_init()
idf_build_get_property(build_dir BUILD_DIR)
idf_build_get_property(executable PROJECT_NAME)
idf_build_executable("${executable}" COMPONENTS main SUFFIX ".elf")
if(CONFIG_APP_BUILD_GENERATE_BINARIES)
# Is it possible to have a configuration where
# CONFIG_APP_BUILD_GENERATE_BINARIES is not set?
if(CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES)
idf_build_binary("${executable}"
OUTPUT_FILE "${build_dir}/${executable}-unsigned.bin"
TARGET "${executable}_binary_unsigned")
idf_sign_binary("${executable}_binary_unsigned"
OUTPUT_FILE "${build_dir}/${executable}.bin"
TARGET "${executable}_binary_signed")
idf_check_binary_size("${executable}_binary_signed")
add_custom_target(app ALL DEPENDS "${executable}_binary_signed")
idf_flash_binary("${executable}_binary_signed"
TARGET app-flash
NAME "app"
FLASH)
else()
idf_build_binary("${executable}"
OUTPUT_FILE "${build_dir}/${executable}.bin"
TARGET "${executable}_binary")
idf_check_binary_size("${executable}_binary")
idf_check_binary_signed("${executable}_binary")
add_custom_target(app ALL DEPENDS "${executable}_binary")
idf_flash_binary("${executable}_binary"
TARGET app-flash
NAME "app"
FLASH)
endif()
# FIXME: Dependencies should be specified within the components, not in the
# build system.
if(CONFIG_APP_BUILD_TYPE_APP_2NDBOOT)
add_dependencies(flash "partition_table_bin")
endif()
if(CONFIG_APP_BUILD_BOOTLOADER)
add_dependencies(flash "bootloader")
endif()
idf_build_generate_flasher_args()
endif()
idf_create_menuconfig("${executable}"
TARGET menuconfig)
idf_create_confserver("${executable}"
TARGET confserver)
idf_create_save_defconfig()
idf_build_generate_metadata("${executable}")
unset(build_dir)
unset(executable)
endmacro()
+278
View File
@@ -0,0 +1,278 @@
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
# Simple project for basic cmakev2 testing
# Run: cmake -S . -B build
cmake_minimum_required(VERSION 3.22)
include($ENV{IDF_PATH}/tools/cmakev2/idf.cmake)
project(cmakev2
VERSION 1.2.3
LANGUAGES C CXX ASM)
add_custom_target(flash)
idf_project_init()
# Test component priority
function(test_component_priority)
# Set the idf component to be replaced with a testing component of higher
# priority.
set(component_name "esp_system")
# Check that idf component is between discovered components.
__get_component_interface(COMPONENT "${component_name}"
OUTPUT component_interface)
if("${component_interface}" STREQUAL "NOTFOUND")
idf_die("Component '${component_name}' not found")
endif()
# Check that idf component has "idf_components" as source.
idf_component_get_property(component_source
${component_interface}
COMPONENT_SOURCE)
if(NOT "${component_source}" STREQUAL "idf_components")
idf_die("Unexpected idf component '${component_name}' source '${component_source}'")
endif()
# Create fake component with same name as idf component.
set(component_dir "${CMAKE_CURRENT_BINARY_DIR}/${component_name}")
file(MAKE_DIRECTORY "${component_dir}")
file(TOUCH "${component_dir}/CMakeLists.txt")
# Initialize fake component with higher "project_components" priority.
idf_build_get_property(component_prefix PREFIX)
__init_component(DIRECTORY "${component_dir}"
PREFIX "${component_prefix}"
SOURCE "project_components")
# Check that the idf component was replaced with fake component.
idf_component_get_property(component_source
${component_interface}
COMPONENT_SOURCE)
if(NOT "${component_source}" STREQUAL "project_components")
idf_die("Unexpected fake component '${component_name}' source '${component_source}'")
endif()
idf_component_get_property(component_dir
${component_interface}
COMPONENT_DIR)
if(NOT "${component_dir}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}/${component_name}")
idf_die("Unexpected fake component '${component_name}' directory '${component_dir}'")
endif()
__dump_component_properties("${component_name}")
endfunction()
# Test that IDF_VERSION and IDF_VER build property is set
function(test_idf_version)
if(NOT DEFINED IDF_VERSION_MAJOR OR
NOT DEFINED IDF_VERSION_MINOR OR
NOT DEFINED IDF_VERSION_PATCH OR
NOT DEFINED ENV{IDF_VERSION})
idf_die("IDF_VERSION not set")
endif()
idf_build_get_property(idf_ver IDF_VER)
if(NOT idf_ver)
idf_die("IDF_VER build property not set")
endif()
endfunction()
# Test that Python interpreter is set
function(test_python)
if(NOT DEFINED PYTHON OR "${PYTHON}" STREQUAL "")
idf_die("PYTHON variable not defined or empty")
endif()
endfunction()
# Test that toolchain is properly set
function(test_toolchain)
if(NOT IDF_TOOLCHAIN)
idf_die("IDF_TOOLCHAIN variable not defined or empty")
endif()
if(NOT CMAKE_TOOLCHAIN_FILE)
idf_die("CMAKE_TOOLCHAIN_FILE variable not defined or empty")
endif()
idf_build_get_property(toolchain IDF_TOOLCHAIN)
if(NOT toolchain)
idf_die("IDF_TOOLCHAIN build property not set")
endif()
idf_build_get_property(toolchain_file IDF_TOOLCHAIN_FILE)
if(NOT toolchain_file)
idf_die("IDF_TOOLCHAIN_FILE build property not set")
endif()
endfunction()
# Test idf_build_library
function(test_idf_build_library)
# Create idflibtest with specific set of components and test that it
# contains them.
set(components app_trace app_update bootloader bootloader_support bt cmock)
set(idflib idflibtest)
idf_build_library("${idflib}" COMPONENTS "${components}")
if(NOT TARGET "${idflib}")
idf_die("'${idflib}' not created")
endif()
idf_library_get_property(lib_components "${idflib}" LIBRARY_COMPONENTS)
if(NOT "${lib_components}" STREQUAL "${components}")
idf_die("Library '${idflib}' components '${lib_components}' do not match "
"expected components '${components}'")
endif()
# Create idflibtest2 without specifying COMPONENTS and test that it
# contains all discovered components.
set(idflib idflibtest2)
idf_build_library("${idflib}")
if(NOT TARGET "${idflib}")
idf_die("'${idflib}' not created")
endif()
idf_library_get_property(lib_components "${idflib}" LIBRARY_COMPONENTS)
idf_build_get_property(component_names COMPONENTS_DISCOVERED)
if(NOT "${lib_components}" STREQUAL "${component_names}")
idf_die("Library '${idflib}' components '${lib_components}' do not match "
"COMPONENTS_DISCOVERED")
endif()
endfunction()
function(test_kconfig)
# Check that kconfig output files were generated
idf_build_get_property(config_dir CONFIG_DIR)
set(output_files sdkconfig.h sdkconfig.cmake sdkconfig.json)
foreach(file ${output_files})
set(file_path "${config_dir}/${file}")
if(NOT EXISTS "${file_path}")
idf_die("Missing kconfig output: ${file_path}")
endif()
file(SIZE "${file_path}" file_size)
if(file_size EQUAL 0)
idf_die("Empty kconfig output: ${file_path}")
endif()
endforeach()
# Check that kconfig targets were created
set(targets menuconfig confserver save-defconfig)
foreach(target ${targets})
if(NOT TARGET ${target})
idf_die("Missing kconfig target: ${target}")
endif()
endforeach()
endfunction()
# Test that PROJECT_NAME and PROJECT_VER build property is set and contain
# values provided in project() call.
function(test_project_properties)
idf_build_get_property(project_name PROJECT_NAME)
if(NOT project_name)
idf_die("PROJECT_NAME build property not set")
endif()
if(NOT "${project_name}" STREQUAL "${PROJECT_NAME}")
idf_die("PROJECT_NAME build property '${project_name}' != '${PROJECT_NAME}'")
endif()
idf_build_get_property(project_ver PROJECT_VER)
if(NOT project_ver)
idf_die("PROJECT_VER build property not set")
endif()
if(NOT "${project_ver}" STREQUAL "${PROJECT_VERSION}")
idf_die("PROJECT_VER build property '${project_ver}' != '${PROJECT_VERSION}'")
endif()
endfunction()
# Test that the targets for component1 and component2 are created when
# component2 is included.
function(test_include_component)
idf_build_library(idflibtest3 COMPONENTS component2)
idf_component_get_property(component_real_target
component2
COMPONENT_REAL_TARGET)
if(NOT TARGET ${component_real_target})
idf_die("Missing component2 target")
endif()
# Test that component1 is included as a dependency of component2.
idf_component_get_property(component_real_target
component1
COMPONENT_REAL_TARGET)
if(NOT TARGET ${component_real_target})
idf_die("Missing component1 target")
endif()
endfunction()
# Add two executables fatfs_example and hello_world_example, generate
# binary images for them and add flash and menuconfig targets.
# After configuration these can be build with
# idf.py hello_world_example_bin
# idf.py fatfs_example_bin
# or simply
# idf.py hello_world_example-flash monitor
# idf.py fatfs-flash monitor
# The menuconfig can be invoked with
# idf.py --no-hints menuconfig-hello_world
# idf.py --no-hints menuconfig-fatfs
# The confserver can be invoked with
# idf.py confserver-hello_world
# idf.py confserver-fatfs
function(test_executable)
idf_build_executable(fatfs_example
COMPONENTS fatfs_example)
idf_build_binary(fatfs_example
TARGET fatfs_example_bin
OUTPUT_FILE fatfs_example.bin)
idf_check_binary_size(fatfs_example_bin)
idf_flash_binary(fatfs_example_bin
TARGET fatfs_example-flash
NAME fatfs_example)
idf_create_menuconfig(fatfs_example
TARGET menuconfig-fatfs)
idf_build_generate_metadata(fatfs_example
OUTPUT_FILE project_description_fatfs.json)
idf_create_confserver(fatfs_example
TARGET confserver-fatfs)
idf_build_executable(hello_world_example
COMPONENTS hello_world_example)
idf_build_binary(hello_world_example
TARGET hello_world_example_bin
OUTPUT_FILE hello_world_example.bin)
idf_check_binary_size(hello_world_example_bin)
idf_flash_binary(hello_world_example_bin
TARGET hello_world_example-flash
NAME hello_world_example)
idf_create_menuconfig(hello_world_example
TARGET menuconfig-hello_world)
idf_build_generate_metadata(hello_world_example
OUTPUT_FILE project_description_hello_world.json)
idf_create_confserver(hello_world_example
TARGET confserver-hello_world)
endfunction()
# Run tests
test_idf_version()
test_python()
test_toolchain()
test_idf_build_library()
test_project_properties()
test_include_component()
test_executable()
# Create default project
idf_project_default()
# The kconfig test also verifies whether kconfig-related targets, such as
# menuconfig, are created. These targets are now generated within
# idf_project_default rather than globally, so this test should be run only
# after the default project is created.
test_kconfig()
# Call this test last because it replaces the ESP system component.
test_component_priority()
__dump_all_properties()
message("ALL TESTS PASSED")
@@ -0,0 +1,2 @@
add_library(${COMPONENT_TARGET} component1.c)
target_include_directories(${COMPONENT_TARGET} PUBLIC ${CMAKE_CURRENT_LIST_DIR})
@@ -0,0 +1,5 @@
menu "component1 configuration"
config COMPONENT1_OPTION
prompt "component1 option"
bool
endmenu
@@ -0,0 +1,11 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
void component1_func(void)
{
printf("component1\n");
}
@@ -0,0 +1,9 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _COMPONENT1_H_
#define _COMPONENT1_H_
void component1_func(void);
#endif
@@ -0,0 +1,5 @@
idf_component_include(component1 INTERFACE component1)
add_library(${COMPONENT_TARGET} component2.c)
target_include_directories(${COMPONENT_TARGET} PUBLIC ${CMAKE_CURRENT_LIST_DIR})
target_link_libraries(${COMPONENT_TARGET} PRIVATE ${component1})
@@ -0,0 +1,5 @@
menu "component2 configuration"
config COMPONENT2_OPTION
prompt "component2 option"
bool
endmenu
@@ -0,0 +1,13 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "component1.h"
void component2_func(void)
{
component1_func();
printf("component2\n");
}
@@ -0,0 +1,10 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _COMPONENT2_H_
#define _COMPONENT2_H_
void component2_func(void);
#endif
@@ -0,0 +1,3 @@
idf_component_register(SRCS "fatfs_getting_started_main.c"
PRIV_REQUIRES vfs fatfs lwip
INCLUDE_DIRS ".")
@@ -0,0 +1,86 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "esp_vfs.h"
#include "esp_vfs_fat.h"
#include "sdkconfig.h"
static const char *TAG = "example";
// Mount path for the partition
const char *base_path = "/spiflash";
// Handle of the wear levelling library instance
static wl_handle_t s_wl_handle = WL_INVALID_HANDLE;
void app_main(void)
{
ESP_LOGI(TAG, "Mounting FAT filesystem");
// To mount device we need name of device partition, define base_path
// and allow format partition in case if it is new one and was not formatted before
const esp_vfs_fat_mount_config_t mount_config = {
.max_files = 4, // Number of files that can be open at a time
.format_if_mount_failed = true, // If true, try to format the partition if mount fails
.allocation_unit_size = CONFIG_WL_SECTOR_SIZE, // Size of allocation unit, cluster size.
.use_one_fat = false, // Use only one FAT table (reduce memory usage), but decrease reliability of file system in case of power failure.
};
// Mount FATFS filesystem located on "storage" partition in read-write mode
esp_err_t err = esp_vfs_fat_spiflash_mount_rw_wl(base_path, "storage", &mount_config, &s_wl_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to mount FATFS (%s)", esp_err_to_name(err));
return;
}
ESP_LOGI(TAG, "Filesystem mounted");
ESP_LOGI(TAG, "Opening file");
const char *filename = "/spiflash/example.txt";
FILE *f = fopen(filename, "wb");
if (f == NULL) {
perror("fopen"); // Print reason why fopen failed
ESP_LOGE(TAG, "Failed to open file for writing");
return;
}
fprintf(f, "Hello World!\n");
fclose(f);
ESP_LOGI(TAG, "File written");
// Open file for reading
ESP_LOGI(TAG, "Reading file");
f = fopen(filename, "r");
if (f == NULL) {
ESP_LOGE(TAG, "Failed to open file for reading");
return;
}
char line[128];
fgets(line, sizeof(line), f);
fclose(f);
// strip newline
char *pos = strchr(line, '\n');
if (pos) {
*pos = '\0';
}
ESP_LOGI(TAG, "Read from file: '%s'", line);
// Unmount FATFS
ESP_LOGI(TAG, "Unmounting FAT filesystem");
ESP_ERROR_CHECK(esp_vfs_fat_spiflash_unmount_rw_wl(base_path, s_wl_handle));
ESP_LOGI(TAG, "Done");
}
@@ -0,0 +1,3 @@
idf_component_register(SRCS "hello_world_main.c"
PRIV_REQUIRES spi_flash
INCLUDE_DIRS "")
@@ -0,0 +1,51 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"
void app_main(void)
{
printf("Hello world!\n");
/* Print chip information */
esp_chip_info_t chip_info;
uint32_t flash_size;
esp_chip_info(&chip_info);
printf("This is %s chip with %d CPU core(s), %s%s%s%s, ",
CONFIG_IDF_TARGET,
chip_info.cores,
(chip_info.features & CHIP_FEATURE_WIFI_BGN) ? "WiFi/" : "",
(chip_info.features & CHIP_FEATURE_BT) ? "BT" : "",
(chip_info.features & CHIP_FEATURE_BLE) ? "BLE" : "",
(chip_info.features & CHIP_FEATURE_IEEE802154) ? ", 802.15.4 (Zigbee/Thread)" : "");
unsigned major_rev = chip_info.revision / 100;
unsigned minor_rev = chip_info.revision % 100;
printf("silicon revision v%d.%d, ", major_rev, minor_rev);
if (esp_flash_get_size(NULL, &flash_size) != ESP_OK) {
printf("Get flash size failed");
return;
}
printf("%" PRIu32 "MB %s flash\n", flash_size / (uint32_t)(1024 * 1024),
(chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external");
printf("Minimum free heap size: %" PRIu32 " bytes\n", esp_get_minimum_free_heap_size());
for (int i = 10; i >= 0; i--) {
printf("Restarting in %d seconds...\n", i);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
printf("Restarting now.\n");
fflush(stdout);
esp_restart();
}
@@ -0,0 +1,3 @@
idf_component_register(SRCS "hello_world_main.c"
PRIV_REQUIRES spi_flash
INCLUDE_DIRS "")
@@ -0,0 +1,51 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"
void app_main(void)
{
printf("Hello world!\n");
/* Print chip information */
esp_chip_info_t chip_info;
uint32_t flash_size;
esp_chip_info(&chip_info);
printf("This is %s chip with %d CPU core(s), %s%s%s%s, ",
CONFIG_IDF_TARGET,
chip_info.cores,
(chip_info.features & CHIP_FEATURE_WIFI_BGN) ? "WiFi/" : "",
(chip_info.features & CHIP_FEATURE_BT) ? "BT" : "",
(chip_info.features & CHIP_FEATURE_BLE) ? "BLE" : "",
(chip_info.features & CHIP_FEATURE_IEEE802154) ? ", 802.15.4 (Zigbee/Thread)" : "");
unsigned major_rev = chip_info.revision / 100;
unsigned minor_rev = chip_info.revision % 100;
printf("silicon revision v%d.%d, ", major_rev, minor_rev);
if (esp_flash_get_size(NULL, &flash_size) != ESP_OK) {
printf("Get flash size failed");
return;
}
printf("%" PRIu32 "MB %s flash\n", flash_size / (uint32_t)(1024 * 1024),
(chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external");
printf("Minimum free heap size: %" PRIu32 " bytes\n", esp_get_minimum_free_heap_size());
for (int i = 10; i >= 0; i--) {
printf("Restarting in %d seconds...\n", i);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
printf("Restarting now.\n");
fflush(stdout);
esp_restart();
}
File diff suppressed because it is too large Load Diff
+16
View File
@@ -0,0 +1,16 @@
{
"COMPONENT_KCONFIGS": "${kconfigs}",
"COMPONENT_KCONFIGS_PROJBUILD": "${kconfig_projbuilds}",
"COMPONENT_SDKCONFIG_RENAMES": "${sdkconfig_renames}",
"COMPONENT_KCONFIGS_EXCLUDED": "${kconfigs_excluded}",
"COMPONENT_KCONFIGS_PROJBUILD_EXCLUDED": "${kconfigs_projbuild_excluded}",
"IDF_TARGET": "${idf_target}",
"IDF_TOOLCHAIN": "${idf_toolchain}",
"IDF_VERSION": "$ENV{IDF_VERSION}",
"IDF_ENV_FPGA": "${idf_env_fpga}",
"IDF_PATH": "${idf_path}",
"COMPONENT_KCONFIGS_SOURCE_FILE": "${kconfigs_path}",
"COMPONENT_KCONFIGS_PROJBUILD_SOURCE_FILE": "${kconfigs_projbuild_path}",
"COMPONENT_KCONFIGS_EXCLUDED_SOURCE_FILE": "${kconfigs_excluded_path}",
"COMPONENT_KCONFIGS_PROJBUILD_EXCLUDED_SOURCE_FILE": "${kconfigs_projbuild_excluded_path}"
}
+41 -22
View File
@@ -1,32 +1,33 @@
#!/usr/bin/env python
#
# SPDX-FileCopyrightText: 2019-2021 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2019-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
from __future__ import print_function, unicode_literals
import argparse
import json
import sys
from io import open
def _prepare_source_files(env_dict, list_separator):
def _prepare_source_files(env_dict: dict[str, str], list_separator: str) -> None:
"""
Prepares source files which are sourced from the main Kconfig because upstream kconfiglib doesn't support sourcing
a file list. The inputs are the same environment variables which are used by kconfiglib:
- COMPONENT_KCONFIGS,
- COMPONENT_KCONFIGS_SOURCE_FILE,
- COMPONENT_KCONFIGS_PROJBUILD,
- COMPONENT_KCONFIGS_PROJBUILD_SOURCE_FILE.
- COMPONENT_KCONFIGS_PROJBUILD_SOURCE_FILE,
- COMPONENT_KCONFIGS_EXCLUDED,
- COMPONENT_KCONFIGS_PROJBUILD_EXCLUDED,
The outputs are written into files pointed by the value of
- COMPONENT_KCONFIGS_SOURCE_FILE,
- COMPONENT_KCONFIGS_PROJBUILD_SOURCE_FILE,
- COMPONENT_KCONFIGS_EXCLUDED_SOURCE_FILE,
- COMPONENT_KCONFIGS_PROJBUILD_EXCLUDED_SOURCE_FILE,
After running this function, COMPONENT_KCONFIGS_SOURCE_FILE and COMPONENT_KCONFIGS_PROJBUILD_SOURCE_FILE will
contain a list of source statements based on the content of COMPONENT_KCONFIGS and COMPONENT_KCONFIGS_PROJBUILD,
respectively. For example, if COMPONENT_KCONFIGS="var1;var2;var3" and
After running this function, all source files will contain a list of source statements based on the
content of their corresponding environment variables. For example, if COMPONENT_KCONFIGS="var1;var2;var3" and
COMPONENT_KCONFIGS_SOURCE_FILE="/path/file.txt" then the content of file /path/file.txt will be:
source "var1"
source "var2"
@@ -36,18 +37,18 @@ def _prepare_source_files(env_dict, list_separator):
Space separated lists are currently only used by the documentation build system (esp-docs).
"""
def _dequote(var):
def _dequote(var: str) -> str:
return var[1:-1] if len(var) > 0 and (var[0], var[-1]) == ('"',) * 2 else var
def _write_source_file(config_var, config_file):
def _write_source_file(config_var: str, config_file: str) -> None:
dequoted_var = _dequote(config_var)
if dequoted_var:
new_content = '\n'.join(['source "{}"'.format(path) for path in dequoted_var.split(list_separator)])
new_content = '\n'.join([f'source "{path}"' for path in dequoted_var.split(list_separator)])
else:
new_content = ''
try:
with open(config_file, 'r', encoding='utf-8') as f:
with open(config_file, encoding='utf-8') as f:
old_content = f.read()
except Exception:
# File doesn't exist or other issue
@@ -62,25 +63,43 @@ def _prepare_source_files(env_dict, list_separator):
try:
_write_source_file(env_dict['COMPONENT_KCONFIGS'], env_dict['COMPONENT_KCONFIGS_SOURCE_FILE'])
_write_source_file(env_dict['COMPONENT_KCONFIGS_PROJBUILD'], env_dict['COMPONENT_KCONFIGS_PROJBUILD_SOURCE_FILE'])
_write_source_file(
env_dict['COMPONENT_KCONFIGS_PROJBUILD'], env_dict['COMPONENT_KCONFIGS_PROJBUILD_SOURCE_FILE']
)
if env_dict.get('COMPONENT_KCONFIGS_EXCLUDED_SOURCE_FILE'):
_write_source_file(
env_dict['COMPONENT_KCONFIGS_EXCLUDED'], env_dict['COMPONENT_KCONFIGS_EXCLUDED_SOURCE_FILE']
)
if env_dict.get('COMPONENT_KCONFIGS_PROJBUILD_EXCLUDED_SOURCE_FILE'):
_write_source_file(
env_dict['COMPONENT_KCONFIGS_PROJBUILD_EXCLUDED'],
env_dict['COMPONENT_KCONFIGS_PROJBUILD_EXCLUDED_SOURCE_FILE'],
)
except KeyError as e:
print('Error:', e, 'is not defined!')
sys.exit(1)
def main():
def main() -> None:
parser = argparse.ArgumentParser(description='Kconfig Source File Generator')
parser.add_argument('--env', action='append', default=[],
help='Environment value', metavar='NAME=VAL')
parser.add_argument('--env', action='append', default=[], help='Environment value', metavar='NAME=VAL')
parser.add_argument('--env-file', type=argparse.FileType('r'),
help='Optional file to load environment variables from. Contents '
'should be a JSON object where each key/value pair is a variable.')
parser.add_argument(
'--env-file',
type=argparse.FileType('r'),
help='Optional file to load environment variables from. Contents '
'should be a JSON object where each key/value pair is a variable.',
)
parser.add_argument('--list-separator', choices=['space', 'semicolon'],
default='space',
help='Separator used in environment list variables (COMPONENT_KCONFIGS, COMPONENT_KCONFIGS_PROJBUILD)')
parser.add_argument(
'--list-separator',
choices=['space', 'semicolon'],
default='space',
help='Separator used in environment list variables '
'(COMPONENT_KCONFIGS, COMPONENT_KCONFIGS_PROJBUILD) '
'TODO: EXCLUDED variants currently disabled',
)
args = parser.parse_args()
+18
View File
@@ -131,6 +131,24 @@ def test_idf_copy(idf_copy):
run_idf_py('build', env=env)
```
### `buildv2_skip` marker
This marker enables the skipping of tests that, for any reason, cannot be
executed with the IDF build system version 2. It accepts an optional string
argument that explains why the test cannot be run with version 2. If no
explanation is provided, a default message is used. This marker is used in
the `pytest_collection_modifyitems` hook to skip tests marked with it when the
`--buildv2` pytest command line option is used. For implementation details,
please refer to `conftest.py`.
```python
@pytest.mark.buildv2_skip
def test_target_guessing()
@pytest.mark.buildv2_skip('This functionality has not been implemented in cmakev2 yet.')
def test_target_guessing()
```
### Build snapshots
`get_snapshot(list_of_globs)` function takes a list of glob expressions, finds the files matching these expressions, and returns a `Snapshot` instance. `Snapshot` instances record file names and their modification timestamps. Two `Snapshot` instances can be compared using `assert_same` and `assert_different` methods:
@@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.22)
# placeholder_before_include_project_cmake
include($ENV{IDF_PATH}/tools/cmakev2/idf.cmake)
# placeholder_after_include_project_cmake
project(build_test_app C CXX ASM)
idf_project_default()
#[[
When doing idf.py add-dependency, the component manager
expects the tools/cmake/project.cmake file to be included
in the project-level CMakeLists.txt file. If it does not
find it, it assumes that that the project-root directory
is a component and crates a manifest in the project-root
directory. Hence, as a workaround, we trick the component
manager by including the tools/cmake/project.cmake file
but return early.
TODO: Remove this workaround (IDF-14072)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
#]]
@@ -0,0 +1 @@
Information about this test app can be found [here](../README.md#application-under-test).
@@ -0,0 +1,5 @@
# placeholder_before_idf_component_register
idf_component_register(SRCS "build_test_app.c"
# placeholder_inside_idf_component_register
)
@@ -0,0 +1,4 @@
# Misspelled Kconfig file checks whether it is picked up by the build system
config FROM_MISSPELLED_KCONFIG
bool "From misspelled Kconfig"
default y
@@ -0,0 +1,12 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
// placeholder_before_main
void app_main(void)
{
// placeholder_inside_main
}
+60 -19
View File
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import datetime
import logging
@@ -10,11 +10,14 @@ from pathlib import Path
from tempfile import mkdtemp
import pytest
from _pytest.config import Config
from _pytest.fixtures import FixtureRequest
from test_build_system_helpers import EnvDict
from _pytest.main import Session
from _pytest.nodes import Item
from test_build_system_helpers import EXT_IDF_PATH
from test_build_system_helpers import get_idf_build_env
from test_build_system_helpers import EnvDict
from test_build_system_helpers import IdfPyFunc
from test_build_system_helpers import get_idf_build_env
from test_build_system_helpers import run_idf_py
@@ -36,18 +39,25 @@ def should_clean_test_dir(request: FixtureRequest) -> bool:
def pytest_addoption(parser: pytest.Parser) -> None:
parser.addoption(
'--work-dir', action='store', default=None,
help='Directory for temporary files. If not specified, an OS-specific '
'temporary directory will be used.'
'--work-dir',
action='store',
default=None,
help='Directory for temporary files. If not specified, an OS-specific temporary directory will be used.',
)
parser.addoption(
'--cleanup-idf-copy', action='store_true',
help='Always clean up the IDF copy after the test. By default, the copy is cleaned up only if the test passes.'
'--cleanup-idf-copy',
action='store_true',
help='Always clean up the IDF copy after the test. By default, the copy is cleaned up only if the test passes.',
)
parser.addoption(
'--buildv2',
action='store_true',
help='Use the IDF build system v2 project for testing.',
)
@pytest.fixture(scope='session')
def _session_work_dir(request: FixtureRequest) -> typing.Generator[typing.Tuple[Path, bool], None, None]:
def _session_work_dir(request: FixtureRequest) -> typing.Generator[tuple[Path, bool], None, None]:
work_dir = request.config.getoption('--work-dir')
if work_dir:
@@ -71,7 +81,7 @@ def _session_work_dir(request: FixtureRequest) -> typing.Generator[typing.Tuple[
@pytest.fixture(name='func_work_dir', autouse=True)
def work_dir(request: FixtureRequest, _session_work_dir: typing.Tuple[Path, bool]) -> typing.Generator[Path, None, None]:
def work_dir(request: FixtureRequest, _session_work_dir: tuple[Path, bool]) -> typing.Generator[Path, None, None]:
session_work_dir, is_temp_dir = _session_work_dir
if request._pyfuncitem.keywords.get('force_temp_work_dir') and not is_temp_dir:
@@ -94,7 +104,10 @@ def work_dir(request: FixtureRequest, _session_work_dir: typing.Tuple[Path, bool
def test_app_copy(func_work_dir: Path, request: FixtureRequest) -> typing.Generator[Path, None, None]:
# by default, use hello_world app and copy it to a temporary directory with
# the name resembling that of the test
copy_from = 'tools/test_build_system/build_test_app'
if request.config.getoption('buildv2', False):
copy_from = 'tools/test_build_system/buildv2_test_app'
else:
copy_from = 'tools/test_build_system/build_test_app'
# sanitize test name in case pytest.mark.parametrize was used
test_name_sanitized = request.node.name.replace('[', '_').replace(']', '')
copy_to = test_name_sanitized + '_app'
@@ -114,7 +127,9 @@ def test_app_copy(func_work_dir: Path, request: FixtureRequest) -> typing.Genera
ignore = shutil.ignore_patterns(
path_to.name,
# also ignore files which may be present in the work directory
'build', 'sdkconfig')
'build',
'sdkconfig',
)
logging.debug(f'copying {path_from} to {path_to}')
shutil.copytree(path_from, path_to, ignore=ignore, symlinks=True)
@@ -127,7 +142,7 @@ def test_app_copy(func_work_dir: Path, request: FixtureRequest) -> typing.Genera
os.chdir(old_cwd)
if should_clean_test_dir(request):
logging.debug('cleaning up work directory after a successful test: {}'.format(path_to))
logging.debug(f'cleaning up work directory after a successful test: {path_to}')
shutil.rmtree(path_to, ignore_errors=True)
@@ -141,8 +156,21 @@ def test_git_template_app(func_work_dir: Path, request: FixtureRequest) -> typin
logging.debug(f'cloning git-template app to {path_to}')
path_to.mkdir()
# No need to clone full repository, just a single master branch
subprocess.run(['git', 'clone', '--single-branch', '-b', 'master', '--depth', '1', 'https://github.com/espressif/esp-idf-template.git', '.'],
cwd=path_to, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
subprocess.run(
[
'git',
'clone',
'--single-branch',
'-b',
'master',
'--depth',
'1',
'https://github.com/espressif/esp-idf-template.git',
'.',
],
cwd=path_to,
capture_output=True,
)
old_cwd = Path.cwd()
os.chdir(path_to)
@@ -152,7 +180,7 @@ def test_git_template_app(func_work_dir: Path, request: FixtureRequest) -> typin
os.chdir(old_cwd)
if should_clean_test_dir(request):
logging.debug('cleaning up work directory after a successful test: {}'.format(path_to))
logging.debug(f'cleaning up work directory after a successful test: {path_to}')
shutil.rmtree(path_to, ignore_errors=True)
@@ -180,7 +208,9 @@ def idf_copy(func_work_dir: Path, request: FixtureRequest) -> typing.Generator[P
path_to.name,
# also ignore the build directories which may be quite large
# plus ignore .git since it is causing trouble when removing on Windows
'**/build', '.git')
'**/build',
'.git',
)
logging.debug(f'copying {path_from} to {path_to}')
shutil.copytree(path_from, path_to, ignore=ignore, symlinks=True)
@@ -193,7 +223,7 @@ def idf_copy(func_work_dir: Path, request: FixtureRequest) -> typing.Generator[P
os.environ['IDF_PATH'] = orig_idf_path
if should_clean_test_dir(request):
logging.debug('cleaning up work directory after a successful test: {}'.format(path_to))
logging.debug(f'cleaning up work directory after a successful test: {path_to}')
shutil.rmtree(path_to, ignore_errors=True)
@@ -204,6 +234,17 @@ def fixture_default_idf_env() -> EnvDict:
@pytest.fixture
def idf_py(default_idf_env: EnvDict) -> IdfPyFunc:
def result(*args: str, check: bool = True, input_str: typing.Optional[str] = None) -> subprocess.CompletedProcess:
def result(*args: str, check: bool = True, input_str: str | None = None) -> subprocess.CompletedProcess:
return run_idf_py(*args, env=default_idf_env, workdir=os.getcwd(), check=check, input_str=input_str) # type: ignore
return result
def pytest_collection_modifyitems(session: Session, config: Config, items: list[Item]) -> None:
if not config.getoption('--buildv2', False):
return
for item in items:
marker = item.get_closest_marker('buildv2_skip')
if marker:
reason = marker.args[0] if marker.args else 'Skipped as this test is specific to build system v1.'
item.add_marker(pytest.mark.skip(reason=reason))
+1
View File
@@ -21,3 +21,4 @@ markers =
idf_copy_with_space: ensures that destination directory where IDF is copied contain space
force_temp_work_dir: force temporary folder as the working directory
with_idf_components: automatically create/delete components under IDF_PATH
buildv2_skip: mark the test to run only when the --buildv2 command line option is not used
+16
View File
@@ -114,6 +114,15 @@ def test_build_skdconfig_phy_init_data(idf_py: IdfPyFunc, test_app_copy: Path) -
logging.info('can build with phy_init_data')
(test_app_copy / 'sdkconfig.defaults').touch()
(test_app_copy / 'sdkconfig.defaults').write_text('CONFIG_ESP32_PHY_INIT_DATA_IN_PARTITION=y')
# The phy_init_data.bin file is generated in the esp_phy component, so it
# should be added to the requirements.
replace_in_file(
test_app_copy / 'main' / 'CMakeLists.txt',
search='# placeholder_inside_idf_component_register',
replace='PRIV_REQUIRES esp_phy',
)
idf_py('reconfigure')
idf_py('build')
assert_built(BOOTLOADER_BINS + APP_BINS + PARTITION_BIN + ['build/phy_init_data.bin'])
@@ -163,6 +172,7 @@ def test_build_fail_on_build_time(idf_py: IdfPyFunc, test_app_copy: Path) -> Non
idf_py('build')
@pytest.mark.buildv2_skip('dfu target not yet added in buildv2')
@pytest.mark.usefixtures('test_app_copy')
def test_build_dfu(idf_py: IdfPyFunc) -> None:
logging.info('DFU build works')
@@ -178,6 +188,7 @@ def test_build_dfu(idf_py: IdfPyFunc) -> None:
assert_built(BOOTLOADER_BINS + APP_BINS + PARTITION_BIN + ['build/dfu.bin'])
@pytest.mark.buildv2_skip('uf2 target not yet added in buildv2')
@pytest.mark.usefixtures('test_app_copy')
def test_build_uf2(idf_py: IdfPyFunc) -> None:
logging.info('UF2 build works')
@@ -199,6 +210,11 @@ def test_build_uf2(idf_py: IdfPyFunc) -> None:
assert_built(BOOTLOADER_BINS + APP_BINS + PARTITION_BIN + ['build/uf2.bin'])
# The bootloader_support component defines its requirements based on the
# sdkconfig values, specifically the CONFIG_APP_BUILD_TYPE_RAM used in this
# test. If CONFIG_APP_BUILD_TYPE_RAM is set, bootloader_support declares a
# dependency on micro-ecc.
@pytest.mark.buildv2_skip('bootloader_support component CMakeLists.txt is broken')
def test_build_loadable_elf(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
logging.info('Loadable ELF build works')
(test_app_copy / 'sdkconfig').write_text(
+10
View File
@@ -13,6 +13,7 @@ from test_build_system_helpers import EnvDict
from test_build_system_helpers import IdfPyFunc
from test_build_system_helpers import append_to_file
from test_build_system_helpers import file_contains
from test_build_system_helpers import replace_in_file
from test_build_system_helpers import run_cmake
from test_build_system_helpers import run_cmake_and_build
from test_build_system_helpers import run_idf_py
@@ -50,6 +51,7 @@ def test_build_custom_cmake_project_host() -> None:
run_cmake_and_build(str(idf_path / 'examples' / 'build_system' / 'cmake' / 'idf_as_lib'), '-G', 'Ninja')
@pytest.mark.buildv2_skip('import_lib example uses cmakev1, not yet updated for buildv2 (IDF-14185)')
def test_build_cmake_library_psram_workaround(test_app_copy: Path) -> None:
logging.info(
'Building a project with CMake library imported and PSRAM workaround, all files compile with workaround'
@@ -75,6 +77,14 @@ def test_build_cmake_library_psram_workaround(test_app_copy: Path) -> None:
def test_build_cmake_library_psram_strategies(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
# Add explicit REQUIRES for cmakev2 compatibility
# Adding a public dependency on esp_psram should propagate the compile options publicly.
replace_in_file(
test_app_copy / 'main' / 'CMakeLists.txt',
'# placeholder_inside_idf_component_register',
'REQUIRES esp_psram',
)
for strategy in ['DUPLDST', 'NOPS', 'MEMW']:
logging.info(f'Test for external libraries in custom CMake projects with PSRAM strategy {strategy}')
(test_app_copy / 'sdkconfig.defaults').write_text(
+2 -1
View File
@@ -71,7 +71,8 @@ def test_idf_build_with_env_var_sdkconfig_defaults(test_app_copy: Path, default_
fw.write('CONFIG_BT_ENABLED=y')
default_idf_env['SDKCONFIG_DEFAULTS'] = 'sdkconfig.test'
run_idf_py('build', env=default_idf_env)
# idf.py reconfigure would generate the sdkconfig file
run_idf_py('reconfigure', env=default_idf_env)
with open(test_app_copy / 'sdkconfig') as fr:
assert 'CONFIG_BT_ENABLED=y' in fr.read()
@@ -187,6 +187,13 @@ class TestOptionalDependencyWithKconfig:
'set(EXTRA_COMPONENT_DIRS foo)',
)
# Add explicit PRIV_REQUIRES for cmakev2 compatibility
replace_in_file(
(test_app_copy / 'main' / 'CMakeLists.txt'),
'# placeholder_inside_idf_component_register',
'PRIV_REQUIRES foo',
)
idf_py('reconfigure')
data = json.load(open(test_app_copy / 'build' / 'project_description.json'))
+39 -4
View File
@@ -48,9 +48,12 @@ def test_component_extra_dirs(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
'# placeholder_before_include_project_cmake',
'set(EXTRA_COMPONENT_DIRS {})'.format(Path('different_main', 'main').as_posix()),
)
ret = idf_py('reconfigure')
assert str((test_app_copy / 'different_main' / 'main').as_posix()) in ret.stdout
assert str((test_app_copy / 'main').as_posix()) not in ret.stdout
idf_py('reconfigure')
# Check project_description.json for component paths
data = json.load(open(test_app_copy / 'build' / 'project_description.json'))
assert str((test_app_copy / 'different_main' / 'main').as_posix()) in data.get('build_component_paths')
assert str((test_app_copy / 'main').as_posix()) not in data.get('build_component_paths')
@pytest.mark.usefixtures('test_app_copy')
@@ -91,6 +94,13 @@ def test_component_sibling_dirs_not_added_to_component_dirs(idf_py: IdfPyFunc, t
(mycomponents_subdir / 'mycomponent').mkdir(parents=True)
(mycomponents_subdir / 'mycomponent' / 'CMakeLists.txt').write_text('idf_component_register()')
# Add PRIV_REQUIRES to main component for cmakev2 compatibility
replace_in_file(
test_app_copy / 'main' / 'CMakeLists.txt',
'# placeholder_inside_idf_component_register',
'PRIV_REQUIRES mycomponent',
)
# first test by adding single component directory to EXTRA_COMPONENT_DIRS
(mycomponents_subdir / 'esp32').mkdir(parents=True)
(mycomponents_subdir / 'esp32' / 'CMakeLists.txt').write_text('idf_component_register()')
@@ -178,6 +188,14 @@ def test_project_components_overrides_extra_components(idf_py: IdfPyFunc, test_a
logging.info('Project components override components defined in EXTRA_COMPONENT_DIRS')
(test_app_copy / 'extra_dir' / 'my_component').mkdir(parents=True)
(test_app_copy / 'extra_dir' / 'my_component' / 'CMakeLists.txt').write_text('idf_component_register()')
# Add PRIV_REQUIRES to main component for cmakev2 compatibility
replace_in_file(
test_app_copy / 'main' / 'CMakeLists.txt',
'# placeholder_inside_idf_component_register',
'PRIV_REQUIRES my_component',
)
replace_in_file(
test_app_copy / 'CMakeLists.txt',
'# placeholder_before_include_project_cmake',
@@ -197,6 +215,9 @@ def test_project_components_overrides_extra_components(idf_py: IdfPyFunc, test_a
assert str((test_app_copy / 'extra_dir' / 'my_component').as_posix()) not in data.get('build_component_paths')
@pytest.mark.buildv2_skip(
'cmakev2 overrides managed components with extra components but both components are included in the build'
)
def test_extra_components_overrides_managed_components(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
logging.info('components defined in EXTRA_COMPONENT_DIRS override managed components')
(test_app_copy / 'main' / 'idf_component.yml').write_text("""
@@ -229,6 +250,14 @@ def test_managed_components_overrides_idf_components(idf_py: IdfPyFunc, test_app
logging.info('Managed components override components defined in IDF_PATH/components')
# created idf component 'cmp' in marker
idf_path = Path(os.environ['IDF_PATH'])
# Add PRIV_REQUIRES to main component for cmakev2 compatibility
replace_in_file(
test_app_copy / 'main' / 'CMakeLists.txt',
'# placeholder_inside_idf_component_register',
'PRIV_REQUIRES cmp',
)
idf_py('reconfigure')
with open(test_app_copy / 'build' / 'project_description.json') as f:
data = json.load(f)
@@ -245,6 +274,7 @@ dependencies:
assert str((idf_path / 'components' / 'cmp').as_posix()) not in data.get('build_component_paths')
@pytest.mark.buildv2_skip('cmakev2 does not override with last added components in the same way as cmakev1')
def test_manifest_local_source_overrides_extra_components(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
(test_app_copy / '..' / 'extra_dir' / 'cmp').mkdir(parents=True)
(test_app_copy / '..' / 'extra_dir' / 'cmp' / 'CMakeLists.txt').write_text('idf_component_register()')
@@ -275,6 +305,7 @@ dependencies:
)
@pytest.mark.buildv2_skip('cmakev2 does not support EXCLUDE_COMPONENTS')
def test_exclude_components_not_passed(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
logging.info('Components in EXCLUDE_COMPONENTS not passed to idf_component_manager')
idf_py('create-component', '-C', 'components', 'to_be_excluded')
@@ -302,9 +333,10 @@ def test_unknown_component_error(idf_py: IdfPyFunc, test_app_copy: Path) -> None
replace='REQUIRES unknown',
)
ret = idf_py('reconfigure', check=False)
assert "Failed to resolve component 'unknown' required by component 'main'" in ret.stderr
assert "Failed to resolve component 'unknown'" in ret.stderr
@pytest.mark.buildv2_skip('not yet implemented in cmakev2')
def test_component_with_improper_dependency(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
# test for __component_validation_check_include_dirs and __component_validation_check_sources
# Checks that the following warnings are produced:
@@ -345,6 +377,7 @@ def test_component_with_improper_dependency(idf_py: IdfPyFunc, test_app_copy: Pa
assert re_source.search(ret.stderr) is not None, f'Expected source file warning not found in: {ret.stderr}'
@pytest.mark.buildv2_skip('not yet implemented in cmakev2')
def test_component_validation_not_run_in_subprojects(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
# test that component validation doesn't run in subprojects like bootloader
logging.info('Check that component validation warnings are not shown in subprojects')
@@ -386,6 +419,7 @@ def test_component_validation_not_run_in_subprojects(idf_py: IdfPyFunc, test_app
assert ret.returncode == 0, 'Build should complete successfully with validation warnings'
@pytest.mark.buildv2_skip('not yet implemented in cmakev2')
def test_component_validation_private_include_dirs(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
# test that component validation works for private include directories
logging.info('Check that component validation warnings are shown for private include directories')
@@ -416,6 +450,7 @@ def test_component_validation_private_include_dirs(idf_py: IdfPyFunc, test_app_c
)
@pytest.mark.buildv2_skip('not yet implemented in cmakev2')
def test_component_validation_finds_right_component(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
# test that __component_validation_get_component_for_path finds the correct component for a given path
#
@@ -1,15 +1,13 @@
# SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import logging
import shutil
from pathlib import Path
from typing import List
from typing import Optional
import pytest
from test_build_system_helpers import EnvDict
from test_build_system_helpers import file_contains
from test_build_system_helpers import IdfPyFunc
from test_build_system_helpers import file_contains
from test_build_system_helpers import run_cmake
ESP32C3_TARGET = 'esp32c3'
@@ -30,12 +28,12 @@ def test_target_from_environment_cmake(default_idf_env: EnvDict) -> None:
env = default_idf_env
env.update({'IDF_TARGET': ESP32S2_TARGET})
run_cmake('-G', 'Ninja', '..', env=env)
assert file_contains('sdkconfig', 'CONFIG_IDF_TARGET="{}"'.format(ESP32S2_TARGET))
assert file_contains('build/CMakeCache.txt', 'IDF_TARGET:STRING={}'.format(ESP32S2_TARGET))
assert file_contains('sdkconfig', f'CONFIG_IDF_TARGET="{ESP32S2_TARGET}"')
assert file_contains('build/CMakeCache.txt', f'IDF_TARGET:STRING={ESP32S2_TARGET}')
def test_target_from_environment_idf_py(idf_py: IdfPyFunc, default_idf_env: EnvDict, test_app_copy: Path) -> None:
def reconfigure_and_check_return_values(errmsg: str, opts: Optional[List[str]] = None) -> None:
def reconfigure_and_check_return_values(errmsg: str, opts: list[str] | None = None) -> None:
opts = opts or []
ret = idf_py(*opts, 'reconfigure', check=False)
assert ret.returncode == 2
@@ -47,36 +45,49 @@ def test_target_from_environment_idf_py(idf_py: IdfPyFunc, default_idf_env: EnvD
cfg_path = (test_app_copy / 'sdkconfig').as_posix()
logging.info("idf.py fails if IDF_TARGET settings don't match the environment")
reconfigure_and_check_return_values("Project sdkconfig '{}' was generated for target '{}', but environment "
"variable IDF_TARGET is set to '{}'.".format(cfg_path, ESP32S2_TARGET,
ESP32_TARGET))
reconfigure_and_check_return_values(
f"Project sdkconfig '{cfg_path}' was generated for target '{ESP32S2_TARGET}', but environment "
f"variable IDF_TARGET is set to '{ESP32_TARGET}'."
)
logging.info("idf.py fails if IDF_TARGET settings in CMakeCache.txt don't match the environment")
(test_app_copy / 'sdkconfig').write_text('CONFIG_IDF_TARGET="{}"'.format(ESP32_TARGET))
reconfigure_and_check_return_values("Target settings are not consistent: '{}' in the environment, "
"'{}' in CMakeCache.txt.".format(ESP32_TARGET, ESP32S2_TARGET))
(test_app_copy / 'sdkconfig').write_text(f'CONFIG_IDF_TARGET="{ESP32_TARGET}"')
reconfigure_and_check_return_values(
f"Target settings are not consistent: '{ESP32_TARGET}' in the environment, "
f"'{ESP32S2_TARGET}' in CMakeCache.txt."
)
logging.info("idf.py fails if IDF_TARGET settings in CMakeCache.txt don't match the sdkconfig")
default_idf_env.pop('IDF_TARGET')
reconfigure_and_check_return_values("Project sdkconfig '{}' was generated for target '{}', but CMakeCache.txt "
"contains '{}'.".format(cfg_path, ESP32_TARGET, ESP32S2_TARGET))
reconfigure_and_check_return_values(
f"Project sdkconfig '{cfg_path}' was generated for target '{ESP32_TARGET}', but CMakeCache.txt "
f"contains '{ESP32S2_TARGET}'."
)
logging.info('idf.py fails if IDF_TARGET is set differently in environment and with -D option')
(test_app_copy / 'sdkconfig').write_text('CONFIG_IDF_TARGET="{}"'.format(ESP32S2_TARGET))
(test_app_copy / 'sdkconfig').write_text(f'CONFIG_IDF_TARGET="{ESP32S2_TARGET}"')
default_idf_env.update({'IDF_TARGET': ESP32S2_TARGET})
reconfigure_and_check_return_values("Target '{}' specified on command line is not consistent with target '{}' "
'in the environment.'.format(ESP32_TARGET, ESP32S2_TARGET),
['-D', 'IDF_TARGET={}'.format(ESP32_TARGET)])
reconfigure_and_check_return_values(
(
f"Target '{ESP32_TARGET}' specified on command line is not consistent with target "
f"'{ESP32S2_TARGET}' in the environment."
),
['-D', f'IDF_TARGET={ESP32_TARGET}'],
)
logging.info('idf.py fails if IDF_TARGET is set differently in CMakeCache.txt and with -D option')
default_idf_env.pop('IDF_TARGET')
reconfigure_and_check_return_values("Target '{}' specified on command line is not consistent with "
"target '{}' in CMakeCache.txt.".format(ESP32_TARGET, ESP32S2_TARGET),
['-D', 'IDF_TARGET={}'.format(ESP32_TARGET)])
reconfigure_and_check_return_values(
(
f"Target '{ESP32_TARGET}' specified on command line is not consistent with target "
f"'{ESP32S2_TARGET}' in CMakeCache.txt."
),
['-D', f'IDF_TARGET={ESP32_TARGET}'],
)
def test_target_consistency_cmake(default_idf_env: EnvDict, test_app_copy: Path) -> None:
def reconfigure_and_check_return_values(errmsg: str, opts: Optional[List[str]] = None) -> None:
def reconfigure_and_check_return_values(errmsg: str, opts: list[str] | None = None) -> None:
opts = opts or []
ret = run_cmake(*opts, '-G', 'Ninja', '..', env=default_idf_env, check=False)
assert ret.returncode == 1
@@ -88,86 +99,90 @@ def test_target_consistency_cmake(default_idf_env: EnvDict, test_app_copy: Path)
logging.info("cmake fails if IDF_TARGET settings don't match the environment")
default_idf_env.update({'IDF_TARGET': ESP32S2_TARGET})
reconfigure_and_check_return_values(f"IDF_TARGET '{ESP32_TARGET}' in CMake cache does not "
f"match currently selected IDF_TARGET '{ESP32S2_TARGET}'")
reconfigure_and_check_return_values(
f"IDF_TARGET '{ESP32_TARGET}' in CMake cache does not match currently selected IDF_TARGET '{ESP32S2_TARGET}'"
)
logging.info("cmake fails if IDF_TARGET settings don't match the sdkconfig")
default_idf_env.pop('IDF_TARGET')
(test_app_copy / 'sdkconfig').write_text(f'CONFIG_IDF_TARGET="{ESP32S2_TARGET}"')
reconfigure_and_check_return_values(f"Target '{ESP32S2_TARGET}' in sdkconfig '{cfg_path}' does not "
f"match currently selected IDF_TARGET '{ESP32_TARGET}'.")
reconfigure_and_check_return_values(
f"Target '{ESP32S2_TARGET}' in sdkconfig '{cfg_path}' does not "
f"match currently selected IDF_TARGET '{ESP32_TARGET}'."
)
logging.info("cmake fails if IDF_TOOLCHAIN settings don't match the environment")
(test_app_copy / 'sdkconfig').write_text(f'CONFIG_IDF_TARGET="{ESP32_TARGET}"')
default_idf_env.update({'IDF_TOOLCHAIN': 'clang'})
reconfigure_and_check_return_values("IDF_TOOLCHAIN 'gcc' in CMake cache does not match "
"currently selected IDF_TOOLCHAIN 'clang'")
reconfigure_and_check_return_values(
"IDF_TOOLCHAIN 'gcc' in CMake cache does not match currently selected IDF_TOOLCHAIN 'clang'"
)
logging.info("cmake fails if IDF_TARGET settings don't match CMAKE_TOOLCHAIN_FILE")
default_idf_env.pop('IDF_TOOLCHAIN')
reconfigure_and_check_return_values("CMAKE_TOOLCHAIN_FILE 'toolchain-esp32' does not "
f"match currently selected IDF_TARGET '{ESP32S2_TARGET}'",
['-D', f'IDF_TARGET={ESP32S2_TARGET}',
'-D', 'SDKCONFIG=custom_sdkconfig'])
reconfigure_and_check_return_values(
f"CMAKE_TOOLCHAIN_FILE 'toolchain-esp32' does not match currently selected IDF_TARGET '{ESP32S2_TARGET}'",
['-D', f'IDF_TARGET={ESP32S2_TARGET}', '-D', 'SDKCONFIG=custom_sdkconfig'],
)
def test_target_precedence(idf_py: IdfPyFunc, default_idf_env: EnvDict, test_app_copy: Path) -> None:
logging.info('IDF_TARGET takes precedence over the value of CONFIG_IDF_TARGET in sdkconfig.defaults')
(test_app_copy / 'sdkconfig.defaults').write_text('CONFIG_IDF_TARGET="{}"'.format(ESP32S2_TARGET))
(test_app_copy / 'sdkconfig.defaults').write_text(f'CONFIG_IDF_TARGET="{ESP32S2_TARGET}"')
default_idf_env.update({'IDF_TARGET': ESP32_TARGET})
idf_py('reconfigure')
assert file_contains('sdkconfig', 'CONFIG_IDF_TARGET="{}"'.format(ESP32_TARGET))
assert file_contains('sdkconfig', 'CONFIG_IDF_TARGET_{}=y'.format(ESP32_TARGET.upper()))
assert file_contains('build/CMakeCache.txt', 'IDF_TARGET:STRING={}'.format(ESP32_TARGET))
assert file_contains('sdkconfig', f'CONFIG_IDF_TARGET="{ESP32_TARGET}"')
assert file_contains('sdkconfig', f'CONFIG_IDF_TARGET_{ESP32_TARGET.upper()}=y')
assert file_contains('build/CMakeCache.txt', f'IDF_TARGET:STRING={ESP32_TARGET}')
def test_target_using_D_parameter(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
logging.info('Can set target using idf.py -D')
idf_py('-DIDF_TARGET={}'.format(ESP32S2_TARGET), 'reconfigure')
assert file_contains('sdkconfig', 'CONFIG_IDF_TARGET="{}"'.format(ESP32S2_TARGET))
assert file_contains('build/CMakeCache.txt', 'IDF_TARGET:STRING={}'.format(ESP32S2_TARGET))
idf_py(f'-DIDF_TARGET={ESP32S2_TARGET}', 'reconfigure')
assert file_contains('sdkconfig', f'CONFIG_IDF_TARGET="{ESP32S2_TARGET}"')
assert file_contains('build/CMakeCache.txt', f'IDF_TARGET:STRING={ESP32S2_TARGET}')
logging.info('Can set target using -D as subcommand parameter for idf.py')
clean_app(test_app_copy)
idf_py('reconfigure', '-DIDF_TARGET={}'.format(ESP32S2_TARGET))
assert file_contains('sdkconfig', 'CONFIG_IDF_TARGET="{}"'.format(ESP32S2_TARGET))
assert file_contains('build/CMakeCache.txt', 'IDF_TARGET:STRING={}'.format(ESP32S2_TARGET))
idf_py('reconfigure', f'-DIDF_TARGET={ESP32S2_TARGET}')
assert file_contains('sdkconfig', f'CONFIG_IDF_TARGET="{ESP32S2_TARGET}"')
assert file_contains('build/CMakeCache.txt', f'IDF_TARGET:STRING={ESP32S2_TARGET}')
@pytest.mark.usefixtures('test_app_copy')
def test_target_using_settarget_parameter_alternative_name(idf_py: IdfPyFunc) -> None:
logging.info('idf.py understands alternative target names')
idf_py('set-target', ESP32S2_TARGET.upper())
assert file_contains('sdkconfig', 'CONFIG_IDF_TARGET="{}"'.format(ESP32S2_TARGET))
assert file_contains('build/CMakeCache.txt', 'IDF_TARGET:STRING={}'.format(ESP32S2_TARGET))
assert file_contains('sdkconfig', f'CONFIG_IDF_TARGET="{ESP32S2_TARGET}"')
assert file_contains('build/CMakeCache.txt', f'IDF_TARGET:STRING={ESP32S2_TARGET}')
@pytest.mark.usefixtures('test_app_copy')
def test_target_using_settarget_parameter(idf_py: IdfPyFunc, default_idf_env: EnvDict) -> None:
logging.info('Can set target using idf.py set-target')
idf_py('set-target', ESP32S2_TARGET)
assert file_contains('sdkconfig', 'CONFIG_IDF_TARGET="{}"'.format(ESP32S2_TARGET))
assert file_contains('build/CMakeCache.txt', 'IDF_TARGET:STRING={}'.format(ESP32S2_TARGET))
assert file_contains('sdkconfig', f'CONFIG_IDF_TARGET="{ESP32S2_TARGET}"')
assert file_contains('build/CMakeCache.txt', f'IDF_TARGET:STRING={ESP32S2_TARGET}')
logging.info('Can guess target from sdkconfig, if CMakeCache does not exist')
idf_py('fullclean')
idf_py('reconfigure')
assert file_contains('sdkconfig', 'CONFIG_IDF_TARGET="{}"'.format(ESP32S2_TARGET))
assert file_contains('build/CMakeCache.txt', 'IDF_TARGET:STRING={}'.format(ESP32S2_TARGET))
assert file_contains('sdkconfig', f'CONFIG_IDF_TARGET="{ESP32S2_TARGET}"')
assert file_contains('build/CMakeCache.txt', f'IDF_TARGET:STRING={ESP32S2_TARGET}')
logging.info('Can set target if IDF_TARGET env is set and old sdkconfig exists')
default_idf_env.update({'IDF_TARGET': ESP32_TARGET})
idf_py('set-target', ESP32_TARGET)
default_idf_env.pop('IDF_TARGET')
assert file_contains('sdkconfig', 'CONFIG_IDF_TARGET="{}"'.format(ESP32_TARGET))
assert file_contains('sdkconfig', f'CONFIG_IDF_TARGET="{ESP32_TARGET}"')
def test_target_using_sdkconfig(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
logging.info('Can set the default target using sdkconfig.defaults')
(test_app_copy / 'sdkconfig.defaults').write_text('CONFIG_IDF_TARGET="{}"'.format(ESP32S2_TARGET))
(test_app_copy / 'sdkconfig.defaults').write_text(f'CONFIG_IDF_TARGET="{ESP32S2_TARGET}"')
idf_py('reconfigure')
assert file_contains('sdkconfig', 'CONFIG_IDF_TARGET="{}"'.format(ESP32S2_TARGET))
assert file_contains('sdkconfig', 'CONFIG_IDF_TARGET_{}=y'.format(ESP32S2_TARGET.upper()))
assert file_contains('sdkconfig', f'CONFIG_IDF_TARGET="{ESP32S2_TARGET}"')
assert file_contains('sdkconfig', f'CONFIG_IDF_TARGET_{ESP32S2_TARGET.upper()}=y')
def test_target_guessing(idf_py: IdfPyFunc, test_app_copy: Path, default_idf_env: EnvDict) -> None:
@@ -178,31 +193,32 @@ def test_target_guessing(idf_py: IdfPyFunc, test_app_copy: Path, default_idf_env
"""
logging.info('Can guess target from sdkconfig.defaults')
(test_app_copy / 'sdkconfig.defaults').write_text('CONFIG_IDF_TARGET="{}"'.format(ESP32_TARGET))
(test_app_copy / 'sdkconfig.defaults').write_text(f'CONFIG_IDF_TARGET="{ESP32_TARGET}"')
idf_py('reconfigure')
assert file_contains('sdkconfig', 'CONFIG_IDF_TARGET="{}"'.format(ESP32_TARGET))
assert file_contains('build/CMakeCache.txt', 'IDF_TARGET:STRING={}'.format(ESP32_TARGET))
assert file_contains('sdkconfig', f'CONFIG_IDF_TARGET="{ESP32_TARGET}"')
assert file_contains('build/CMakeCache.txt', f'IDF_TARGET:STRING={ESP32_TARGET}')
logging.info('Can guess target from SDKCONFIG_DEFAULTS environment variable')
(test_app_copy / 'sdkconfig1').write_text('NOTHING HERE')
(test_app_copy / 'sdkconfig2').write_text('CONFIG_IDF_TARGET="{}"'.format(ESP32S2_TARGET))
(test_app_copy / 'sdkconfig2').write_text(f'CONFIG_IDF_TARGET="{ESP32S2_TARGET}"')
clean_app(test_app_copy)
default_idf_env.update({'SDKCONFIG_DEFAULTS': 'sdkconfig1;sdkconfig2'})
idf_py('reconfigure')
assert file_contains('sdkconfig', 'CONFIG_IDF_TARGET="{}"'.format(ESP32S2_TARGET))
assert file_contains('build/CMakeCache.txt', 'IDF_TARGET:STRING={}'.format(ESP32S2_TARGET))
assert file_contains('sdkconfig', f'CONFIG_IDF_TARGET="{ESP32S2_TARGET}"')
assert file_contains('build/CMakeCache.txt', f'IDF_TARGET:STRING={ESP32S2_TARGET}')
default_idf_env.pop('SDKCONFIG_DEFAULTS')
logging.info('Can guess target from SDKCONFIG_DEFAULTS using -D')
(test_app_copy / 'sdkconfig3').write_text('CONFIG_IDF_TARGET="{}"'.format(ESP32S2_TARGET))
(test_app_copy / 'sdkconfig4').write_text('CONFIG_IDF_TARGET="{}"'.format(ESP32S3_TARGET))
(test_app_copy / 'sdkconfig3').write_text(f'CONFIG_IDF_TARGET="{ESP32S2_TARGET}"')
(test_app_copy / 'sdkconfig4').write_text(f'CONFIG_IDF_TARGET="{ESP32S3_TARGET}"')
clean_app(test_app_copy)
idf_py('-D', 'SDKCONFIG_DEFAULTS=sdkconfig4;sdkconfig3', 'reconfigure')
assert file_contains('sdkconfig', 'CONFIG_IDF_TARGET="{}"'.format(ESP32S3_TARGET))
assert file_contains('build/CMakeCache.txt', 'IDF_TARGET:STRING={}'.format(ESP32S3_TARGET))
assert file_contains('sdkconfig', f'CONFIG_IDF_TARGET="{ESP32S3_TARGET}"')
assert file_contains('build/CMakeCache.txt', f'IDF_TARGET:STRING={ESP32S3_TARGET}')
logging.info('Can guess target from custom sdkconfig')
(test_app_copy / 'sdkconfig5').write_text('CONFIG_IDF_TARGET="{}"'.format(ESP32C3_TARGET))
(test_app_copy / 'sdkconfig5').write_text(f'CONFIG_IDF_TARGET="{ESP32C3_TARGET}"')
clean_app(test_app_copy)
idf_py('-D', 'SDKCONFIG=sdkconfig5', '-D', 'SDKCONFIG_DEFAULTS=sdkconfig4;sdkconfig3', 'reconfigure')
assert file_contains('sdkconfig5', 'CONFIG_IDF_TARGET="{}"'.format(ESP32C3_TARGET))
assert file_contains('build/CMakeCache.txt', 'IDF_TARGET:STRING={}'.format(ESP32C3_TARGET))
assert file_contains('sdkconfig5', f'CONFIG_IDF_TARGET="{ESP32C3_TARGET}"')
assert file_contains('build/CMakeCache.txt', f'IDF_TARGET:STRING={ESP32C3_TARGET}')
+64 -38
View File
@@ -1,13 +1,15 @@
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import logging
import os
import subprocess
import typing
from pathlib import Path
import pytest
from test_build_system_helpers import EnvDict, IdfPyFunc, append_to_file, replace_in_file
from test_build_system_helpers import EnvDict
from test_build_system_helpers import IdfPyFunc
from test_build_system_helpers import append_to_file
from test_build_system_helpers import replace_in_file
#############################################################################################
@@ -38,16 +40,20 @@ def test_versions_get_default_version(idf_py: IdfPyFunc, test_app_copy: Path) ->
# 4. Verify that the app version is picked up from the git describe command
#
#############################################################################################
def test_versions_get_version_from_git_describe(idf_py: IdfPyFunc,
test_git_template_app: Path,
env: typing.Optional[EnvDict] = None) -> None:
def test_versions_get_version_from_git_describe(
idf_py: IdfPyFunc, test_git_template_app: Path, env: EnvDict | None = None
) -> None:
logging.info('Verify that the version of app can be set from git describe')
idf_ret = idf_py('reconfigure')
env_dict = dict(**os.environ)
if env:
env_dict.update(env)
git_ret = subprocess.run(['git', 'describe', '--always', '--tags', '--dirty'],
cwd=test_git_template_app, env=env_dict, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
git_ret = subprocess.run(
['git', 'describe', '--always', '--tags', '--dirty'],
cwd=test_git_template_app,
env=env_dict,
capture_output=True,
)
assert f'App "app-template" version: {git_ret.stdout.decode("utf-8")}' in idf_ret.stdout
@@ -64,40 +70,52 @@ def test_versions_get_version_from_git_describe(idf_py: IdfPyFunc,
# 6. Verify that cmake correctly flags invalid inputs for the VERSION argument and accepts valid inputs for the same
#
#############################################################################################
@pytest.mark.buildv2_skip('cmakev2 does not need to parse the VERSION argument in the build system')
def test_versions_get_version_from_version_arg(idf_py: IdfPyFunc, test_git_template_app: Path) -> None:
logging.info('Verify that the VERSION argument in project() is correctly parsed by cmake')
# empty VERSION argument
replace_in_file((test_git_template_app / 'CMakeLists.txt'), 'project(app-template)',
'__parse_and_store_version_arg(app-template VERSION)')
replace_in_file(
(test_git_template_app / 'CMakeLists.txt'),
'project(app-template)',
'__parse_and_store_version_arg(app-template VERSION)',
)
# Invalid VERSION argument format
append_to_file((test_git_template_app / 'CMakeLists.txt'),
'\n__parse_and_store_version_arg(app-tempplate VERSION 1..2)')
append_to_file(
(test_git_template_app / 'CMakeLists.txt'), '\n__parse_and_store_version_arg(app-tempplate VERSION 1..2)'
)
# Invalid VERSION argument format
append_to_file((test_git_template_app / 'CMakeLists.txt'),
'\n__parse_and_store_version_arg(app-template VERSION version_text)')
append_to_file(
(test_git_template_app / 'CMakeLists.txt'), '\n__parse_and_store_version_arg(app-template VERSION version_text)'
)
# Invalid VERSION argument format
append_to_file((test_git_template_app / 'CMakeLists.txt'),
'\n__parse_and_store_version_arg(app-template VERSION 1.2.3.4.5)')
append_to_file((test_git_template_app / 'CMakeLists.txt'),
'\n__parse_and_store_version_arg(app-template VERSION 0)')
append_to_file(
(test_git_template_app / 'CMakeLists.txt'), '\n__parse_and_store_version_arg(app-template VERSION 1.2.3.4.5)'
)
append_to_file(
(test_git_template_app / 'CMakeLists.txt'), '\n__parse_and_store_version_arg(app-template VERSION 0)'
)
# Valid VERSION argument format
append_to_file((test_git_template_app / 'CMakeLists.txt'),
'\n__parse_and_store_version_arg(app-template VERSION 0.1)')
append_to_file(
(test_git_template_app / 'CMakeLists.txt'), '\n__parse_and_store_version_arg(app-template VERSION 0.1)'
)
# Valid VERSION argument format
append_to_file((test_git_template_app / 'CMakeLists.txt'),
'\n__parse_and_store_version_arg(app-template VERSION 0.1.2)')
append_to_file(
(test_git_template_app / 'CMakeLists.txt'), '\n__parse_and_store_version_arg(app-template VERSION 0.1.2)'
)
# Valid VERSION argument format
append_to_file((test_git_template_app / 'CMakeLists.txt'),
'\n__parse_and_store_version_arg(app-template VERSION 0.1.2.3)')
append_to_file(
(test_git_template_app / 'CMakeLists.txt'), '\n__parse_and_store_version_arg(app-template VERSION 0.1.2.3)'
)
# project() call with valid VERSION argument format
append_to_file((test_git_template_app / 'CMakeLists.txt'),
'\nproject(app-template VERSION 0.1.2.3)')
append_to_file((test_git_template_app / 'CMakeLists.txt'), '\nproject(app-template VERSION 0.1.2.3)')
with pytest.raises(subprocess.CalledProcessError) as e:
idf_py('reconfigure')
assert 'VERSION keyword not followed by a value or was followed by a value that expanded to nothing.' in e.stdout
assert (
'VERSION keyword not followed by a value or was followed by a value that expanded to nothing.' in e.stdout
)
assert 'Version "1..2" format invalid' in e.stderr
assert 'Version "version_text" format invalid' in e.stderr
assert 'Version "1.2.3.4.5" format invalid' in e.stderr
@@ -123,12 +141,13 @@ def test_versions_get_version_from_version_arg(idf_py: IdfPyFunc, test_git_templ
#############################################################################################
def test_versions_get_version_from_version_file(idf_py: IdfPyFunc, test_git_template_app: Path) -> None:
logging.info('Verify that the version of app can be set from version.txt file')
replace_in_file((test_git_template_app / 'CMakeLists.txt'), 'project(app-template)',
'project(app-template VERSION 0.1.2.3)')
replace_in_file(
(test_git_template_app / 'CMakeLists.txt'), 'project(app-template)', 'project(app-template VERSION 0.1.2.3)'
)
(test_git_template_app / 'version.txt').write_text('project_version_from_txt')
idf_ret = idf_py('reconfigure')
assert f'App "app-template" version: project_version_from_txt' in idf_ret.stdout
assert 'App "app-template" version: project_version_from_txt' in idf_ret.stdout
#############################################################################################
@@ -146,13 +165,16 @@ def test_versions_get_version_from_version_file(idf_py: IdfPyFunc, test_git_temp
#############################################################################################
def test_versions_get_version_from_top_level_cmake(idf_py: IdfPyFunc, test_git_template_app: Path) -> None:
logging.info('Verify that the version of app can be set from PROJECT_VER in CMakeLists.txt')
replace_in_file((test_git_template_app / 'CMakeLists.txt'), 'project(app-template)',
'set(PROJECT_VER project_version_from_CMakeLists)')
replace_in_file(
(test_git_template_app / 'CMakeLists.txt'),
'project(app-template)',
'set(PROJECT_VER project_version_from_CMakeLists)',
)
append_to_file((test_git_template_app / 'CMakeLists.txt'), 'project(app-template VERSION 0.1.2.3)')
(test_git_template_app / 'version.txt').write_text('project_version_from_txt')
idf_ret = idf_py('reconfigure')
assert f'App "app-template" version: project_version_from_CMakeLists' in idf_ret.stdout
assert 'App "app-template" version: project_version_from_CMakeLists' in idf_ret.stdout
#############################################################################################
@@ -172,11 +194,15 @@ def test_versions_get_version_from_top_level_cmake(idf_py: IdfPyFunc, test_git_t
#############################################################################################
def test_versions_get_version_from_kconfig_option(idf_py: IdfPyFunc, test_git_template_app: Path) -> None:
logging.info('Verify that the version of app can be set from Kconfig option')
replace_in_file((test_git_template_app / 'CMakeLists.txt'), 'project(app-template)',
'set(PROJECT_VER project_version_from_CMakeLists)')
replace_in_file(
(test_git_template_app / 'CMakeLists.txt'),
'project(app-template)',
'set(PROJECT_VER project_version_from_CMakeLists)',
)
append_to_file((test_git_template_app / 'CMakeLists.txt'), 'project(app-template VERSION 0.1.2.3)')
(test_git_template_app / 'sdkconfig.defaults').write_text('\n'.join(['CONFIG_APP_PROJECT_VER_FROM_CONFIG=y',
'CONFIG_APP_PROJECT_VER="project_version_from_Kconfig"']))
(test_git_template_app / 'sdkconfig.defaults').write_text(
'\n'.join(['CONFIG_APP_PROJECT_VER_FROM_CONFIG=y', 'CONFIG_APP_PROJECT_VER="project_version_from_Kconfig"'])
)
idf_ret = idf_py('reconfigure')
assert f'App "app-template" version: project_version_from_Kconfig' in idf_ret.stdout
assert 'App "app-template" version: project_version_from_Kconfig' in idf_ret.stdout