CMake编写教程

示例项目

示例项目的目录树结构可能如下所示:

- myProject/
             - CMakeLists.txt
             - sdkconfig
             - components/ - component1/ - CMakeLists.txt
                                         - Kconfig
                                         - src1.c
                           - component2/ - CMakeLists.txt
                                         - Kconfig
                                         - src1.c
                                         - include/ - component2.h
             - main/       - src1.c
                           - src2.c
             - build/

该示例项目 myproject 包含以下组成部分:

  • 顶层项目 CMakeLists.txt 文件,这是 CMake 用于学习如何构建项目的主要文件,可以在这个文件中设置项目全局的 CMake 变量。顶层项目 CMakeLists.txt 文件会导入 /tools/cmake/project.cmake 文件,由它负责实现构建系统的其余部分。该文件最后会设置项目的名称,并定义该项目。

  • sdkconfig 项目配置文件,执行 idf.py menuconfig 时会创建或更新此文件,文件中保存了项目中所有组件(包括 ESP-IDF 本身)的配置信息。 sdkconfig 文件可能会也可能不会被添加到项目的源码管理系统中。

  • 可选的 component 目录中包含了项目的部分自定义组件,并不是每个项目都需要这种自定义组件,但它组件有助于构建可复用的代码或者导入第三方(不属于 ESP-IDF)的组件。

  • main 目录是一个特殊的 伪组件,包含项目本身的源代码。main 是默认名称,CMake 变量 COMPONENT_DIRS 默认包含此组件,但您可以修改此变量。或者,您也可以在顶层 CMakeLists.txt 中设置 EXTRA_COMPONENT_DIRS 变量以查找其他指定位置处的组件。有关详细信息,请参阅 重命名 main 组件。如果项目中源文件较多,建议将其归于组件中,而不是全部放在 main 中。

  • build 目录是存放构建输出的地方,如果没有此目录,idf.py 会自动创建。CMake 会配置项目,并在此目录下生成临时的构建文件。随后,在主构建进程的运行期间,该目录还会保存临时目标文件、库文件以及最终输出的二进制文件。此目录通常不会添加到项目的源码管理系统中,也不会随项目源码一同发布。

每个组件目录都包含一个 CMakeLists.txt 文件,里面会定义一些变量以控制该组件的构建过程,以及其与整个项目的集成。更多详细信息请参阅 组件 CMakeLists 文件

每个组件还可以包含一个 Kconfig 文件,它用于定义 menuconfig 时展示的 组件配置 选项。某些组件可能还会包含 Kconfig.projbuild 和 project_include.cmake 特殊文件,它们用于 覆盖项目的部分设置

项目 CMakeLists 文件

每个项目都有一个顶层 CMakeLists.txt 文件,包含整个项目的构建设置。默认情况下,项目 CMakeLists 文件会非常小。

最小 CMakeLists 文件示例

最小项目:

cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(myProject)

必要部分

每个项目都要按照上面显示的顺序添加上述三行代码:

  • cmake_minimum_required(VERSION 3.5) 必须放在 CMakeLists.txt 文件的第一行,它会告诉 CMake 构建该项目所需要的最小版本号。ESP-IDF 支持 CMake 3.5 或更高的版本。

  • include($ENV{IDF_PATH}/tools/cmake/project.cmake) 会导入 CMake 的其余功能来完成配置项目、检索组件等任务。

  • project(myProject) 会创建项目本身,并指定项目名称。该名称会作为最终输出的二进制文件的名字,即 myProject.elf 和 myProject.bin。每个 CMakeLists 文件只能定义一个项目。

可选的项目变量

以下这些变量都有默认值,用户可以覆盖这些变量值以自定义构建行为。更多实现细节,请参阅 /tools/cmake/project.cmake 文件。

  • COMPONENT_DIRS:组件的搜索目录,默认为 ${IDF_PATH}/components${PROJECT_PATH}/components 和 EXTRA_COMPONENT_DIRS。如果您不想在这些位置搜索组件,请覆盖此变量。

  • EXTRA_COMPONENT_DIRS:用于搜索组件的其它可选目录列表。路径可以是相对于项目目录的相对路径,也可以是绝对路径。

  • COMPONENTS:要构建进项目中的组件名称列表,默认为 COMPONENT_DIRS 目录下检索到的所有组件。使用此变量可以“精简”项目以缩短构建时间。请注意,如果一个组件通过 COMPONENT_REQUIRES 指定了它依赖的另一个组件,则会自动将其添加到 COMPONENTS 中,所以 COMPONENTS 列表可能会非常短。

  • COMPONENT_REQUIRES_COMMON:每个组件都需要的通用组件列表,这些通用组件会自动添加到每个组件的 COMPONENT_PRIV_REQUIRES 列表中以及项目的 COMPONENTS 列表中。默认情况下,此变量设置为 ESP-IDF 项目所需的最小核心“系统”组件集。通常您无需在项目中更改此变量。

