docs(cmakev2): Restructure build system v2 documentation into a guide

Split the single build-system-v2 page into a landing page that dispatches
to focused how-to guides (creating a project, creating components,
configuring dependencies, third-party libraries, ESP-IDF as a library,
multiple binaries) and reference pages (design and architecture, breaking
changes, and the generated API reference). Mirror the new structure on
zh_CN and update the API Guides table of contents.

Signed-off-by: Frantisek Hrbata <frantisek.hrbata@espressif.com>
This commit is contained in:
Frantisek Hrbata
2026-06-24 09:13:01 +02:00
parent f2c3fd3966
commit f8adfd176e
36 changed files with 1468 additions and 538 deletions
@@ -0,0 +1,56 @@
API Reference
*************
This page is an automatically generated reference for the public API of Build System v2, extracted from the CMake source in ``tools/cmakev2``. For how these are used in practice, see the guides on the :doc:`Build System v2 <index>` page.
.. contents::
:local:
:depth: 1
.. _cmakev2_functions:
Functions
=========
.. _cmakev2_macros:
Macros
======
.. _cmakev2_variables:
Variables
=========
.. _cmakev2_build_properties:
Build Properties
================
Build properties are global to the project and are read and written with :cmakev2:ref:`idf_build_get_property` and :cmakev2:ref:`idf_build_set_property`.
.. _cmakev2_component_properties:
Component Properties
====================
Component properties are attached to a single component and are read and written with :cmakev2:ref:`idf_component_get_property` and :cmakev2:ref:`idf_component_set_property`.
.. cmakev2:include:: ../../../../tools/cmakev2/build.cmake
.. cmakev2:include:: ../../../../tools/cmakev2/compat.cmake
.. cmakev2:include:: ../../../../tools/cmakev2/component.cmake
.. cmakev2:include:: ../../../../tools/cmakev2/dfu.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/size.cmake
.. cmakev2:include:: ../../../../tools/cmakev2/uf2.cmake
.. cmakev2:include:: ../../../../tools/cmakev2/utilities.cmake
.. cmakev2:include:: ../../../../tools/cmakev2/component_validation.cmake
@@ -0,0 +1,166 @@
Breaking Changes
****************
Most components written for :doc:`Build System v1 </api-guides/build-system>` build under v2 without modification. There are, however, design differences between v1 and v2 that may require changes to a v1 component. This page is a catalog of those differences and how to adapt to each. To keep a single component building under both v1 and v2, see :doc:`managing-compatibility`. The concepts behind these changes are described in :doc:`design`.
.. _cmakev2-post-elf-unavailable:
``idf_build_add_post_elf_dependency`` and ``idf_build_get_post_elf_dependencies`` are Unavailable
=================================================================================================
In v1, components that need to run a step after the executable is linked but before the binary image is generated use ``idf_build_add_post_elf_dependency`` to register a dependency and ``idf_build_get_post_elf_dependencies`` to retrieve the list of such dependencies (see the :doc:`Build System v1 </api-guides/build-system>` API). These functions are **not available** in v2.
In v2, use a build event callback instead. Register a ``POST_ELF`` callback with :cmakev2:ref:`idf_component_register_build_event_callback` in the component's ``project_include.cmake``. The callback receives the executable target name; use it to attach a ``POST_BUILD`` command (for example with ``add_custom_command(TARGET ... POST_BUILD ...)``) or to add custom targets that depend on the executable. This achieves the same ordering, run after the ELF and before the binary, without relying on internal build properties. See :doc:`build-event-callbacks`.
.. _cmakev2-build-components:
The ``BUILD_COMPONENTS`` Build Property is Unavailable
======================================================
In v1, the build system collects the components built by the project in the ``BUILD_COMPONENTS`` build property. This is achieved by evaluating components early using CMake script mode and collecting them based on the dependencies provided in :cmakev2:ref:`idf_component_register`. The ``BUILD_COMPONENTS`` are later evaluated again using CMake's ``add_subdirectory``. This approach imposes several restrictions. First, components cannot express dependencies based on Kconfig variables, because Kconfig variables are not known during the early evaluation. Second, CMake script mode does not allow 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.
v2 removes this two-stage component evaluation, so the ``BUILD_COMPONENTS`` build property does not exist. Any attempt to obtain it with :cmakev2:ref:`idf_build_get_property` results in an error. ``BUILD_COMPONENTS`` was primarily used in v1 by components to discover which other components were being built and to adjust their behavior, for example by adding source files. In v2, this can be replaced with the ``$<TARGET_EXISTS:tgt>`` generator expression.
For example, in v1 the ``esp_eth`` component is compiled with the additional ``esp_eth_netif_glue.c`` file when the ``esp_netif`` component is also being built:
.. 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 work with v2, the use 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 works for both v1 and v2. ``IDF_BUILD_V2`` is used here only to show the two different approaches.
.. _cmakev2-early-expansion:
The ``CMAKE_BUILD_EARLY_EXPANSION`` Variable is Never Set
=========================================================
In v1, each component is evaluated twice: an early pass in CMake script mode that collects the component's requirements, followed by the regular pass that evaluates the component with ``add_subdirectory`` and defines its targets. v1 sets the ``CMAKE_BUILD_EARLY_EXPANSION`` variable during the early pass, and components use it to guard work that must run only in the regular pass, such as defining targets or reading build-time state:
.. code-block:: cmake
if(NOT CMAKE_BUILD_EARLY_EXPANSION)
# In v1, this runs only in the regular pass, not the early one.
# ...
endif()
v2 uses :ref:`single-pass evaluation <cmakev2-term-single-pass-evaluation>`: each component is evaluated once, as ordinary CMake code, so there is no early pass and ``CMAKE_BUILD_EARLY_EXPANSION`` is never set. This affects the two ways the variable is used:
* ``if(NOT CMAKE_BUILD_EARLY_EXPANSION)`` (the usual form) is always true, so its body runs once, exactly as in v1's regular pass. This form needs no change and remains valid under both v1 and v2.
* ``if(CMAKE_BUILD_EARLY_EXPANSION)`` runs only during v1's early pass and never runs in v2. Code that relied on it must be reworked, as v2 has no early pass.
.. _cmakev2-kconfig-file-names:
Standardized ``Kconfig`` and ``Kconfig.projbuild`` File Names
=============================================================
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 build are collected during early component evaluation, which allows custom names for the Kconfig files to be specified. 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 convention, or by including them from Kconfig files that do.
.. _cmakev2-config-visibility:
Component Configuration Visibility
==================================
In v1, the ``sdkconfig`` is generated from the Kconfig files of the components that are part of the build. This means that if a component is not included in the build, for instance because it is not required as a dependency of another component, its configuration is not visible or available when components are evaluated. In v2, the ``sdkconfig`` is generated from the Kconfig files of all available components. This means that the presence of a configuration option does not guarantee that the component providing it is participating in the build, unlike in v1.
For example, a component might link ``vfs`` only when ``CONFIG_VFS_SUPPORT_IO`` is set. This works in v1 because the option is visible only when ``vfs`` is part of the build:
.. code-block:: cmake
if(CONFIG_VFS_SUPPORT_IO)
target_link_libraries(${COMPONENT_LIB} PUBLIC idf::vfs)
endif()
In v2 the option is visible even when ``vfs`` is not in the build, so the condition can be true while the ``idf::vfs`` target does not exist. Because a configuration option no longer implies that its component is present, a component that needs ``vfs`` when the option is set must bring it into the build explicitly with :cmakev2:ref:`idf_component_include` (see :doc:`component-dependencies`):
.. code-block:: cmake
if(CONFIG_VFS_SUPPORT_IO)
idf_component_include(vfs)
target_link_libraries(${COMPONENT_LIB} PUBLIC idf::vfs)
endif()
.. 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 another component's functionality is available simply because its Kconfig variables are set. For example, if ``CONFIG_VFS_SUPPORT_IO`` 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 build and that the component declares a dependency on ``vfs`` in its ``CMakeLists.txt``.
.. _cmakev2-recursive-evaluation:
Recursive Evaluation of Components
==================================
In v1, the components participating in the build are collected in the ``BUILD_COMPONENTS`` build property during the early evaluation phase, prior to their evaluation with ``add_subdirectory``. This means v1 is aware of all components participating in the build and their dependencies before CMake evaluates them, which allows v1 to evaluate components non-recursively 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. It is therefore important to ensure that all variables used by a component are properly initialized before use. 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)
.. _cmakev2-project-include-order:
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 component's ``project_include.cmake`` is included only after the ``project_include.cmake`` files of its dependencies, 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 build, and in the order in which the components are discovered. 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
===========================
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.
.. _cmakev2-optional-requires:
The Behavior of ``idf_component_optional_requires`` has Changed
===============================================================
In v1, the ``idf_component_optional_requires`` function adds a dependency on a specified component only if that component is already included in the build, for instance because it is already required by another component. To achieve this, v1 examines the ``BUILD_COMPONENTS`` build property, which is generated during the early evaluation phase and lists all components involved in the build.
In v2, there is no early collection phase and ``BUILD_COMPONENTS`` does not exist. The build system discovers components as it evaluates dependencies, so v2 cannot use the same "only if already in the build" check and has to choose a different rule.
The build system supports two behaviors, controlled by the ``IDF_COMPONENT_OPTIONAL_REQUIRES_MODE`` build property:
* **IMMEDIATE (default)**: When a component calls ``idf_component_optional_requires(type req_component)``, the build system includes ``req_component`` and links it to the caller if it is recognized (discovered). No check is made whether the rest of the project actually needs that component. This is safe for multi-binary projects (multiple executables or binaries), but it can pull in more components than necessary and increase build time.
* **DEFERRED**: The build system does not include or link immediately. It records the request and resolves it later in :cmakev2:ref:`idf_build_library`: the optional component is linked only if it ends up in that library's dependency graph. This matches v1 semantics and keeps the number of linked components minimal. It **must not** be used when building more than one library (see below).
A multi-binary project is one that creates more than one executable or binary, for example several application executables built from the same tree (see :doc:`multiple-binaries`). Such a project calls :cmakev2:ref:`idf_build_library` or :cmakev2:ref:`idf_build_executable` more than once. In v2, component targets are shared globally across all libraries. If ``IDF_COMPONENT_OPTIONAL_REQUIRES_MODE`` is set to **DEFERRED**, the build system resolves optional requirements when it processes each library. When it processes the second or a later library, it may add new links to component targets that are already used by the first library. The first library's metadata (such as the list of linker fragments or linked components) was already computed when that library was processed and is not updated. As a result, linker script generation and section placement for the first library can be incorrect or stale. For this reason, DEFERRED mode is not allowed when more than one library is built; the build fails with an error in that case. **IMMEDIATE** mode does not have this problem, because optional requirements are applied during component evaluation, before any per-library metadata is computed. Its side effect is that it can pull in more components than necessary and increase build time.
:cmakev2:ref:`idf_project_default` (the usual entry point for a single-executable project) sets ``IDF_COMPONENT_OPTIONAL_REQUIRES_MODE`` to **DEFERRED** before building the default executable when no libraries have been created yet. So if your project uses ``idf_project_default()`` and builds only one executable, you get DEFERRED behavior automatically and do not need to do anything.
If you do not use :cmakev2:ref:`idf_project_default` and instead call :cmakev2:ref:`idf_project_init` and then the lower-level API (:cmakev2:ref:`idf_build_executable`, :cmakev2:ref:`idf_build_library`) yourself, the default mode is **IMMEDIATE**. If you build only one library or executable and want the same efficient, v1-like behavior as ``idf_project_default``, set the mode to DEFERRED yourself after project init:
.. code-block:: cmake
idf_project_init()
idf_build_set_property(IDF_COMPONENT_OPTIONAL_REQUIRES_MODE DEFERRED)
idf_build_executable(my_app COMPONENTS main ...)
# ... rest of your project ...
Do **not** set ``IDF_COMPONENT_OPTIONAL_REQUIRES_MODE`` to ``DEFERRED`` if you build multiple libraries; the build will error. Keep the default IMMEDIATE in that case.
.. _Local Directory Dependencies: https://docs.espressif.com/projects/idf-component-manager/en/latest/reference/manifest_file.html#local-directory-dependencies
@@ -0,0 +1,32 @@
.. _cmakev2-build-event-callbacks:
Using Build Event Callbacks
***************************
A component can register a callback that the build system invokes at a specific point in the build. This is the v2 way to run a custom step on the linked application, for example running a tool on the executable, without relying on internal build targets or properties.
Registering a Callback
======================
A component registers a callback in its ``project_include.cmake`` with :cmakev2:ref:`idf_component_register_build_event_callback`. The callback must be a CMake function defined in the same file. At the specified event, the build system invokes the callback and passes the relevant CMake target as the first argument.
.. code-block:: cmake
:caption: project_include.cmake
function(my_post_elf_hook target)
add_custom_command(TARGET ${target} POST_BUILD
COMMAND my_tool "$<TARGET_FILE:${target}>"
COMMENT "Running my_tool on the executable")
endfunction()
idf_component_register_build_event_callback(EVENT POST_ELF CALLBACK my_post_elf_hook)
Supported Events
================
``POST_ELF``
Fired after the executable target is created and linked, but before the binary (``.bin``) image is generated. The callback receives the executable target name. Use it to act on the ELF, for example by attaching a ``POST_BUILD`` command to the executable with ``add_custom_command(TARGET ... POST_BUILD ...)``, or to add custom targets that depend on the executable.
``POST_ELF`` is currently the only supported event. Additional events may be added in the future when required.
In v1, this kind of post-link step used ``idf_build_add_post_elf_dependency``, which is not available in v2; see :ref:`cmakev2-post-elf-unavailable`.
@@ -0,0 +1,82 @@
Configuring Component Dependencies
**********************************
A component declares the other components it uses, and v2 lets those declarations depend on the project configuration. Expressing dependencies in terms of Kconfig options, so that the set of components built into a project follows its configuration, is the main new capability of v2. This page covers how to declare dependencies and, in particular, how to make them conditional on configuration.
.. important::
Configuration-conditional dependencies are a v2-only feature. A component that makes its dependencies conditional on ``CONFIG_*`` options will not work as expected under v1. To keep such a component working under both v1 and v2, see :doc:`managing-compatibility`.
How Dependencies Are Declared
=============================
A component brings another component into the build and links it. With :cmakev2:ref:`idf_component_register`, this is the ``REQUIRES`` and ``PRIV_REQUIRES`` arguments (see :ref:`cmakev2-component-compatible`). A native CMake component does it explicitly, by calling :cmakev2:ref:`idf_component_include` and linking the dependency's ``idf::<name>`` interface target (see :ref:`cmakev2-component-native`):
.. code-block:: cmake
idf_component_include(log)
target_link_libraries(${COMPONENT_TARGET} PRIVATE idf::log)
With ``idf_component_register``, the :ref:`common components <cmakev2-term-common-components>` (``freertos``, ``log``, and so on) are added automatically, as in v1. A native CMake component receives nothing automatically and must include every component it uses with :cmakev2:ref:`idf_component_include`, including the common ones.
Configuration-Driven Dependencies
==================================
A dependency is declared in one of two ways (see `How Dependencies Are Declared`_): with the ``REQUIRES`` or ``PRIV_REQUIRES`` arguments of :cmakev2:ref:`idf_component_register`, or with :cmakev2:ref:`idf_component_include` in a native CMake component. Because the configuration of all discovered components is available while a component is evaluated, either form can be made conditional on ``CONFIG_*`` options.
The option that drives the dependency is declared in a Kconfig file (see :doc:`/api-guides/kconfig/index`):
.. code-block:: kconfig
:caption: main/Kconfig.projbuild
config EXAMPLE_ENABLE_LOGGING
bool "Enable Logging Utility Component"
default y
Using ``idf_component_register``
--------------------------------
A component that uses :cmakev2:ref:`idf_component_register` builds its ``PRIV_REQUIRES`` list (or ``REQUIRES`` for a public dependency) conditionally and passes it to the call. This is usually the most natural form for a component coming from v1:
.. code-block:: cmake
set(reqs)
if(CONFIG_EXAMPLE_ENABLE_LOGGING)
list(APPEND reqs logging_util)
endif()
idf_component_register(SRCS main.c PRIV_REQUIRES ${reqs})
Initialize the list with ``set(reqs)`` before appending to it, so it does not inherit a value from an enclosing component's scope; see :ref:`cmakev2-recursive-evaluation`.
Using ``idf_component_include``
-------------------------------
A native CMake component expresses the same condition around the :cmakev2:ref:`idf_component_include` call and the ``target_link_libraries``. The :example:`conditional_component example <build_system/cmakev2/features/conditional_component>` is written this way: its ``main`` component depends on the ``logging_util`` and ``math_util`` components only when the corresponding options are enabled.
.. code-block:: cmake
:caption: main/CMakeLists.txt
add_library(${COMPONENT_TARGET} STATIC main.c)
if(CONFIG_EXAMPLE_ENABLE_LOGGING)
idf_component_include(logging_util)
target_link_libraries(${COMPONENT_TARGET} PRIVATE idf::logging_util)
endif()
In both forms, the component's source code uses the same option to compile the dependent code conditionally:
.. code-block:: c
#ifdef CONFIG_EXAMPLE_ENABLE_LOGGING
#include "logging_util.h"
#endif
.. important::
A dependency must be included with :cmakev2:ref:`idf_component_include` before it is used. In v2 the configuration of every discovered component is visible, so a ``CONFIG_*`` option being set does not mean the component that defines it is part of the build. Do not assume a component is available just because its configuration option is set; include it explicitly. This visibility change from v1 is detailed in :ref:`cmakev2-config-visibility`.
Optional Dependencies
=====================
A component can declare an optional dependency with :cmakev2:ref:`idf_component_optional_requires`, which links another component only if it is already part of the build. This function exists mainly for backward compatibility with v1 and should generally be avoided in v2; prefer making dependencies conditional on configuration options, as described above. When it is used, v2 resolves the dependency in one of two modes, IMMEDIATE or DEFERRED, depending on how the project is built; the modes and their constraints are described in :ref:`cmakev2-optional-requires`.
@@ -0,0 +1,321 @@
Creating a New Component
************************
A component is a reusable unit that the build system compiles into a library and links into the application that uses it. There are two ways to write one:
- **Backward-compatible**, using the :cmakev2:ref:`idf_component_register` function. This is the recommended approach and the one most components use. A component written this way builds under both v1 and v2 as long as it stays within the features v1 also supports. Relying on v2-only behavior, such as configuration-conditional dependencies, can break it under v1; see :doc:`managing-compatibility`.
- **Native CMake**, written as a plain CMake static library. This gives full access to native CMake, but on its own the component builds only under v2. It can still be made to work under v1; see :doc:`managing-compatibility`.
Prefer :cmakev2:ref:`idf_component_register`. Choose a native CMake component only when it needs CMake features that :cmakev2:ref:`idf_component_register` does not expose.
Component Layout
================
A component is a directory that contains a ``CMakeLists.txt`` file, its source files, and a directory with its public header files. For example:
.. code-block:: text
my_component
├── CMakeLists.txt
├── my_component.c
└── include
└── my_component.h
The component's name is the name of its directory. The build system finds components in the project's ``components`` directory, in the directories listed in ``EXTRA_COMPONENT_DIRS``, and among the components bundled with ESP-IDF. How components are discovered is described in :doc:`design`. The layout is the same for both kinds of component; only the content of ``CMakeLists.txt`` differs, as described in the sections below.
.. _cmakev2-component-compatible:
Backward-Compatible Component
=============================
The :cmakev2:ref:`idf_component_register` function declares a component's sources, include directories, and dependencies. It is the recommended way to write a component and behaves the same under both v1 and v2, so a component written this way builds under either build system. For a complete, runnable example, see the ``main`` component of the hello_world project: :example_file:`main/CMakeLists.txt <build_system/cmakev2/get-started/hello_world/main/CMakeLists.txt>`.
Registering the Component
-------------------------
The component's ``CMakeLists.txt`` calls :cmakev2:ref:`idf_component_register` to declare its source files, include directories, and dependencies:
.. code-block:: cmake
:caption: my_component/CMakeLists.txt
idf_component_register(SRCS "my_component.c"
INCLUDE_DIRS "include"
REQUIRES mbedtls)
The most commonly used arguments are:
``SRCS``
The component's source files. They are compiled into the component's library.
``INCLUDE_DIRS``
Public include directories. They are added to the include path of this component and of every component that depends on it.
``PRIV_INCLUDE_DIRS``
Private include directories. They are added only to this component's own include path.
``REQUIRES``
Public component dependencies. Their headers and libraries are available to this component and propagate to components that depend on it.
``PRIV_REQUIRES``
Private component dependencies. They are available to this component only and do not propagate to its dependents.
A component that has no source files of its own, for example a configuration-only or header-only component, can call :cmakev2:ref:`idf_component_register` with only ``INCLUDE_DIRS``, or with no arguments at all.
:cmakev2:ref:`idf_component_register` accepts further arguments, including ``SRC_DIRS`` and ``EXCLUDE_SRCS`` for selecting sources by directory, ``LDFRAGMENTS`` for linker fragments, ``EMBED_FILES`` and ``EMBED_TXTFILES`` for embedding binary data, ``REQUIRED_IDF_TARGETS`` to restrict the component to specific chips, and ``WHOLE_ARCHIVE``. These behave as in v1. For the full list of arguments, see the :cmakev2:ref:`API Reference <idf_component_register>`.
Declaring Dependencies
----------------------
A component declares the other components it uses with ``REQUIRES`` (public) and ``PRIV_REQUIRES`` (private). For example, an application's ``main`` component that uses two other components privately:
.. code-block:: cmake
:caption: main/CMakeLists.txt
idf_component_register(SRCS "app_main.c"
PRIV_REQUIRES component1 component2)
Like v1, the common components (``freertos``, ``log``, and so on) are added automatically; every other component a component uses must be listed in ``REQUIRES`` or ``PRIV_REQUIRES``.
To keep a component backward compatible with v1, its dependencies cannot be made conditional on configuration options, which v1 does not support. The same applies to other v2-only features. How to handle the differences between v1 and v2 in a single component is described in :doc:`managing-compatibility`.
Differences from v1
-------------------
:cmakev2:ref:`idf_component_register` behaves the same in v1 and v2, with one exception: the v2 version does not support the ``KCONFIG`` and ``KCONFIG_PROJBUILD`` arguments for Kconfig files whose names do not follow the standard ``Kconfig`` and ``Kconfig.projbuild`` convention. See :ref:`cmakev2-kconfig-file-names`.
.. important::
Make sure every variable used in the component's ``CMakeLists.txt`` is initialized before use. In v2 a component may be evaluated within the variable scope of another component, so a variable can already hold a value set by that component. A common mistake is appending to a CMake list without first initializing it. This is explained in :ref:`cmakev2-recursive-evaluation`.
For the complete list of differences that may require changes to a v1 component, see :doc:`breaking-changes`. To keep a single component building under both v1 and v2, see :doc:`managing-compatibility`.
.. _cmakev2-component-native:
Native CMake Component
======================
A native CMake component creates its library target directly with ``add_library``, instead of calling :cmakev2:ref:`idf_component_register`. This gives full access to native CMake, which is useful when :cmakev2:ref:`idf_component_register` does not provide enough control. On its own such a component builds only under v2; see :doc:`managing-compatibility` to keep it working under v1 as well.
This section builds a complete example component, ``esp_target_info``, step by step, and exercises the main features available to a native component: creating the component target, depending on other components, expressing a dependency conditionally, and setting component properties such as linker fragments and linker scripts. A configuration-driven native component is also available as the :example:`conditional_component example <build_system/cmakev2/features/conditional_component>`.
The Example Component
---------------------
The ``esp_target_info`` component provides a single function, ``print_esp_target_info``, which prints basic information about the target: its name, the number of CPU cores, the free heap size, and the flash size. Its directory is laid out as follows:
.. code-block:: text
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 declares the single public function:
.. 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`` source defines it. It reads the target name from the ``CONFIG_IDF_TARGET`` configuration option, the CPU core count from the ``esp_hw_support`` component, the free heap size from ``esp_system``, and the flash size from ``spi_flash``:
.. 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 the symbol ``esp_target_chip_info`` as an alias for ``esp_chip_info``. The source calls the alias instead of ``esp_chip_info`` directly. This serves no real purpose other than to demonstrate a working linker script:
.. code-block:: text
:caption: esp_target_info.ld
esp_target_chip_info = esp_chip_info;
The ``linker.lf`` linker fragment places ``print_esp_target_info`` in IRAM instead of flash, again only for demonstration. For the linker fragment format, see :doc:`/api-guides/linker-script-generation`:
.. code-block:: text
:caption: linker.lf
[mapping:esp_target_info]
archive: libesp_target_info.a
entries:
print_esp_target_info (noflash)
The rest of this section builds the component's ``CMakeLists.txt`` that ties these files together, one step at a time.
The Component Target
--------------------
When the build system evaluates a component, it sets the :cmakev2:ref:`COMPONENT_TARGET` variable to the name of the library target that the component must create. The component creates this target, typically with ``add_library``, and the build system links it into the component's interface target so that other components can depend on it. Other components refer to this component through its interface alias ``idf::<name>``, never through ``COMPONENT_TARGET`` directly.
The first step creates the static library from the component's source and exposes its public headers (highlighted lines):
.. 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")
``target_include_directories(... PUBLIC ...)`` makes the ``include`` directory part of the include path of this component and of every component that links it, which is the native equivalent of the ``INCLUDE_DIRS`` argument of :cmakev2:ref:`idf_component_register`. Use ``PRIVATE`` for include directories that should not be visible to dependents.
Depending on Other Components
-----------------------------
``esp_target_info`` uses functionality from ``esp_hw_support``, ``esp_system``, and ``spi_flash``, so it must declare its dependencies on them. A native component does this in two steps: it includes each component with :cmakev2:ref:`idf_component_include`, then links the component's ``idf::<name>`` interface target. The highlighted lines add these dependencies:
.. 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::
Unlike v1, v2 adds no dependency automatically. In v1, a ``main`` component with no declared dependencies receives every component in the build as a dependency, and dependencies declared in ``idf_component.yml`` for the component manager are added automatically. In v2, every dependency a component uses must be declared explicitly.
:cmakev2:ref:`idf_component_include` evaluates the named component exactly once: it calls CMake's ``add_subdirectory`` for the component and links the ``COMPONENT_TARGET`` it creates into its interface target. Other components reference that interface target through the ``idf::<name>`` alias, as in the ``target_link_libraries`` call above. Link the interface ``PRIVATE`` for a private dependency, or ``PUBLIC`` for one that should propagate to dependents.
:cmakev2:ref:`idf_component_include` can also store the interface target name in a variable through its ``INTERFACE`` option, which is convenient when the target is passed to other CMake commands, stored in a list, or referenced programmatically:
.. code-block:: cmake
idf_component_include(spi_flash INTERFACE spi_flash_iface)
target_link_libraries(${COMPONENT_TARGET} PRIVATE ${spi_flash_iface})
Because the configuration of all discovered components is available while a component is evaluated, a dependency can be made conditional on a ``CONFIG_*`` option. The component must still be included before it is linked:
.. code-block:: cmake
if(CONFIG_VFS_SUPPORT_IO)
idf_component_include(vfs)
target_link_libraries(${COMPONENT_TARGET} PRIVATE idf::vfs)
endif()
.. important::
Always include a component before using it. In v2 the configuration of every discovered component is visible, so a ``CONFIG_*`` option being set does not mean the component that defines it is part of the build. Configuration-driven dependencies are covered in detail in :doc:`component-dependencies`.
Setting Component Properties
----------------------------
A native component configures build behavior through component properties, set with :cmakev2:ref:`idf_component_set_property`. The highlighted lines set the properties for ``esp_target_info`` and force a symbol to be retained:
.. 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")
:cmakev2:ref:`WHOLE_ARCHIVE`
Keep every object file of the component's library in the final binary, even if no symbol references it. This is used for link-time registration, where objects register themselves through constructors or linker-section entries. The :example:`plugins example <build_system/cmakev2/features/plugins>` relies on it.
:cmakev2:ref:`LDFRAGMENTS`
Linker fragment files for the component, processed by the linker script generator. Here it adds ``linker.lf``. See :doc:`/api-guides/linker-script-generation`.
:cmakev2:ref:`LINKER_SCRIPTS`
Linker script files added to the link command for the component. Here it adds ``esp_target_info.ld``.
The final line forces the linker to keep ``esp_chip_info`` in the binary even when section garbage collection (``--gc-sections``) is enabled. This is needed because ``esp_target_info.ld`` defines ``esp_target_chip_info`` as an alias for ``esp_chip_info``; without forcing the reference, the underlying ``esp_chip_info`` function could be discarded as unused. ``target_link_options(... INTERFACE "SHELL:-u esp_chip_info")`` adds the ``-u esp_chip_info`` undefined-symbol reference to every target that links the component.
The full list of component properties is in the :ref:`API Reference <cmakev2_component_properties>`. To run a custom step at a point in the build, such as acting on the linked executable, a component can register a build event callback; see :doc:`build-event-callbacks`.
How the Component is Built
--------------------------
A component is compiled and linked only if it is required, directly or transitively, by a component that is being built. :cmakev2:ref:`idf_project_default` builds the application from the ``main`` component, so a native component becomes part of the application when ``main`` or one of its dependencies includes it. ``main`` is a convention of :cmakev2:ref:`idf_project_default`, not a requirement of the build system; a project that drives the build with the lower-level API can build the application from any component (see :doc:`multiple-binaries` and :doc:`idf-as-library`). The difference between discovering a component and including it is described in :doc:`design`.
@@ -0,0 +1,67 @@
Creating a New Project
**********************
This page shows how to create a new ESP-IDF project that uses Build System v2. A v2 project has the same layout as a v1 project; only the top-level ``CMakeLists.txt`` differs. To convert an existing v1 project to v2, see :doc:`updating-project`.
Project Structure
=================
A project is a directory that contains a top-level ``CMakeLists.txt``, a ``main`` component, and optionally a ``components`` directory with additional components. The minimal ``hello_world`` project looks like this:
.. code-block:: text
hello_world
├── CMakeLists.txt
└── main
├── CMakeLists.txt
└── hello_world_main.c
The top-level ``CMakeLists.txt`` configures the build system and defines the application. The ``main`` component holds the application's entry point and is built and linked automatically. The complete project is available as :example:`get-started/hello_world <build_system/cmakev2/get-started/hello_world>`.
The Project CMakeLists.txt
==========================
The following minimal top-level ``CMakeLists.txt`` is sufficient for most projects:
.. code-block:: cmake
cmake_minimum_required(VERSION 3.22)
include($ENV{IDF_PATH}/tools/cmakev2/idf.cmake)
project(hello_world C CXX ASM)
idf_project_default()
The order of these commands is important:
#. ``cmake_minimum_required`` sets the minimum required CMake version. It must come first.
#. ``include($ENV{IDF_PATH}/tools/cmakev2/idf.cmake)`` loads the build system. It performs the build system initialization and toolchain configuration that must be completed before CMake's ``project()`` command. This line is what selects v2; a v1 project includes ``tools/cmake/project.cmake`` instead.
#. ``project(<name> C CXX ASM)`` runs CMake's project setup: it sets project-related variables and initializes the toolchain for the listed languages. ESP-IDF sources use C, C++, and assembly, so all three must be listed; omitting one leaves CMake with no toolchain for that language and the build fails. The project name becomes the application and binary image name.
#. :cmakev2:ref:`idf_project_default` builds the default application from the ``main`` component and its transitive dependencies, generates the binary image, and adds the usual targets such as ``flash`` and ``menuconfig``.
For finer control over what is built, for example to produce more than one binary, call the lower-level functions instead of :cmakev2:ref:`idf_project_default`; see :doc:`multiple-binaries` and :doc:`idf-as-library`. The full build flow is described in :doc:`design`.
The main Component
==================
The application's entry point lives in the ``main`` component. :cmakev2:ref:`idf_project_default` builds the application from ``main`` and its dependencies, which is why a project that uses ``idf_project_default`` has a ``main`` component. The build system itself does not require a component named ``main``; this is a convention of ``idf_project_default``, and a project that drives the build with the lower-level API can build its application from any component (see :doc:`multiple-binaries` and :doc:`idf-as-library`). In ``hello_world``, ``main`` registers a single source file and a private dependency:
.. code-block:: cmake
:caption: main/CMakeLists.txt
idf_component_register(SRCS "hello_world_main.c"
PRIV_REQUIRES spi_flash
INCLUDE_DIRS "")
This is the recommended way to declare a component, and it works under both v1 and v2. For details on writing components, see :doc:`creating-component`.
Building and Flashing
=====================
A v2 project is built with ``idf.py``, exactly as a v1 project:
.. code-block:: bash
idf.py set-target <target>
idf.py build
idf.py flash monitor
The available actions (``build``, ``flash``, ``monitor``, ``menuconfig``, ``size``, and others) are the same as for v1 and are described in :doc:`/api-guides/tools/idf-py`.
@@ -0,0 +1,206 @@
.. _cmakev2-design:
Design and Architecture
***********************
This page explains how Build System v2 works internally: the stages it runs through, how it models components, how it evaluates and links them, and how configuration is handled. It is a conceptual overview. For task oriented instructions, see the guides on the :doc:`Build System v2 <index>` page; for the exact function and variable reference, see :doc:`api`; for the differences from v1 that may require changes to a component, see :doc:`breaking-changes`.
Overview
========
v2 builds a project from components. A component is a reusable, separately compiled unit of code: a directory that contains a ``CMakeLists.txt`` file. The build system discovers the available components, evaluates the ones the project needs, builds each into a library, and links them into an application executable, from which the final binary image is generated.
Three design choices distinguish v2 from v1 and shape the rest of this document:
- **Single-pass component evaluation.** v2 evaluates each component once, as ordinary CMake code. v1 evaluated components twice: an early pass in CMake script mode to collect dependencies, followed by the real pass. Removing the early pass makes a component's behavior easier to predict and enables the other two changes below.
- **Global configuration visibility.** The project configuration (``sdkconfig``) is generated from the Kconfig of every discovered component, not only the components linked into the build. Configuration can therefore be queried before the dependency graph is known, which is what allows dependencies to be expressed in terms of configuration.
- **Native CMake components.** Because there is no early script-mode pass, a component can be a plain CMake target created with ``add_library``, with full access to native CMake features. The :cmakev2:ref:`idf_component_register` function is still provided for components that must also build under v1.
At a high level, a build proceeds through the following stages:
.. code-block:: text
components -> discovery -> configuration -> evaluation -> libraries -> executable -> image
The Build Process
=================
A v2 build runs in three stages. The first two are set up by the project's top level ``CMakeLists.txt``:
.. code-block:: cmake
cmake_minimum_required(VERSION 3.22)
include($ENV{IDF_PATH}/tools/cmakev2/idf.cmake) # stage 1
project(my_project C CXX ASM) # CMake project setup
idf_project_default() # stages 2 and 3
Stage 1: Infrastructure Initialization
--------------------------------------
Including ``idf.cmake`` prepares the build system before CMake's ``project()`` command runs. This stage does not look at the project's components yet. It:
- loads the v2 CMake modules (component, build, kconfig, project, manager, compat, ldgen, and the image helpers);
- creates the ``idf_build_properties`` interface target that holds global build properties (see `Properties`_), and sets ``IDF_PATH``, ``PREFIX``, ``PROJECT_DIR``, and ``BUILD_DIR``;
- sets the build system version properties ``IDF_BUILD_V2``, ``IDF_BUILD_VER`` (``2``), and ``IDF_BUILD_VER_TAG``;
- determines and validates ``IDF_TARGET``, selects the matching toolchain file, and checks the Python environment and Git submodules.
``idf.cmake`` must be included before ``project()`` because it configures the toolchain that ``project()`` then uses.
Stage 2: Project Initialization
-------------------------------
:cmakev2:ref:`idf_project_init` runs after ``project()`` (it is called for you by :cmakev2:ref:`idf_project_default`). This is where the project's components come into play. In order, it:
1. discovers components and initializes each one (see `The Component Model`_), without evaluating them;
2. generates the initial ``sdkconfig`` from the Kconfig of all discovered components (see `Configuration and Visibility`_);
3. runs the component manager if enabled, which may add managed components and regenerate ``sdkconfig`` until the component set converges (see `Component Manager Integration`_);
4. includes the generated ``sdkconfig.cmake`` so that ``CONFIG_*`` values become CMake variables;
5. derives the global compile options, compile definitions, and link options from the configuration;
6. includes the ``project_include.cmake`` file of every discovered component, in discovery order, in the global scope.
Because step 6 evaluates files in the global scope, :cmakev2:ref:`idf_project_init` is a macro and must be called from the project's top level ``CMakeLists.txt``. Note that ``project_include.cmake`` files are included for all discovered components in discovery order, not in dependency order as in v1; see :ref:`cmakev2-project-include-order`.
Stage 3: Build Definition
-------------------------
The final stage defines what is actually built. :cmakev2:ref:`idf_project_default` builds the default application from the ``main`` component and its dependencies and adds the binary, flash, and utility targets. Internally it calls :cmakev2:ref:`idf_build_executable` (see `Libraries, Executables, and Linking`_). Projects that need more than one binary, or that drive the build from an external CMake project, call the lower level functions directly; see :doc:`multiple-binaries` and :doc:`idf-as-library`.
The Component Model
===================
A component is a directory that contains a ``CMakeLists.txt`` file. The component's name is the name of its directory. Each component is built into its own library (typically a static library, or an interface library when it has no source files) and can declare dependencies on other components.
Component Sources and Precedence
--------------------------------
The build system looks for components in several locations, each associated with a *source* that carries a priority. From highest to lowest priority:
.. list-table::
:header-rows: 1
:widths: 30 14 56
* - Source
- Priority
- Where the components come from
* - ``project_components``
- 3 (highest)
- The project's ``main`` and ``components`` directories (or ``COMPONENT_DIRS``)
* - ``project_extra_components``
- 2
- Directories listed in ``EXTRA_COMPONENT_DIRS``
* - ``project_managed_components``
- 1
- Components fetched by the component manager
* - ``idf_components``
- 0 (lowest)
- Components bundled with ESP-IDF (``$IDF_PATH/components``)
When two directories provide a component with the same name, the higher priority one wins and shadows the other; two components of the same name at the same priority are an error. This lets a project override a bundled ESP-IDF component by placing a component of the same name in its own ``components`` directory.
Components published under a namespace (for example ``espressif__led_strip``) are also reachable under their short name (``led_strip``) when that short name is unambiguous.
.. _cmakev2-design-discovery-inclusion:
Discovery vs. Inclusion
-----------------------
Discovery and inclusion are distinct, and the distinction is central to v2.
- **Discovery** registers a component: the build system records its directory, Kconfig files, and ``project_include.cmake``, creates its interface target, and makes its configuration available. A discovered component is *known* but is not built.
- **Inclusion** evaluates a component: the build system calls ``add_subdirectory`` on it, which runs its ``CMakeLists.txt`` and creates its library target. Only included components are compiled and linked.
All available components are discovered, but only the components actually required by the application are included. For example, a default build of the ``hello_world`` example discovers around 150 components yet includes only about 55, the ones that ``main`` transitively needs. The rest stay configurable (their options still appear in ``menuconfig``) but contribute no code.
Interface Targets and the Component Library
-------------------------------------------
Each component is represented by two CMake targets:
- An **interface target**, named ``idf_<name>``, created at discovery. It carries the component's properties and is what other components link against. It has a convenience alias ``idf::<name>``.
- A **component library target**, whose name is given to the component in the ``COMPONENT_TARGET`` variable. The component is responsible for creating this target (for example with ``add_library``); it holds the component's compiled code.
When a component is included, the build system links its library target into its interface target. A component that depends on another therefore links the dependency's ``idf::<name>`` interface, and CMake propagates the dependency's include directories and library transitively. Other components reference a component only through its interface target, never its library target directly.
Single-Pass Component Evaluation
================================
A component is evaluated exactly once. :cmakev2:ref:`idf_component_include` performs the evaluation: it calls ``add_subdirectory`` the first time the component is requested, and on any later request it returns immediately because the component is already evaluated. A per-component flag records that the component has been included, so repeated requests from different dependents are cheap and never re-run the component's ``CMakeLists.txt``.
Evaluation is recursive and depth first. While a component is being evaluated, its own calls to :cmakev2:ref:`idf_component_include` (directly, or through the ``REQUIRES`` of :cmakev2:ref:`idf_component_register`) evaluate its dependencies before it finishes. The application's dependency graph is thus realized by walking it outward from ``main``, rather than by collecting it in advance as v1 did.
Two consequences follow from evaluating components as ordinary, nested CMake code:
- **Variable hygiene.** A component may be evaluated within the variable scope of the component that pulled it in. A component must initialize every variable it uses rather than relying on it being unset. See :ref:`cmakev2-recursive-evaluation`.
- **No precomputed component list.** There is no point at which the full set of components is known before evaluation, so the v1 ``BUILD_COMPONENTS`` property does not exist in v2. See :ref:`cmakev2-build-components`.
Circular dependencies are tolerated because the interface target of every component exists from discovery, before any component is evaluated. If component A requires B and B requires A, then while B is being evaluated it can already link A's interface target, and A finishes evaluating after B. The build system tracks the chain of in-progress evaluations to avoid infinite recursion.
Dependency Resolution
=====================
A component declares the components it needs and links their interface targets. With :cmakev2:ref:`idf_component_register`, this is done through ``REQUIRES`` (public dependencies, propagated to dependents) and ``PRIV_REQUIRES`` (private dependencies). The function includes each required component and links its ``idf::<name>`` interface, public or private. A native CMake component does the same explicitly, by calling :cmakev2:ref:`idf_component_include` and ``target_link_libraries(... idf::<name>)``.
With ``idf_component_register``, the :ref:`common components <cmakev2-term-common-components>` (``freertos``, ``log``, ``esp_system``, and so on) are still added automatically, as in v1. A native CMake component receives nothing automatically: it must declare every component it uses with ``idf_component_include``, including those common ones.
Because the full configuration is available during evaluation (see `Configuration and Visibility`_), a component can decide its dependencies based on ``CONFIG_*`` options. This is the main new capability in v2 and is covered in :doc:`component-dependencies`. Optional dependencies, linked only when the other component is already part of the build, are available through ``idf_component_optional_requires``; its two resolution modes are described in :ref:`cmakev2-optional-requires`.
Configuration and Visibility
============================
Project configuration uses Kconfig, as in v1. Each component may provide a ``Kconfig`` file, and a ``Kconfig.projbuild`` for project wide options. These files must use exactly those names in the component's root directory; see :ref:`cmakev2-kconfig-file-names`. v2 collects the Kconfig files from **every discovered component** and generates the project configuration from all of them, regardless of which components end up in the build.
The configuration is generated into several files under ``build/config``:
- ``sdkconfig``: the human-readable, persisted configuration, kept in the project directory;
- ``sdkconfig.h``: C and C++ preprocessor defines, included by component source code;
- ``sdkconfig.cmake``: ``set(CONFIG_* ...)`` statements, included during stage 2 so that component ``CMakeLists.txt`` files can read ``CONFIG_*`` variables;
- ``sdkconfig.json``: a machine-readable form for tools.
Generating configuration from all discovered components is what makes configuration available before the dependency graph is resolved, and therefore what makes configuration-driven dependencies possible. It also has an important implication: the presence of a ``CONFIG_*`` option does not mean that the component defining it is part of the build. For example, in a default ``hello_world`` build, ``CONFIG_LWIP_MAX_SOCKETS`` is defined even though the ``lwip`` component is only discovered, not linked. Component code and ``CMakeLists.txt`` files must therefore not assume that another component is present merely because its configuration is set. This difference from v1 is detailed in :ref:`cmakev2-config-visibility`.
Component Manager Integration
=============================
The :doc:`component manager </api-guides/tools/idf-component-manager>` lets a component declare dependencies on components from the ESP Component Registry, from Git, or from local paths, in an ``idf_component.yml`` manifest. In v2, the components the manager resolves are added as a component source (``project_managed_components``) and are then discovered and included like any other component.
Because configuration affects which components a project uses, and managed components bring their own Kconfig, the manager runs as part of stage 2 in an iterative loop: it resolves and downloads dependencies, the configuration is regenerated to include the new components' Kconfig, and the process repeats until the component set is stable. The requirements the manager resolves for each component are injected back as that component's ``REQUIRES`` and ``PRIV_REQUIRES``.
Libraries, Executables, and Linking
===================================
The application is assembled by two functions.
:cmakev2:ref:`idf_build_library` aggregates a set of components into a single interface library. Given a list of components, it includes each one (pulling in transitive dependencies), links their interface targets into the library, and records which components were actually linked. It also collects each linked component's linker fragments and archives for linker script generation, processes the component linker scripts, and runs the component validation checks. The library is itself an interface target: it carries no code of its own, only the aggregated include directories, libraries, and link options.
:cmakev2:ref:`idf_build_executable` builds an application on top of :cmakev2:ref:`idf_build_library`. It creates an internal library from the requested components, creates the executable target, and links the library into it, so the executable inherits every linked component's code, include paths, link options, and linker scripts. It can also produce a linker map file. After the executable target is created, the build system fires the ``POST_ELF`` build event, which lets a component run an action on the linked ELF; see :doc:`build-event-callbacks`.
:cmakev2:ref:`idf_project_default` is the common case: it calls :cmakev2:ref:`idf_build_executable` with the ``main`` component to build a single application. A project can instead call these functions directly to build several binaries, or more than one library. Component targets are created once and shared across all libraries in the project. Multiple binary builds are described in :doc:`multiple-binaries`, and using the build system from an external CMake project in :doc:`idf-as-library`.
Linker Script Generation
------------------------
Placement of code and data into memory regions is controlled by linker fragments, as in v1. Each component can contribute linker fragment files through its ``LDFRAGMENTS`` property. When a library is built, the build system gathers the fragment files and the archives of the linked components and runs the ``ldgen`` tool, which expands a linker script template into the final linker script used for the link. Static linker scripts are added directly, and template scripts are generated per library so that a component linked into more than one library does not collide with itself. For the linker fragment format, see :doc:`/api-guides/linker-script-generation`.
Properties
==========
The build system stores its state on CMake interface targets used as property bags, rather than in global variables. There are three kinds:
- **Build properties**: global to the project, stored on the ``idf_build_properties`` target. Accessed with :cmakev2:ref:`idf_build_set_property` and :cmakev2:ref:`idf_build_get_property` (for example ``IDF_TARGET``, ``IDF_PATH``, ``COMPONENTS_DISCOVERED``).
- **Component properties**: per component, stored on the component's interface target. Accessed with :cmakev2:ref:`idf_component_set_property` and :cmakev2:ref:`idf_component_get_property` (for example ``WHOLE_ARCHIVE``, ``LDFRAGMENTS``, ``LINKER_SCRIPTS``). A property can be read by component name, alias, or target.
- **Library properties**: per library produced by :cmakev2:ref:`idf_build_library`.
Properties can be appended to, and can be returned as generator expressions for use at generate time. The full list of public functions and properties is in :doc:`api`.
Generated Artifacts and Targets
===============================
From the linked executable (the ELF), :cmakev2:ref:`idf_project_default` produces the application binary image and the supporting targets:
- the **binary image** (``.bin``), generated from the ELF and, when secure boot signing is enabled, signed;
- **flash targets** (``flash`` and ``app-flash``) that write the image to the device, and ``app`` that builds it;
- **metadata**: ``project_description.json``, describing the project, its configuration, and its components, for IDEs and other tools;
- **configuration targets**: ``menuconfig``, ``confserver``, ``save-defconfig``, and ``config-report``;
- **analysis and packaging targets**: ``size`` (binary size report) and ``uf2`` (USB flashing image), plus ``dfu`` on targets that support it.
These targets are normally invoked through ``idf.py`` rather than directly. The set of targets follows the configuration and target; for example, the DFU targets are created only on chips that support DFU.
@@ -0,0 +1,121 @@
.. _cmakev2-glossary:
Glossary
********
This page defines the terms used throughout the Build System v2 documentation. The terms are local to Build System v2; where a term also has a more general meaning in ESP-IDF, the definition here is the one that applies in this documentation.
.. _cmakev2-term-application:
application (app)
The executable the build system produces from a set of components, together with the binary image flashed to the :ref:`target <cmakev2-term-target>`. A project usually builds two applications: the project application (the firmware) and the bootloader. ``app`` is a synonym.
.. _cmakev2-term-backward-compatible-component:
backward-compatible component
A component declared with :cmakev2:ref:`idf_component_register`. It builds under both Build System v1 and v2, as long as it stays within the features that v1 also supports. Contrast :ref:`native component <cmakev2-term-native-component>`.
.. _cmakev2-term-build-event-callback:
build event callback
A function a component registers with :cmakev2:ref:`idf_component_register_build_event_callback` to run at a defined point in the build. The only event currently supported is ``POST_ELF``, fired after the executable is linked and before the binary image is generated. See :doc:`build-event-callbacks`.
.. _cmakev2-term-build-property:
build property
A setting that is global to the project, stored on the ``idf_build_properties`` target and accessed with :cmakev2:ref:`idf_build_get_property` and :cmakev2:ref:`idf_build_set_property`. Contrast :ref:`component property <cmakev2-term-component-property>` and :ref:`library property <cmakev2-term-library-property>`.
.. _cmakev2-term-common-components:
common components
The core components, such as ``freertos`` and ``log``, that the build system adds as dependencies of every component declared with :cmakev2:ref:`idf_component_register`, without an explicit ``REQUIRES``. A :ref:`native component <cmakev2-term-native-component>` receives nothing automatically and must include every component it uses, including these.
.. _cmakev2-term-component:
component
A reusable, separately compiled unit of code: a directory that contains a ``CMakeLists.txt`` file. The build system compiles each component into a library and links the ones the application needs. The component's name is the name of its directory.
.. _cmakev2-term-component-library-target:
component library target
The library target a component creates, typically with ``add_library``, and whose name the build system passes to the component in the :cmakev2:ref:`COMPONENT_TARGET` variable (also available as ``COMPONENT_LIB``). It holds the component's compiled code. Distinct from the :ref:`interface target <cmakev2-term-interface-target>`.
.. _cmakev2-term-component-property:
component property
A setting attached to a single component, stored on its :ref:`interface target <cmakev2-term-interface-target>` and accessed with :cmakev2:ref:`idf_component_get_property` and :cmakev2:ref:`idf_component_set_property`. Examples are :cmakev2:ref:`WHOLE_ARCHIVE`, :cmakev2:ref:`LDFRAGMENTS`, and :cmakev2:ref:`LINKER_SCRIPTS`.
.. _cmakev2-term-component-source:
component source
A location the build system searches for components, each with a precedence. From highest to lowest: ``project_components`` (the project's ``main`` and ``components`` directories), ``project_extra_components`` (directories in ``EXTRA_COMPONENT_DIRS``), ``project_managed_components`` (fetched by the component manager), and ``idf_components`` (bundled with ESP-IDF). A component from a higher-precedence source shadows a same-named component from a lower one. See :doc:`design`.
.. _cmakev2-term-configuration:
configuration (sdkconfig)
The project's Kconfig-based settings, persisted in the ``sdkconfig`` file and exposed to source code and ``CMakeLists.txt`` files as ``CONFIG_*`` options. In v2 the configuration is generated from the Kconfig of every :ref:`discovered <cmakev2-term-discovery>` component, not only the ones in the build. See :doc:`/api-guides/kconfig/index`.
.. _cmakev2-term-discovery:
discovery
Registering a component with the build system: recording its directory, Kconfig files, and ``project_include.cmake``, and creating its :ref:`interface target <cmakev2-term-interface-target>`. A discovered component is known and configurable, but is not compiled. Contrast :ref:`inclusion <cmakev2-term-inclusion>`.
.. _cmakev2-term-inclusion:
inclusion
Bringing a discovered component into the build by evaluating it: the build system calls ``add_subdirectory`` on it, which runs its ``CMakeLists.txt`` and creates its :ref:`component library target <cmakev2-term-component-library-target>`. Only included components are compiled and linked. :cmakev2:ref:`idf_component_include` performs the inclusion. Contrast :ref:`discovery <cmakev2-term-discovery>`.
.. _cmakev2-term-interface-alias:
interface alias
The ``idf::<name>`` alias of a component's :ref:`interface target <cmakev2-term-interface-target>`. It is the canonical, readable way for other components to refer to a component, for example ``idf::spi_flash``.
.. _cmakev2-term-interface-target:
interface target
The ``idf_<name>`` target created for a component at :ref:`discovery <cmakev2-term-discovery>`. It carries the component's properties and is what other components link against, usually through its :ref:`interface alias <cmakev2-term-interface-alias>` ``idf::<name>``. Distinct from the :ref:`component library target <cmakev2-term-component-library-target>`.
.. _cmakev2-term-library:
library
An interface library produced by :cmakev2:ref:`idf_build_library` that bundles a set of components and their transitive dependencies. Linking it into an executable brings in those components' code, include paths, and link options. Not to be confused with a component's own :ref:`library target <cmakev2-term-component-library-target>` or the final binary image.
.. _cmakev2-term-library-property:
library property
A setting recorded for a single :ref:`library <cmakev2-term-library>` produced by :cmakev2:ref:`idf_build_library`, such as the list of components it linked. Contrast :ref:`build property <cmakev2-term-build-property>` and :ref:`component property <cmakev2-term-component-property>`.
.. _cmakev2-term-linker-fragment:
linker fragment
A file, contributed through a component's :cmakev2:ref:`LDFRAGMENTS` property, that the linker script generator uses to place a component's code and data into memory regions. Distinct from a linker script, which is added with :cmakev2:ref:`LINKER_SCRIPTS`. See :doc:`/api-guides/linker-script-generation`.
.. _cmakev2-term-main-component:
main component
The component named ``main`` from which :cmakev2:ref:`idf_project_default` builds the default application. Building from ``main`` is a convention of :cmakev2:ref:`idf_project_default`, not a requirement of the build system; a project that uses the lower-level API can build its application from any component.
.. _cmakev2-term-native-component:
native component
A component written as a plain CMake static library, created directly with ``add_library`` instead of :cmakev2:ref:`idf_component_register`. It has full access to native CMake, but on its own builds only under v2. Contrast :ref:`backward-compatible component <cmakev2-term-backward-compatible-component>`.
.. _cmakev2-term-optional-dependency:
optional dependency
A dependency declared with :cmakev2:ref:`idf_component_optional_requires` that links another component only when it is part of the build. v2 resolves it in one of two modes, ``IMMEDIATE`` or ``DEFERRED``; see :ref:`cmakev2-optional-requires`.
.. _cmakev2-term-project:
project
A directory that contains a top-level ``CMakeLists.txt``, usually a :ref:`main component <cmakev2-term-main-component>`, and optionally a ``components`` directory with additional components. It builds into one or more :ref:`applications <cmakev2-term-application>`. See :doc:`creating-project`.
.. _cmakev2-term-single-pass-evaluation:
single-pass evaluation
The v2 design property that each component is :ref:`evaluated <cmakev2-term-inclusion>` exactly once, as ordinary CMake code. v1 instead evaluated components twice, with an early pass in CMake script mode. See :doc:`design`.
.. _cmakev2-term-target:
target
The chip the project is built for, for example ``esp32``, selected with ``idf.py set-target`` and available as the :cmakev2:ref:`IDF_TARGET` build property and the ``CONFIG_IDF_TARGET`` option. The word "target" also refers to a CMake build target, as in :ref:`interface target <cmakev2-term-interface-target>`; this documentation uses "target" for the chip unless qualified.
@@ -0,0 +1,86 @@
Using ESP-IDF as a Library
**************************
In the usual project, :cmakev2:ref:`idf_project_default` configures everything and builds the application from the ``main`` component. A standard CMake project can instead drive the ESP-IDF build itself with the lower-level API: it initializes the build system, bundles the required ESP-IDF components into a single library with :cmakev2:ref:`idf_build_library`, and links that library into its own executable. This is useful for integrating ESP-IDF into an existing CMake project, or for building host and target binaries from the same sources. The :example:`idf_as_lib example <build_system/cmakev2/features/idf_as_lib>` shows the complete pattern.
Driving the Build
=================
The project's ``CMakeLists.txt`` includes ``idf.cmake``, runs ``project()``, and creates its own executable, then uses the lower-level API to attach ESP-IDF to it:
.. code-block:: cmake
:caption: CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
include($ENV{IDF_PATH}/tools/cmakev2/idf.cmake)
project(my_app C CXX ASM)
add_executable(my_app.elf main.c)
# Initialize the build system and bundle the needed components.
idf_project_init()
idf_build_library(idf_components COMPONENTS spi_flash esp_system)
# Apply the include paths, definitions, and options that ESP-IDF
# normally adds, then link the bundled library.
idf_build_get_property(include_directories INCLUDE_DIRECTORIES GENERATOR_EXPRESSION)
target_include_directories(my_app.elf PRIVATE "${include_directories}")
idf_build_get_property(compile_definitions COMPILE_DEFINITIONS GENERATOR_EXPRESSION)
target_compile_definitions(my_app.elf PRIVATE "${compile_definitions}")
idf_build_get_compile_options(compile_options)
target_compile_options(my_app.elf PRIVATE "${compile_options}")
target_link_libraries(my_app.elf PRIVATE idf_components)
The key functions are:
:cmakev2:ref:`idf_project_init`
Initialize the build system after ``project()``: discover components, generate the configuration, and set up the compile and link options. It must be called before the functions below.
:cmakev2:ref:`idf_build_library`
Bundle the listed components and their transitive dependencies into a single interface library, here named ``idf_components``. Linking this library into an executable brings in the components' code, include paths, and linker options.
:cmakev2:ref:`idf_build_get_property` and :cmakev2:ref:`idf_build_get_compile_options`
Read the global include directories, compile definitions, and compile options that ESP-IDF applies to its components, so that the same settings can be applied to the project's own executable.
Generating the Binary and Flash Targets
========================================
To produce a flashable image and the usual targets, add them explicitly after linking:
.. code-block:: cmake
idf_build_binary(my_app.elf
OUTPUT_FILE "${CMAKE_BINARY_DIR}/my_app.bin"
TARGET my_app_binary)
idf_flash_binary(my_app_binary TARGET app-flash NAME "app" FLASH)
idf_check_binary_size(my_app_binary)
idf_build_generate_metadata(BINARY my_app_binary)
idf_build_generate_flasher_args()
add_custom_target(app ALL DEPENDS my_app_binary)
:cmakev2:ref:`idf_build_binary` generates the binary image from the ELF, :cmakev2:ref:`idf_flash_binary` adds the flash target, and :cmakev2:ref:`idf_build_generate_metadata` writes ``project_description.json`` for tooling. These are the steps that :cmakev2:ref:`idf_project_default` performs automatically.
Building for Host and Target
============================
Because the ESP-IDF parts are explicit, the same project can also build a plain host executable. ``idf.py`` sets the :cmakev2:ref:`ESP_PLATFORM` variable, so the ESP-IDF specific code can be guarded with it:
.. code-block:: cmake
if(ESP_PLATFORM)
include($ENV{IDF_PATH}/tools/cmakev2/idf.cmake)
endif()
project(my_app C CXX ASM)
add_executable(my_app.elf main.c)
if(ESP_PLATFORM)
# idf_project_init(), idf_build_library(), and the rest go here.
endif()
Without ``ESP_PLATFORM``, the same ``CMakeLists.txt`` builds an ordinary host executable with no ESP-IDF dependency. Guard the ESP-IDF API calls in the source with ``#ifdef ESP_PLATFORM`` as well. The :example:`idf_as_lib example <build_system/cmakev2/features/idf_as_lib>` builds both ways from a single ``main.c``.
.. note::
A project that builds a single library this way uses the IMMEDIATE optional-requires mode by default. To get the same minimal, v1-like component set as :cmakev2:ref:`idf_project_default`, set the mode to DEFERRED after :cmakev2:ref:`idf_project_init`. See :ref:`cmakev2-optional-requires`.
@@ -0,0 +1,51 @@
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.
Introduction
============
The ESP-IDF CMake-based build system v2, referred to throughout this documentation simply as v2, is the next generation of the ESP-IDF build system. It is the successor to the :doc:`CMake-based build system v1 </api-guides/build-system>`, referred to as v1, which has been the default ESP-IDF build system since ESP-IDF v4.0, when CMake superseded the earlier GNU Make based build system. Like v1, v2 builds and links an ESP-IDF project from reusable units called components, while resolving several structural limitations of the original CMake-based design.
The most significant changes introduced by v2 are:
- **Configuration-driven component dependencies.** Component dependencies can be expressed in terms of Kconfig configuration options, so the set of components built into a project can follow its configuration. See :doc:`component-dependencies`.
- **Single-pass component evaluation.** v2 removes the early component evaluation that v1 performed in CMake script mode. Each component is evaluated once, as ordinary CMake code, which makes the build easier to reason about. The model is described in :doc:`design`.
- **Native CMake components.** A component can be written as a plain CMake static library, without the ``idf_component_register`` wrapper, giving full access to native CMake functionality. See :ref:`cmakev2-component-native`.
v2 is designed to stay as backward compatible with v1 as possible. Most projects and components written for v1 build under v2 without modification. Where a design difference does require a change to a v1 component, it is documented in :doc:`breaking-changes`. To convert an existing project to v2, see :doc:`updating-project`.
Runnable applications that accompany the guides below are provided in the :example:`Build System v2 examples <build_system/cmakev2>`. Terminology used throughout this guide is collected in the :doc:`glossary`.
Guides
======
.. toctree::
:maxdepth: 1
creating-project
updating-project
creating-component
updating-component
managing-compatibility
component-dependencies
third-party-libraries
idf-as-library
multiple-binaries
multiple-configurations
build-event-callbacks
Reference
=========
.. toctree::
:maxdepth: 1
design
breaking-changes
api
glossary
Build System v1 </api-guides/build-system>
@@ -0,0 +1,45 @@
Managing Component Backward Compatibility
*****************************************
A component that must build under both v1 and v2 can use the :cmakev2:ref:`IDF_BUILD_V2` variable to run different CMake code under each build system. ``IDF_BUILD_V2`` is set when the component is evaluated under v2. You can use it to adjust small parts of an existing v1 component, or to include a completely separate ``CMakeLists.txt`` for v2. For the specific differences between v1 and v2 that may require this, see :doc:`breaking-changes`.
Below is a component, ``my_component``, whose ``CMakeLists.txt`` is adjusted to include and evaluate a separate v2 file, ``CMakeLists_v2.txt``, when it is evaluated under v2.
.. note::
Most v1 components should work without modification under v2. This is only a simple, illustrative example of how ``IDF_BUILD_V2`` can be used to evaluate different ``CMakeLists.txt`` files for v1 and v2.
Adjusted ``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 "my_component.c"
PRIV_REQUIRES spi_flash
INCLUDE_DIRS "")
The ``CMakeLists_v2.txt`` for v2:
.. code-block:: cmake
idf_component_include(spi_flash)
add_library(${COMPONENT_TARGET} STATIC
"my_component.c"
)
target_include_directories(${COMPONENT_TARGET} PUBLIC
"${CMAKE_CURRENT_LIST_DIR}"
)
target_link_libraries(${COMPONENT_TARGET} PRIVATE
idf::spi_flash
)
For more on writing a native v2 component, see :ref:`cmakev2-component-native`.
@@ -0,0 +1,45 @@
Building Multiple Binaries
**************************
A project does not have to produce a single application; it can build several independent binaries from one source tree, going beyond the single-application default of :cmakev2:ref:`idf_project_default`. This fits a set of related applications that share a common component base and the same configuration but differ in which components they include, for example several feature or product variants of the same firmware; building them from one project compiles the shared components once and keeps them consistent across binaries. Applications that each need their own configuration should be separate projects. To build one application in several configurations instead, see :doc:`multiple-configurations`.
To build more than one executable from a project, call :cmakev2:ref:`idf_project_init` once and then :cmakev2:ref:`idf_build_executable` for each binary, giving each its own set of components. Component targets are shared across the whole project, so a component used by several executables is built only once. The :example:`multi_binary example <build_system/cmakev2/features/multi_binary>` builds two applications, ``app1`` and ``app2``, from shared and distinct components:
.. code-block:: cmake
:caption: CMakeLists.txt
include($ENV{IDF_PATH}/tools/cmakev2/idf.cmake)
project(multi_binary C CXX ASM)
idf_project_init()
idf_build_executable(app1.elf COMPONENTS app1_main component1 component2)
idf_build_executable(app2.elf COMPONENTS app2_main component1 component2 component3)
Each executable then gets its own binary, flash, and configuration targets, named so that they do not collide:
.. code-block:: cmake
idf_build_binary(app1.elf OUTPUT_FILE "${CMAKE_BINARY_DIR}/app1.bin" TARGET app1_binary)
idf_flash_binary(app1_binary TARGET app1-flash NAME "app1" FLASH)
idf_create_menuconfig(app1.elf TARGET app1-menuconfig)
idf_build_binary(app2.elf OUTPUT_FILE "${CMAKE_BINARY_DIR}/app2.bin" TARGET app2_binary)
idf_flash_binary(app2_binary TARGET app2-flash NAME "app2")
add_custom_target(app ALL DEPENDS app1.bin app2.bin)
A single build now produces both ``app1.bin`` and ``app2.bin``, each with its own ``appN-flash`` and ``appN-menuconfig`` targets.
.. note::
A multi-binary project has a single project-wide ``sdkconfig``, and a
component shared between executables is one instance with one
configuration. All ``appN-menuconfig`` targets therefore edit the same
``sdkconfig``: changing a shared component's option through any of them
affects every executable that uses that component. To build the same
application in genuinely independent configurations, use
:doc:`multiple-configurations` instead.
.. important::
A multi-binary project builds more than one library, so it must use the **IMMEDIATE** optional-requires mode, which is the default. Do not set ``IDF_COMPONENT_OPTIONAL_REQUIRES_MODE`` to ``DEFERRED`` when building multiple libraries; the build fails with an error. See :ref:`cmakev2-optional-requires`.
@@ -0,0 +1,56 @@
Building Multiple Configurations
********************************
A project can build the same application repeatedly with different configurations, each into its own build directory, going beyond the single-configuration default of :cmakev2:ref:`idf_project_default`. To build several different applications from one source tree instead, see :doc:`multiple-binaries`.
This is done with CMake presets. Each preset selects its own ``sdkconfig.defaults`` files and its own build directory, so the configurations are built side by side without affecting each other. The :example:`multi_config example <build_system/cmakev2/features/multi_config>` defines a default (development) preset and two production presets:
.. code-block:: json
:caption: CMakePresets.json
{
"version": 3,
"configurePresets": [
{
"name": "default",
"binaryDir": "build/default",
"cacheVariables": {
"SDKCONFIG": "./build/default/sdkconfig"
}
},
{
"name": "prod1",
"binaryDir": "build/prod1",
"cacheVariables": {
"SDKCONFIG_DEFAULTS": "sdkconfig.defaults.prod_common;sdkconfig.defaults.prod1",
"SDKCONFIG": "./build/prod1/sdkconfig"
}
},
{
"name": "prod2",
"binaryDir": "build/prod2",
"cacheVariables": {
"SDKCONFIG_DEFAULTS": "sdkconfig.defaults.prod_common;sdkconfig.defaults.prod2",
"SDKCONFIG": "./build/prod2/sdkconfig"
}
}
]
}
So that the configurations do not interfere, the project keeps each ``sdkconfig`` in its build directory:
.. code-block:: cmake
:caption: CMakeLists.txt
set(SDKCONFIG "${CMAKE_BINARY_DIR}/sdkconfig")
include($ENV{IDF_PATH}/tools/cmakev2/idf.cmake)
project(multi_config C CXX ASM)
idf_project_default()
Build a specific configuration by selecting its preset:
.. code-block:: bash
idf.py --preset prod1 build
Each preset builds into its own directory with its own configuration, from the same application sources.
@@ -0,0 +1,74 @@
Integrating Third-Party Libraries
*********************************
A project can use libraries that are not ESP-IDF components in three ways: by importing a precompiled static library, by building a library from source and wrapping it as a component, or by linking an external CMake library directly. Each approach is shown below and has a complete example.
Importing a Prebuilt Library
============================
If you already have a compiled static library (``.a``) and its headers, import it with :cmakev2:ref:`add_prebuilt_library` and link it to a component. The :example:`import_prebuilt example <build_system/cmakev2/features/import_prebuilt>` imports ``libprebuilt.a``:
.. code-block:: cmake
:caption: main/CMakeLists.txt
idf_component_register(SRCS "main.c"
INCLUDE_DIRS ".")
# Import the prebuilt library and declare the components it depends on.
add_prebuilt_library(prebuilt "libprebuilt.a"
PRIV_REQUIRES spi_flash app_update log)
# main calls a function from the library, so link it.
target_link_libraries(${COMPONENT_LIB} PRIVATE prebuilt)
:cmakev2:ref:`add_prebuilt_library` creates an imported library target from the ``.a`` file. ``REQUIRES`` and ``PRIV_REQUIRES`` declare the ESP-IDF components that the library itself depends on. Add the library's include directory with ``target_include_directories`` so that code using it can find its headers.
Building a Library from Source as a Component
=============================================
If the third-party library is built with its own CMake build, build it with CMake's ``ExternalProject_Add`` and wrap it in a component. This is the recommended approach for C++ libraries, because the wrapping component can declare a dependency on the ESP-IDF C++ runtime through ``PRIV_REQUIRES cxx``. The :example:`import_lib example <build_system/cmakev2/features/import_lib>` downloads and builds ``tinyxml2``:
.. code-block:: cmake
:caption: components/tinyxml2/CMakeLists.txt
idf_component_register()
include(ExternalProject)
externalproject_add(tinyxml2_proj
URL https://github.com/leethomason/tinyxml2/archive/refs/tags/9.0.0.zip
# Use the same toolchain as the project.
CMAKE_ARGS -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}
-DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}/tinyxml2_install
BUILD_BYPRODUCTS "${CMAKE_CURRENT_BINARY_DIR}/tinyxml2_install/lib/libtinyxml2.a")
# Consume the built library: import it, add its headers, and link it.
add_prebuilt_library(tinyxml2_lib "${CMAKE_CURRENT_BINARY_DIR}/tinyxml2_install/lib/libtinyxml2.a"
PRIV_REQUIRES cxx)
target_include_directories(tinyxml2_lib INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/tinyxml2_install/include")
add_dependencies(tinyxml2_lib tinyxml2_proj)
target_link_libraries(${COMPONENT_LIB} INTERFACE tinyxml2_lib)
``ExternalProject_Add`` downloads, configures, builds, and installs the library at build time, using the same toolchain as the project. The resulting ``.a`` is then consumed with :cmakev2:ref:`add_prebuilt_library`, exactly as for a prebuilt library. See the example for the complete, commented version.
Linking an External CMake Library Directly
===========================================
For a self-contained library, typically a pure C library, you can fetch and link it directly without a separate wrapper component. Because v2 components are native CMake code, a component can use CMake's ``FetchContent`` and link the fetched target. The :example:`import_lib_direct example <build_system/cmakev2/features/import_lib_direct>` fetches ``lwjson`` and links it to ``main``:
.. code-block:: cmake
:caption: main/CMakeLists.txt
include(FetchContent)
fetchcontent_declare(lwjson
GIT_REPOSITORY https://github.com/MaJerle/lwjson.git
GIT_TAG v1.8.1)
fetchcontent_makeavailable(lwjson)
idf_component_register(SRCS "main.c"
INCLUDE_DIRS ".")
target_link_libraries(${COMPONENT_LIB} PUBLIC lwjson)
``FetchContent`` makes the library's CMake target available, which is then linked to the component with ``target_link_libraries``. This is the simplest approach when the library is a plain CMake project with no special runtime requirements.
@@ -0,0 +1,14 @@
Updating an Existing Component
******************************
Most components written for v1 build under v2 without modification. This page describes what to do when one does not, and how to keep a component working under both build systems.
Workflow
========
#. Build the component under v2 by building a project that uses it with Build System v2; see :doc:`updating-project`.
#. If the component builds and links correctly, no change is needed. v2 is designed to stay as backward compatible with v1 as possible.
#. If the build fails, find the relevant difference in :doc:`breaking-changes`. Each entry explains how v2 differs from v1 and how to adapt the component.
#. If the component must continue to build under v1 as well, apply the change in a way that works for both build systems, using the techniques in :doc:`managing-compatibility`.
Most adaptations are small and local. The techniques for building under both v1 and v2 are only needed when a component must still build with v1; a component that targets v2 only can be changed directly to the v2 behavior, or rewritten as a :ref:`native CMake component <cmakev2-component-native>`.
@@ -0,0 +1,28 @@
Updating an Existing Project
****************************
Converting an existing v1 project to v2 usually requires only a change to the top-level ``CMakeLists.txt``. The project layout, the components, and the application code stay the same. The structure of a v2 project is described in :doc:`creating-project`.
Switching the Project to v2
===========================
Change the project's ``CMakeLists.txt`` from the v1 form:
.. code-block:: cmake
cmake_minimum_required(VERSION 3.22)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(my_project)
to the v2 form:
.. code-block:: cmake
cmake_minimum_required(VERSION 3.22)
include($ENV{IDF_PATH}/tools/cmakev2/idf.cmake)
project(my_project C CXX ASM)
idf_project_default()
The change is the included file, ``tools/cmakev2/idf.cmake`` instead of ``tools/cmake/project.cmake``, the explicit list of project languages, and the call to :cmakev2:ref:`idf_project_default`. The meaning of each line is described in :doc:`creating-project`.
This is sufficient for most projects. If a component does not build under v2, see :doc:`breaking-changes` for the differences that may require a change, and :doc:`managing-compatibility` for keeping a component working under both v1 and v2.