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 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 Managing Component Backward 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 Managing Component Backward Compatibility.

Prefer idf_component_register. Choose a native CMake component only when it needs CMake features that 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:

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 Design and Architecture. The layout is the same for both kinds of component; only the content of CMakeLists.txt differs, as described in the sections below.

Backward-Compatible Component

The 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: main/CMakeLists.txt .

Registering the Component

The component's CMakeLists.txt calls idf_component_register to declare its source files, include directories, and dependencies:

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 idf_component_register with only INCLUDE_DIRS, or with no arguments at all.

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 API Reference.

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:

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 Managing Component Backward Compatibility.

Differences from v1

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 Standardized Kconfig and Kconfig.projbuild File Names.

重要

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 Recursive Evaluation of Components.

For the complete list of differences that may require changes to a v1 component, see Breaking Changes. To keep a single component building under both v1 and v2, see Managing Component Backward Compatibility.

Native CMake Component

A native CMake component creates its library target directly with add_library, instead of calling idf_component_register. This gives full access to native CMake, which is useful when idf_component_register does not provide enough control. On its own such a component builds only under v2; see Managing Component Backward 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 conditional_component example .

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:

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:

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:

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:

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 链接器脚本生成机制:

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

CMakeLists.txt
 1add_library(${COMPONENT_TARGET} STATIC
 2    "srcs/esp_target_info.c"
 3)
 4
 5target_include_directories(${COMPONENT_TARGET} PUBLIC
 6    ${CMAKE_CURRENT_LIST_DIR}/include
 7)
 8
 9idf_component_include(esp_hw_support)
10idf_component_include(spi_flash)
11idf_component_include(esp_system)
12
13target_link_libraries(${COMPONENT_TARGET} PRIVATE
14    idf::esp_hw_support
15    idf::spi_flash
16    idf::esp_system
17)
18
19idf_component_set_property(${COMPONENT_TARGET} WHOLE_ARCHIVE TRUE)
20idf_component_set_property(${COMPONENT_TARGET} LDFRAGMENTS linker.lf APPEND)
21idf_component_set_property(${COMPONENT_TARGET} LINKER_SCRIPTS esp_target_info.ld APPEND)
22
23target_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 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 idf_component_include, then links the component's idf::<name> interface target. The highlighted lines add these dependencies:

CMakeLists.txt
 1add_library(${COMPONENT_TARGET} STATIC
 2    "srcs/esp_target_info.c"
 3)
 4
 5target_include_directories(${COMPONENT_TARGET} PUBLIC
 6    ${CMAKE_CURRENT_LIST_DIR}/include
 7)
 8
 9idf_component_include(esp_hw_support)
10idf_component_include(spi_flash)
11idf_component_include(esp_system)
12
13target_link_libraries(${COMPONENT_TARGET} PRIVATE
14    idf::esp_hw_support
15    idf::spi_flash
16    idf::esp_system
17)
18
19idf_component_set_property(${COMPONENT_TARGET} WHOLE_ARCHIVE TRUE)
20idf_component_set_property(${COMPONENT_TARGET} LDFRAGMENTS linker.lf APPEND)
21idf_component_set_property(${COMPONENT_TARGET} LINKER_SCRIPTS esp_target_info.ld APPEND)
22
23target_link_options(${COMPONENT_TARGET} INTERFACE "SHELL:-u esp_chip_info")

备注

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.

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.

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:

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:

if(CONFIG_VFS_SUPPORT_IO)
    idf_component_include(vfs)
    target_link_libraries(${COMPONENT_TARGET} PRIVATE idf::vfs)
endif()

重要

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 Configuring Component Dependencies.

Setting Component Properties

A native component configures build behavior through component properties, set with idf_component_set_property. The highlighted lines set the properties for esp_target_info and force a symbol to be retained:

CMakeLists.txt
 1add_library(${COMPONENT_TARGET} STATIC
 2    "srcs/esp_target_info.c"
 3)
 4
 5target_include_directories(${COMPONENT_TARGET} PUBLIC
 6    ${CMAKE_CURRENT_LIST_DIR}/include
 7)
 8
 9idf_component_include(esp_hw_support)
10idf_component_include(spi_flash)
11idf_component_include(esp_system)
12
13target_link_libraries(${COMPONENT_TARGET} PRIVATE
14    idf::esp_hw_support
15    idf::spi_flash
16    idf::esp_system
17)
18
19idf_component_set_property(${COMPONENT_TARGET} WHOLE_ARCHIVE TRUE)
20idf_component_set_property(${COMPONENT_TARGET} LDFRAGMENTS linker.lf APPEND)
21idf_component_set_property(${COMPONENT_TARGET} LINKER_SCRIPTS esp_target_info.ld APPEND)
22
23target_link_options(${COMPONENT_TARGET} INTERFACE "SHELL:-u esp_chip_info")
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 plugins example relies on it.

LDFRAGMENTS

Linker fragment files for the component, processed by the linker script generator. Here it adds linker.lf. See 链接器脚本生成机制.

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 API Reference. 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 Using 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. 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 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 Building Multiple Binaries and Using ESP-IDF as a Library). The difference between discovering a component and including it is described in Design and Architecture.


此文档对您有帮助吗?