以上变量中的路径可以是绝对路径,或者是相对于项目目录的相对路径。

请使用 cmake 中的 set 命令 来设置这些变量,即 set(VARIABLE "VALUE")。请注意,set() 命令需放在 include(...) 之前,cmake_minimum(...) 之后。

重命名 main 组件

构建系统会对 main 组件进行特殊处理。假如 main 组件位于预期的位置(即 ${PROJECT_PATH}/main),那么它会被自动添加到构建系统中。其他组件也会作为其依赖项被添加到构建系统中,这使用户免于处理依赖关系,并提供即时可用的构建功能。重命名 main 组件会减轻上述这些幕后工作量,但要求用户指定重命名后的组件位置,并手动为其添加依赖项。重命名 main 组件的步骤如下:

  1. 重命名 main 目录。

  2. 在项目 CMakeLists.txt 文件中设置 EXTRA_COMPONENT_DIRS,并添加重命名后的 main 目录。

  3. 在组件的 CMakeLists.txt 文件中设置 COMPONENT_REQUIRES 或 COMPONENT_PRIV_REQUIRES 以指定依赖项。

组件 CMakeLists 文件

每个项目都包含一个或多个组件,这些组件可以是 ESP-IDF 的一部分,可以是项目自身组件目录的一部分,也可以从自定义组件目录添加( 见上文)。

组件是 COMPONENT_DIRS 列表中包含 CMakeLists.txt 文件的任何目录。

搜索组件

搜索 COMPONENT_DIRS 中的目录列表以查找项目的组件,此列表中的目录可以是组件自身(即包含 CMakeLists.txt 文件的目录),也可以是子目录为组件的顶级目录。

当 CMake 运行项目配置时,它会记录本次构建包含的组件列表,它可用于调试某些组件的添加/排除。

同名组件

ESP-IDF 在搜索所有待构建的组件时,会按照 COMPONENT_DIRS 指定的顺序依次进行,这意味着在默认情况下,首先搜索 ESP-IDF 内部组件,然后是项目组件,最后是 EXTRA_COMPONENT_DIRS 中的组件。如果这些目录中的两个或者多个包含具有相同名字的组件,则使用搜索到的最后一个位置的组件。这就允许将组件复制到项目目录中再修改以覆盖 ESP-IDF 组件,如果使用这种方式,ESP-IDF 目录本身可以保持不变。

最小的组件 CMakeLists 文件

最小组件 CMakeLists.txt 文件内容如下:

set(COMPONENT_SRCS "foo.c")
set(COMPONENT_ADD_INCLUDEDIRS "include")
register_component()
  • COMPONENT_SRCS 是用空格分隔的源文件列表(*.c*.cpp*.cc*.S),里面所有的源文件都将会编译进组件库中。

  • COMPONENT_ADD_INCLUDEDIRS 是用空格分隔的目录列表,里面的路径会被添加到所有需要该组件的组件(包括 main 组件)全局 include 搜索路径中。

  • register_component() 使用上述设置的变量将组件添加到构建系统中,构建生成与组件同名的库,并最终被链接到应用程序中。如果因为使用了 CMake 中的 if 命令 或类似命令而跳过了这一步,那么该组件将不会被添加到构建系统中。

上述目录通常设置为相对于 CMakeLists.txt 文件的相对路径,当然也可以设置为绝对路径。

有关更完整的 CMakeLists.txt 示例,请参阅 组件 CMakeLists 示例

预设的组件变量

