构建系统

概述

一个 ESP-IDF 项目可以看作是多个不同组件的集合,例如一个显示当前湿度的网页服务器会包含以下组件:

ESP-IDF 基础库,包括 libc、ROM bindings 等

  • Wi-Fi 驱动
  • TCP/IP 协议栈
  • FreeRTOS 操作系统
  • 网页服务器
  • 湿度传感器的驱动
  • 负责将上述组件整合到一起的主程序

ESP-IDF 可以显式地指定和配置每个组件。在构建项目的时候,构建系统会前往 ESP-IDF 目录、项目目录和用户自定义组件目录(可选)中查找所有组件,允许用户通过文本菜单系统配置 ESP-IDF 项目中用到的每个组件。在所有组件配置结束后,构建系统开始编译整个项目。

构建系统使用

idf.py

idf.py 命令行工具提供了一个前端,可以帮助你轻松管理项目的构建过程,它管理了以下工具:

  • CMake,配置待构建的项目
  • Ninja,用于构建项目
  • esptool.py,烧录目标硬件设备

可通过 idf.py 配置构建系统,具体可参考相关文档。

直接使用 CMake

为了方便,idf.py 已经封装了 CMake 命令,但是你愿意,也可以直接调用 CMake。

当 idf.py 在执行某些操作时,它会打印出其运行的每条命令以便参考。例如运行 idf.py build 命令与在 bash shell(或者 Windows Command Prompt)中运行以下命令是相同的:

1
2
3
4
mkdir -p build
cd build
cmake .. -G Ninja # 或者 'Unix Makefiles'
ninja

在上面的命令列表中,cmake 命令对项目进行配置,并生成用于最终构建工具的构建文件。在这个例子中,最终构建工具是 Ninja: 运行 ninja 来构建项目。

没有必要多次运行 cmake。第一次构建后,往后每次只需运行 ninja 即可。如果项目需要重新配置,ninja 会自动重新调用 cmake。

若在 CMake 中使用 ninja 或 make,则多数 idf.py 子命令也会有其对应的目标,例如在构建目录下运行 make menuconfig 或 ninja menuconfig 与运行 idf.py menuconfig 是相同的。

在 IDE 中使用 CMake

还可以使用集成了 CMake 的 IDE,仅需将项目 CMakeLists.txt 文件的路径告诉 IDE 即可。集成 CMake 的 IDE 通常会有自己的构建工具(CMake 称之为“生成器”),它是组成 IDE 的一部分,用来构建源文件。

向 IDE 中添加除 build 目标以外的自定义目标(如添加 “flash” 目标到 IDE)时,建议调用 idf.py 命令来执行这些“特殊”的操作。

有关将 ESP-IDF 同 CMake 集成到 IDE 中的详细信息,请参阅 构建系统的元数据。

示例项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- myProject/
- CMakeLists.txt
- sdkconfig
- dependencies.lock
- bootloader_components/ - boot_component/ - CMakeLists.txt
- Kconfig
- src1.c
- components/ - component1/ - CMakeLists.txt
- Kconfig
- src1.c
- component2/ - CMakeLists.txt
- Kconfig
- src1.c
- include/ - component2.h
- managed_components/ - namespace__component-name/ - CMakelists.txt
- src1.c
- idf_component.yml
- include/ - src1.h
- main/ - CMakeLists.txt
- src1.c
- src2.c
- idf_component.yml
- build/

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

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

  • dependencies.lock“ 文件包含项目中当前使用的所有托管的组件及其版本。使用 IDF 组件管理器添加或更新项目组件时,会自动生成或更新 dependencies.lock 文件。因此,请勿手动编辑此文件!如果项目中没有组件包含 idf_component.yml 文件,则不会创建 dependencies.lock 文件。

  • idf_component.yml“ 是可选文件,里面包含组件的元数据及其依赖项。IDF 组件管理器使用该文件下载和解析这些依赖项。更多信息,请参阅 idf_component.yml

  • bootloader_components“ 是可选目录,里面包含了需要在引导加载项目中进行编译和链接的组件。并不是每个项目都需要这种自定义组件,但此类组件在引导加载程序需要修改以嵌入新功能时可能很有用。

  • components“ 是可选目录,里面包含了项目的部分自定义组件,并不是每个项目都需要这种自定义组件,但它有助于构建可复用的代码或者导入第三方(不属于 ESP-IDF)的组件。或者,你也可以在顶层 CMakeLists.txt 中设置 EXTRA_COMPONENT_DIRS 变量以查找其他指定位置处的组件。

  • main“ 目录是一个特殊的组件,它包含项目本身的源代码。”main” 是默认名称,CMake 变量 COMPONENT_DIRS 默认包含此组件,但你可以修改此变量。有关详细信息,请参阅 重命名 main 组件。如果项目中源文件较多,建议将其归于组件中,而不是全部放在 “main” 中。

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

  • managed_components“ 目录由 IDF 组件管理器创建,用于存储由 IDF 组件管理器管理的组件。每个托管组件通常包含 idf_component.yml 清单文件,定义了包括版本和依赖项在内的组件元数据。但对于那些来自 Git 仓库的组件,清单文件是可选的。请勿手动修改 “managed_components“ 目录下的内容。如果需要进行更改,可以将组件复制到 components 目录下。”managed_components“ 目录通常不在 Git 中进行版本控制,也不会与项目源代码一起分发。

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

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

项目 CMakeLists 文件

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

最小 CMakeLists 文件示例

1
2
3
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(myProject)

必要部分

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

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

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

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

可选的项目变量

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

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

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

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

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

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

组件 CMakeLists 文件

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

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

搜索组件

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

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

ESP-IDF 在搜索所有待构建的组件时,会按照以下优先级搜索组件目录(从高到低):

  • 项目目录下的组件
  • EXTRA_COMPONENT_DIRS 中的组件
  • 项目目录下 managed_components 目录中的组件。这些组件由 IDF Component Manager 下载并管理。
  • IDF_PATH/components 目录下的组件

如果有两个及以上同名组件,构建系统会使用优先级更高的组件。这使得我们可以在项目中覆盖 ESP-IDF 提供的组件。只需要复制 ESP-IDF 组件到项目目录下,然后修改它。这样可以在修改组件的同时,不修改 ESP-IDF 的源代码。

最小组件 CMakeLists 文件

最小组件 CMakeLists.txt 文件通过使用 idf_component_register 将组件添加到构建系统中。

1
2
idf_component_register(SRCS "foo.c" "bar.c"
INCLUDE_DIRS "include" REQUIRES mbedtls)
  • SRCS 是源文件列表(*.c*.cpp*.cc*.S),里面所有的源文件都将会编译进组件库中。
  • INCLUDE_DIRS 是目录列表,里面的路径会被添加到所有需要该组件的组件(包括 main 组件)全局 include 搜索路径中。
  • REQUIRES 实际上并不是必需的,但通常需要它来声明该组件需要使用哪些其它组件,请参考 组件依赖。

上述命令会构建生成与组件同名的库,并最终被链接到应用程序中。

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

还有其它参数可以传递给 idf_component_register,具体可参考 here。