# Breaking Changes

Most components written for Build System v1 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 Managing Component Backward Compatibility. The concepts behind these changes are described in Design and Architecture.

## `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 Build System v1 API). These functions are not available in v2.

In v2, use a build event callback instead. Register a `POST_ELF` callback with 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 Using Build Event Callbacks.

## 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 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 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:

    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:

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

备注

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.

## 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:

    if(NOT CMAKE_BUILD_EARLY_EXPANSION)
        # In v1, this runs only in the regular pass, not the early one.
        # ...
    endif()

v2 uses 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.

## Standardized `Kconfig` and `Kconfig.projbuild` File Names

v1 allows the use of `Kconfig` and `Kconfig.projbuild` with custom file names in 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.

## 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:

    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 idf_component_include (see Configuring Component Dependencies):

    if(CONFIG_VFS_SUPPORT_IO)
        idf_component_include(vfs)
        target_link_libraries(${COMPONENT_LIB} PUBLIC idf::vfs)
    endif()

重要

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`.

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

    # Wrong
    list(APPEND srcs main.c)

    # Correct
    set(srcs)
    list(APPEND srcs main.c)

## The `project_include.cmake` Files are Included in a Non-Specific Order

In v1, the `project_include.cmake` files from components are included in the order specified by the `BUILD_COMPONENTS` build property. The components in `BUILD_COMPONENTS` are sorted based on their requirements, as determined during early evaluation. This means a 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.

备注

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 同名组件. 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.

## 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 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 Building Multiple Binaries). Such a project calls idf_build_library or 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.

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 idf_project_default and instead call idf_project_init and then the lower-level API (idf_build_executable, 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:

    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.

此文档对您有帮助吗？

- 反馈已收到，谢谢！
- 如果您有其他意见，欢迎填写乐鑫文档反馈表。

- 我们重视您的反馈。
- 您可以填写乐鑫文档反馈表告诉我们如何改进该文档。