以下专用于组件的变量可以在组件 CMakeLists 中使用,但不建议修改:

  • COMPONENT_PATH:组件目录,即包含 CMakeLists.txt 文件的绝对路径,它与 CMAKE_CURRENT_SOURCE_DIR 变量一样,路径中不能包含空格。

  • COMPONENT_NAME:组件名,与组件目录名相同。

  • COMPONENT_TARGET:库目标名,它由构建系统在内部为组件创建。

以下变量在项目级别中被设置,但可在组件 CMakeLists 中使用:

  • PROJECT_NAME:项目名,在项目 CMakeLists.txt 文件中设置。

  • PROJECT_PATH:项目目录(包含项目 CMakeLists 文件)的绝对路径,与 CMAKE_SOURCE_DIR 变量相同。

  • COMPONENTS:此次构建中包含的所有组件的名称,具体格式为用分号隔开的 CMake 列表。

  • CONFIG_*:项目配置中的每个值在 cmake 中都对应一个以 CONFIG_ 开头的变量。更多详细信息请参阅 Kconfig

  • IDF_VER:ESP-IDF 的 git 版本号,由 git describe 命令生成。

  • IDF_VERSION_MAJORIDF_VERSION_MINORIDF_VERSION_PATCH: ESP-IDF 的组件版本,可用于条件表达式。请注意这些信息的精确度不如 IDF_VER 变量,版本号 v4.0-dev-*, v4.0-beta1, v4.0-rc1 和 v4.0 对应的 IDF_VERSION_* 变量值是相同的,但是 IDF_VER 的值是不同的。

  • IDF_TARGET:项目的硬件目标名称。

  • PROJECT_VER:项目版本号。

    • 如果在项目 CMakeLists.txt 文件中设置了 PROJECT_VER 变量,则该变量值可以使用。

    • 或者,如果 ${PROJECT_PATH}/version.txt 文件存在,其内容会用作 PROJECT_VER 的值。

    • 或者,如果项目位于某个 Git 仓库中,则使用 git describe 命令的输出作为 PROJECT_VER 的值。

    • 否则,PROJECT_VER 的值为空。

如果您在组件的 CMakeLists.txt 中修改以上变量,并不会影响其他组件的构建,但可能会使该组件变得难以构建或调试。

  • COMPONENT_ADD_INCLUDEDIRS:相对于组件目录的相对路径,为被添加到所有需要该组件的其他组件的全局 include 搜索路径中。如果某个 include 路径仅仅在编译当前组件时需要,请将其添加到 COMPONENT_PRIV_INCLUDEDIRS 中。

  • COMPONENT_REQUIRES 是一个用空格分隔的组件列表,列出了当前组件依赖的其他组件。如果当前组件有一个头文件位于 COMPONENT_ADD_INCLUDEDIRS 目录下,且该头文件包含了另一个组件的头文件,那么这个被依赖的组件需要在 COMPONENT_REQUIRES 中指出。这种依赖关系可以是递归的。

    COMPONENT_REQUIRES 可以为空,因为所有的组件都需要一些常用的组件(如 newlib 组件提供的 libc 库、freertos 组件提供的 RTOS 功能),这些通用组件已经在项目级变量 COMPONENT_REQUIRES_COMMON 中被设置。

    如果一个组件仅需要额外组件的头文件来编译其源文件(而不是全局引入它们的头文件),则这些被依赖的组件需要在 COMPONENT_PRIV_REQUIRES 中指出。

    请参阅 组件依赖,查看详细信息。

可选的组件特定变量

以下变量可在 CMakeLists.txt 中进行设置,用以控制该组件的构建行为:

  • COMPONENT_PRIV_INCLUDEDIRS:相对于组件目录的相对路径,仅会被添加到该组件的 include 搜索路径中。

  • COMPONENT_PRIV_REQUIRES:以空格分隔的组件列表,用于编译或链接当前组件的源文件。这些组件的头文件路径不会传递给其余需要它的组件,仅用于编译当前组件的源代码。更多详细信息请参阅 组件依赖

  • COMPONENT_SRCS:要编译进当前组件的源文件的路径,推荐使用此方法向构建系统中添加源文件。

  • COMPONENT_SRCDIRS:相对于组件目录的源文件目录路径,用于搜索源文件(*.cpp*.c*.S)。匹配成功的源文件会替代 COMPONENT_SRCS 中指定的源文件,进而被编译进组件。即设置 COMPONENT_SRCDIRS 会导致 COMPONENT_SRCS 会被忽略。此方法可以很容易地将源文件整体导入到组件中,但并不推荐使用(详情请参阅 文件通配符 & 增量构建)。

  • COMPONENT_SRCEXCLUDE:需要从组件中剔除的源文件路径。当某个目录中有大量的源文件需要被导入组件中,但同时又有个别文件不需要导入时,可以配合 COMPONENT_SRCDIRS 变量一起设置。路径可以是相对于组件目录的相对路径,也可以是绝对路径。

  • COMPONENT_ADD_LDFRAGMENTS:组件使用的链接片段文件的路径,用于自动生成链接器脚本文件。详细信息请参阅 链接脚本生成机制

注解

如果没有设置 COMPONENT_SRCDIRS 或 COMPONENT_SRCS,组件不会被编译成库文件,但仍可以被添加到 include 路径中,以便在编译其他组件时使用。

组件编译控制

在编译特定组件的源文件时,可以使用 target_compile_options 命令来传递编译器选项:

target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-unused-variable)

这条命令封装了 CMake 的 target_compile_options 命令。

如果给单个源文件指定编译器标志,可以使用 CMake 的 set_source_files_properties 命令:

set_source_files_properties(mysrc.c
    PROPERTIES COMPILE_FLAGS
    -Wno-unused-variable
)

如果上游代码在编译的时候发出了警告,那这么做可能会很有效。

请注意,上述两条命令只能在组件 CMakeLists 文件的 register_component() 命令之后调用。

组件配置

每个组件都可以包含一个 Kconfig 文件,和 CMakeLists.txt 放在同一目录下。Kconfig 文件中包含要添加到该组件配置菜单中的一些配置设置信息。

运行 menuconfig 时,可以在 Component Settings 菜单栏下找到这些设置。

创建一个组件的 Kconfig 文件,最简单的方法就是使用 ESP-IDF 中现有的 Kconfig 文件作为模板,在这基础上进行修改。

有关示例请参阅 添加条件配置

预处理器定义

ESP-IDF 构建系统会在命令行中添加以下 C 预处理器定义:

  • ESP_PLATFORM:可以用来检测在 ESP-IDF 内发生了构建行为。

  • IDF_VER:定义 git 版本字符串,例如:v2.0 用于标记已发布的版本,v1.0-275-g0efaa4f 则用于标记任意某次的提交记录。

  • PROJECT_VER:项目版本号,详细信息请参阅 预设的组件变量

  • PROJECT_NAME:项目名称,定义在项目 CMakeLists.txt 文件中。

组件依赖

编译各个组件时,ESP-IDF 系统会递归评估其组件。

每个组件的源文件都会使用以下路径中的头文件进行编译:

  • 当前组件的 COMPONENT_ADD_INCLUDEDIRS 和 COMPONENT_PRIV_INCLUDEDIRS

  • 当前组件的 COMPONENT_REQUIRES 和 COMPONENT_PRIV_REQUIRES 变量指定的其他组件(即当前组件的所有公共和私有依赖项)所设置的 COMPONENT_ADD_INCLUDEDIRS

  • 所有组件的 COMPONENT_REQUIRES 做递归操作,即该组件递归运算后的所有公共依赖项。

编写组件

  • COMPONENT_REQUIRES 需要包含所有被当前组件的公共头文件 #include 的头文件所在的组件。

  • COMPONENT_PRIV_REQUIRES 需要包含被当前组件的源文件 #include 的头文件所在的组件(除非已经被设置在了 COMPONENT_PRIV_REQUIRES 中)。或者是当前组件正常工作必须要链接的组件。

  • COMPONENT_REQUIRESCOMPONENT_PRIV_REQUIRES 需要在调用 register_component() 之前设置。

  • COMPONENT_REQUIRES 和 COMPONENT_PRIV_REQUIRES 的值不能依赖于任何配置选项(CONFIG_xxx),这是因为在配置加载之前,依赖关系就已经被展开。其它组件变量(比如 COMPONENT_SRCS 和 COMPONENT_ADD_INCLUDEDIRS)可以依赖配置选择。

  • 如果当前组件除了 COMPONENT_REQUIRES_COMMON 中设置的通用组件(比如 RTOS、libc 等)外,并不依赖其它组件,那么上述两个 REQUIRES 变量可以为空。

如果组件仅支持某些硬件目标(即依赖于特定的 IDF_TARGET),则可以调用 require_idf_targets(NAMES...) CMake 函数来声明这个需求。在这种情况下,如果构建系统导入了不支持当前硬件目标的组件时就会报错。

创建项目

  • 默认情况下,每个组件都会包含在构建系统中。

  • 如果将 COMPONENTS 变量设置为项目直接使用的最小组件列表,那么构建系统会导入:

    • COMPONENTS 中明确提及的组件。

    • 这些组件的依赖项(以及递归运算后的组件)。

    • 每个组件都依赖的通用组件。

  • 将 COMPONENTS 设置为所需组件的最小列表,可以显著减少项目的构建时间。

构建系统中依赖处理的实现细节

  • 在 CMake 配置进程的早期阶段会运行 expand_requirements.cmake 脚本。该脚本会对所有组件的 CMakeLists.txt 文件进行局部的运算,得到一张组件依赖关系图(此图可能会有闭环)。此图用于在构建目录中生成 component_depends.cmake 文件。

  • CMake 主进程会导入该文件,并以此来确定要包含到构建系统中的组件列表(内部使用的 BUILD_COMPONENTS 变量)。BUILD_COMPONENTS 变量已排好序,依赖组件会排在前面。由于组件依赖关系图中可能存在闭环,因此不能保证每个组件都满足该排序规则。如果给定相同的组件集和依赖关系,那么最终的排序结果应该是确定的。

  • CMake 会将 BUILD_COMPONENTS 的值以 “Component names:” 的形式打印出来。

  • 然后执行构建系统中包含的每个组件的配置。

  • 每个组件都被正常包含在构建系统中,然后再次执行 CMakeLists.txt 文件,将组件库加入构建系统。

组件依赖顺序

BUILD_COMPONENTS 变量中组件的顺序决定了构建过程中的其它顺序,包括:

  • 项目导入 project_include.cmake 文件的顺序。

  • 生成用于编译(通过 -I 参数)的头文件路径列表的顺序。请注意,对于给定组件的源文件,仅需将该组件的依赖组件的头文件路径告知编译器。

构建的内部过程

关于 CMake 以及 CMake 命令的详细信息,请参阅 CMake v3.5 官方文档 。

project.cmake 的内容

当项目 CMakeLists 文件导入 project.cmake 文件时,project.cmake 会定义一些实用的模块和全局变量。如果系统环境中没有设置 IDF_PATH,那么它还会自动设置 IDF_PATH 变量。

project.cmake 文件还重写了 CMake 内置的 project 函数,以添加所有 ESP-IDF 项目特有的功能。

project 函数

自定义的 project() 函数会执行以下步骤:

  • 确定硬件目标(由 IDF_TARGET 环境变量设置),并将其保存在 CMake cache 中。如果环境变量中设置的硬件目标与 CMake cache 中的不匹配,则会报错并退出。

  • 计算组件依赖,并构造 BUILD_COMPONENTS 变量,它是包含所有需要导入到构建系统中的组件列表(详情请见上文)。

  • 查找项目中所有的组件(搜索 COMPONENT_DIRS,并按 COMPONENTS 进行过滤(前提是设置了该变量)。

  • 从 sdkconfig 文件中加载项目配置信息,生成 sdkconfig.cmake 和 sdkconfig.h 文件,分别用在 CMake 和 C/C++ 中定义配置项。如果项目配置发生了更改,CMake 会自动重新运行,重新生成上述两个文件,接着重新配置项目。

  • 根据硬件目标(IDF_TARGET)的值,将 CMAKE_TOOLCHAIN_FILE 变量设置为相应的工具链文件。

  • 调用 CMake 的 project 函数 声明实际的 CMake-level 项目。

  • 加载 git 版本号。如果在 git 中检出了新的版本,就会使用一些技巧重新运行 CMake。详情请参考 文件通配符 & 增量构建

  • 从包含有 project_include.cmake 文件的组件中导入该文件。

  • 将每个组件都添加到构建系统中。每个组件的 CMakeLists 文件都会调用 register_component 函数,它会调用 CMake 的 add_library 函数来添加一个库,然后添加源文件、编译选项等。

  • 将最终的应用程序可执行文件添加到构建系统中。

  • 返回并为组件之间指定依赖关系(将每个组件的公共头文件目录添加到其他组件中)。

更多详细信息请参阅 /tools/cmake/project.cmake 文件。

CMake 调试

调试 ESP-IDF CMake 构建系统的一些技巧:

  • CMake 运行时,会打印大量诊断信息,包括组件列表和组件路径。

  • 运行 cmake -DDEBUG=1,IDF 构建系统会生成更详细的诊断输出。

  • 运行 cmake 时指定 --trace 或 --trace-expand 选项会提供大量有关控制流信息。详情请参考 CMake 命令行文档

警告未定义的变量

默认情况下,idf.py 在调用 CMake 时会给它传递 --warn-uninitialized 标志,如果在构建的过程中引用了未定义的变量,CMake 会打印警告。这对查找有错误的 CMake 文件非常有用。

如果您不想启用此功能,可以给 idf.py 传递 --no-warnings 标志。

覆盖项目的部分设置

project_include.cmake

如果组件的某些构建行为需要在组件 CMakeLists 文件之前被执行,您可以在组件目录下创建名为 project_include.cmake 的文件,project.cmake 在运行过程中会导入此 CMake 文件。

project_include.cmake 文件在 ESP-IDF 内部使用,以定义项目范围内的构建功能,比如 esptool.py 的命令行参数和 bootloader 这个特殊的应用程序。

与组件 CMakeLists.txt 文件有所不同,在导入``project_include.cmake`` 文件的时候,当前源文件目录(即 CMAKE_CURRENT_SOURCE_DIR)和工作目录为项目目录。如果想获得当前组件的绝对路径,可以使用 COMPONENT_PATH 变量。

请注意,project_include.cmake 对于大多数常见的组件并不是必需的。例如给项目添加 include 搜索目录,给最终的链接步骤添加 LDFLAGS 选项等等都可以通过 CMakeLists.txt 文件来自定义。详细信息请参考 可选的项目变量

project_include.cmake 文件会按照 BUILD_COMPONENTS 变量中组件的顺序(由 CMake 记录)依次导入。即只有在当前组件所有依赖组件的 project_include.cmake 文件都被导入后,当前组件的 project_include.cmake 文件才会被导入,除非两个组件在同一个依赖闭环中。如果某个 project_include.cmake 文件依赖于另一组件设置的变量,则要特别注意上述情况。更多详情请参阅 构建系统中依赖处理的实现细节

在 project_include.cmake 文件中设置变量或目标时要格外小心,这些值被包含在项目的顶层 CMake 文件中,因此他们会影响或破坏所有组件的功能。

KConfig.projbuild

与 project_include.cmake 类似,也可以为组件定义一个 KConfig 文件以实现全局的 组件配置。如果要在 menuconfig 的顶层添加配置选项,而不是在 “Component Configuration” 子菜单中,则可以在 CMakeLists.txt 文件所在目录的 KConfig.projbuild 文件中定义这些选项。

在此文件中添加配置时要小心,因为这些配置会包含在整个项目配置中。在可能的情况下,请为 组件配置 创建 KConfig 文件。

仅配置组件

仅配置组件是一类不包含源文件的特殊组件,仅包含 Kconfig.projbuildKConfig 和 CMakeLists.txt 文件,该 CMakeLists.txt 文件仅有一行代码,调用了 register_config_only_component() 函数。此函数会将组件导入到项目构建中,但不会构建任何库,也不会将头文件添加到任何 include 搜索路径中。

如果 CMakeLists.txt 文件没有调用 register_component() 或 register_config_only_component(),那么该文件将会被排除在项目构建之外。根据项目的配置,有时可能需要这么做。

组件 CMakeLists 示例

因为构建环境试图设置大多数情况都能工作的合理默认值,所以组件 CMakeLists.txt 文件可能非常小,甚至是空的,请参考 最小的组件 CMakeLists 文件。但有些功能往往需要覆盖 预设的组件变量 才能实现。

以下是组件 CMakeLists 文件的更高级的示例。

添加条件配置

配置系统可用于根据项目配置中选择的选项有条件地编译某些文件。

Kconfig:

config FOO_ENABLE_BAR
    bool "Enable the BAR feature."
    help
        This enables the BAR feature of the FOO component.

CMakeLists.txt:

set(COMPONENT_SRCS "foo.c" "more_foo.c")

if(CONFIG_FOO_ENABLE_BAR)
    list(APPEND COMPONENT_SRCS "bar.c")
endif()

上述示例使用了 CMake 的 if 函数和 list APPEND 函数。

也可用于选择或删除某一实现,如下所示:

Kconfig:

config ENABLE_LCD_OUTPUT
    bool "Enable LCD output."
    help
        Select this if your board has a LCD.

config ENABLE_LCD_CONSOLE
    bool "Output console text to LCD"
    depends on ENABLE_LCD_OUTPUT
    help
        Select this to output debugging output to the lcd

config ENABLE_LCD_PLOT
    bool "Output temperature plots to LCD"
    depends on ENABLE_LCD_OUTPUT
    help
        Select this to output temperature plots

CMakeLists.txt:

if(CONFIG_ENABLE_LCD_OUTPUT)
    set(COMPONENT_SRCS lcd-real.c lcd-spi.c)
else()
    set(COMPONENT_SRCS lcd-dummy.c)
endif()

# 如果启用了控制台或绘图功能,则需要加入字体
if(CONFIG_ENABLE_LCD_CONSOLE OR CONFIG_ENABLE_LCD_PLOT)
    list(APPEND COMPONENT_SRCS "font.c")
endif()

硬件目标的的条件判断

CMake 文件可以使用 IDF_TARGET 变量来获取当前的硬件目标。

此外,如果当前的硬件目标是 xyz``(即 ``IDF_TARGET=xyz),那么 Kconfig 变量 CONFIG_IDF_TARGET_XYZ 同样也会被设置。

请注意,组件可以依赖 IDF_TARGET 变量,但不能依赖这个 Kconfig 变量。同样也不可在 CMake 文件的 include 语句中使用 Kconfig 变量,在这种上下文中可以使用 IDF_TARGET

生成源代码

有些组件的源文件可能并不是由组件本身提供,而必须从另外的文件生成。假设组件需要一个头文件,该文件由 BMP 文件转换后(使用 bmp2h 工具)的二进制数据组成,然后将头文件包含在名为 graphics_lib.c 的文件中:

add_custom_command(OUTPUT logo.h
    COMMAND bmp2h -i ${COMPONENT_DIR}/logo.bmp -o log.h
    DEPENDS ${COMPONENT_DIR}/logo.bmp
    VERBATIM)

add_custom_target(logo DEPENDS logo.h)
add_dependencies(${COMPONENT_LIB} logo)

set_property(DIRECTORY "${COMPONENT_DIR}" APPEND PROPERTY
    ADDITIONAL_MAKE_CLEAN_FILES logo.h)

这个示例改编自 CMake 的一则 FAQ,其中还包含了一些同样适用于 ESP-IDF 构建系统的示例。

这个示例会在当前目录(构建目录)中生成 logo.h 文件,而 logo.bmp 会随组件一起提供在组件目录中。因为 logo.h 是一个新生成的文件,一旦项目需要清理,该文件也应该要被清除。因此,要将该文件添加到 ADDITIONAL_MAKE_CLEAN_FILES 属性中。

注解

如果需要生成文件作为项目 CMakeLists.txt 的一部分,而不是作为组件 CMakeLists.txt 的一部分,此时需要使用 ${PROJECT_PATH} 替代 ${COMPONENT_DIR},使用 ${PROJECT_NAME}.elf 替代 ${COMPONENT_LIB}

如果某个源文件是从其他组件中生成,且包含 logo.h 文件,则需要调用 add_dependencies, 在这两个组件之间添加一个依赖项,以确保组件源文件按照正确顺序进行编译。

嵌入二进制数据

有时您的组件希望使用一个二进制文件或者文本文件,但是您又不希望将它们重新格式化为 C 源文件,这时,您可以在组件 CMakeLists 中添加 COMPONENT_EMBED_FILES 变量,指定要嵌入的文件名称(以空格分隔):

set(COMPONENT_EMBED_FILES server_root_cert.der)

或者,如果文件是字符串,则可以设置 COMPONENT_EMBED_TXTFILES 变量,把文件的内容转成以 null 结尾的字符串嵌入:

set(COMPONENT_EMBED_TXTFILES server_root_cert.pem)

文件的内容会被添加到 Flash 的 .rodata 段,用户可以通过符号名来访问,如下所示:

extern const uint8_t server_root_cert_pem_start[] asm("_binary_server_root_cert_pem_start");
extern const uint8_t server_root_cert_pem_end[]   asm("_binary_server_root_cert_pem_end");

符号名会根据文件全名生成,如 COMPONENT_EMBED_FILES 中所示,字符 /. 等都会被下划线替代。符号名称中的 _binary 前缀由 objcopy 命令添加,对文本文件和二进制文件都是如此。

如果要将文件嵌入到项目中,而非组件中,可以调用 target_add_binary_data 函数:

target_add_binary_data(myproject.elf "main/data.bin" TEXT)

并这行代码放在项目 CMakeLists.txt 的 project() 命令之后,修改 myproject.elf 为你自己的项目名。如果最后一个参数是 TEXT,那么构建系统会嵌入以 null 结尾的字符串,如果最后一个参数被设置为 BINARY,则将文件内容按照原样嵌入。

有关使用此技术的示例,请参考 protocols/https_request,证书文件的内容会在编译时从 .pem 文件中加载。

代码和数据的存放

ESP-IDF 还支持自动生成链接脚本,它允许组件通过链接片段文件定义其代码和数据在内存中的存放位置。构建系统会处理这些链接片段文件,并将处理后的结果扩充进链接脚本,从而指导应用程序二进制文件的链接过程。更多详细信息与快速上手指南,请参阅 链接脚本生成机制

完全覆盖组件的构建过程

当然,在有些情况下,上面提到的方法不一定够用。如果组件封装了另一个第三方组件,而这个第三方组件并不能直接在 ESP-IDF 的构建系统中工作,在这种情况下,就需要放弃 ESP-IDF 的构建系统,改为使用 CMake 的 ExternalProject 功能。组件 CMakeLists 示例如下:

# 用于 quirc 的外部构建过程,在源目录中运行并生成 libquirc.a
externalproject_add(quirc_build
    PREFIX ${COMPONENT_DIR}
    SOURCE_DIR ${COMPONENT_DIR}/quirc
    CONFIGURE_COMMAND ""
    BUILD_IN_SOURCE 1
    BUILD_COMMAND make CC=${CMAKE_C_COMPILER} libquirc.a
    INSTALL_COMMAND ""
    )

# 将 libquirc.a 添加到构建系统中
add_library(quirc STATIC IMPORTED GLOBAL)
add_dependencies(quirc quirc_build)

set_target_properties(quirc PROPERTIES IMPORTED_LOCATION
    ${COMPONENT_DIR}/quirc/libquirc.a)
set_target_properties(quirc PROPERTIES INTERFACE_INCLUDE_DIRECTORIES
    ${COMPONENT_DIR}/quirc/lib)

set_directory_properties( PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES
    "${COMPONENT_DIR}/quirc/libquirc.a")

(上述 CMakeLists.txt 可用于创建名为 quirc 的组件,该组件使用自己的 Makefile 构建 quirc 项目。)

  • externalproject_add 定义了一个外部构建系统。

    • 设置 SOURCE_DIRCONFIGURE_COMMANDBUILD_COMMAND 和 INSTALL_COMMAND。如果外部构建系统没有配置这一步骤,可以将 CONFIGURE_COMMAND 设置为空字符串。在 ESP-IDF 的构建系统中,一般会将 INSTALL_COMMAND 变量设置为空。

    • 设置 BUILD_IN_SOURCE,即构建目录与源目录相同。否则,您也可以设置 BUILD_DIR 变量。

    • 有关 externalproject_add() 命令的详细信息,请参阅 ExternalProject

  • 第二组命令添加了一个目标库,指向外部构建系统生成的库文件。为了添加 include 目录,并告知 CMake 该文件的位置,需要再设置一些属性。

  • 最后,生成的库被添加到 ADDITIONAL_MAKE_CLEAN_FILES 中。即执行 make clean 后会删除该库。请注意,构建系统中的其他目标文件不会被删除。

url: https://docs.espressif.com/projects/esp-idf/zh_CN/release-v4.2/esp32/api-guides/build-system.html?highlight=cmake#component-directories

  • 3
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值