本系列文章主要介绍ESP-IDF构建系统的实现原理,以及组件
等相关概念。
概述
一个 ESP-IDF 项目可以看作是多个不同组件的集合,例如一个显示当前湿度的网页服务器会包含以下组件:
- ESP-IDF 基础库,包括 libc、ROM bindings 等
- Wi-Fi 驱动
- TCP/IP 协议栈
- FreeRTOS 操作系统
- 网页服务器
- 湿度传感器的驱动
- 负责将上述组件整合到一起的主程序
ESP-IDF 可以显式地指定和配置每个组件。在构建项目的时候,构建系统会前往 ESP-IDF 目录、项目目录和用户自定义组件目录(可选)中查找所有组件,允许用户通过文本菜单系统配置 ESP-IDF 项目中用到的每个组件。在所有组件配置结束后,构建系统开始编译整个项目。
概念
项目
特指一个目录,其中包含了构建可执行应用程序所需的全部文件和配置,以及其他支持型文件,例如分区表、数据/文件系统分区和引导程序。
项目配置
保存在项目根目录下名为 sdkconfig
的文件中,可以通过 idf.py menuconfig
进行修改,且一个项目只能包含一个项目配置。
应用程序
是由 ESP-IDF 构建得到的可执行文件。一个项目通常会构建两个应用程序:项目应用程序(可执行的主文件,即用户自定义的固件)和引导程序(启动并初始化项目应用程序)。
组件
是模块化且独立的代码,会被编译成静态库(.a 文件)并链接到应用程序。部分组件由 ESP-IDF 官方提供,其他组件则来源于其它开源项目。
目标
特指运行构建后应用程序的硬件设备。运行 idf.py –list-targets
可以查看当前 ESP-IDF 版本中支持目标的完整列表。
请注意,以下内容并不属于项目的组成部分:
ESP-IDF
并不是项目的一部分,它独立于项目,通过 IDF_PATH
环境变量(保存 esp-idf
目录的路径)链接到项目,从而将 IDF 框架与项目分离。
交叉编译工具链并不是项目的组成部分,它应该被安装在系统 PATH 环境变量中。
使用构建系统
idf.py
idf.py 命令行工具提供了一个前端,可以帮助你轻松管理项目的构建过程,它管理了以下工具:
- CMake,配置待构建的项目
- Ninja,用于构建项目
- esptool.py,烧录目标硬件设备
可通过 idf.py 配置构建系统,具体可参考 ESP-IDF 前端工具 idf.py。
直接使用CMake
为了方便,idf.py
已经封装了 CMake
命令,但是你愿意,也可以直接调用 CMake
。
当 idf.py
在执行某些操作时,它会打印出其运行的每条命令以便参考。例如运行 idf.py build
命令与在 bash shell(或者 Windows Command Prompt)中运行以下命令是相同的:
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
是相同的。
使用Ninja/Make来烧录
可以直接使用 ninja 或 make 运行如下命令来构建项目并烧录:
ninja flash
或:
make app-flash
可用的目标还包括:flash
、app-flash
(仅用于 app)、bootloader-flash
(仅用于 bootloader)。
以这种方式烧录时,可以通过设置 ESPPORT
和 ESPBAUD
环境变量来指定串口设备和波特率。可以在操作系统或 IDE 项目中设置该环境变量,或者直接在命令行中进行设置:
ESPPORT=/dev/ttyUSB0 ninja flash
在命令的开头为环境变量赋值属于 Bash shell 的语法,可在 Linux 、macOS 和 Windows 的类 Bash shell 中运行,但在 Windows Command Prompt 中无法运行。
或:
make -j3 app-flash ESPPORT=COM4 ESPBAUD=2000000
在命令末尾为变量赋值属于 make 的语法,适用于所有平台的 make。
示例项目
一个ESP-IDF项目的目录树结构通常如下所示:
- myProject/
- CMakeLists.txt
- sdkconfig
- components/
- component1/
- CMakeLists.txt
- Kconfig
- src1.c
- component2/
- CMakeLists.txt
- Kconfig
- src1.c
- include/
- component2.h
- main/
- CMakeLists.txt
- src1.c
- src2.c
- build/
该示例项目 “myProject” 包含以下组成部分:
-
顶层项目
CMakeLists.txt
文件,这是 CMake 用于学习如何构建项目的主要文件,可以在这个文件中设置项目全局的 CMake 变量。顶层项目CMakeLists.txt
文件会导入/tools/cmake/project.cmake
文件,由它负责实现构建系统的其余部分。该文件最后会设置项目的名称,并定义该项目。 -
“sdkconfig” 项目配置文件,执行
idf.py menuconfig
时会创建或更新此文件,文件中保存了项目中所有组件(包括 ESP-IDF 本身)的配置信息。sdkconfig
文件可能会也可能不会被添加到项目的源码管理系统中。 -
可选的
components
目录中包含了项目的部分自定义组件,并不是每个项目都需要这种自定义组件,但它有助于构建可复用的代码或者导入第三方(不属于 ESP-IDF)的组件。或者,你也可以在顶层CMakeLists.txt
中设置EXTRA_COMPONENT_DIRS
变量以查找其他指定位置处的组件。 -
main
目录是一个特殊的组件,它包含项目本身的源代码。main
是默认名称,CMake 变量COMPONENT_DIRS
默认包含此组件,但你可以修改此变量。有关详细信息,请参阅 重命名main
组件。如果项目中源文件较多,建议将其归于组件中,而不是全部放在main
中。 -
build
目录是存放构建输出的地方,如果没有此目录,idf.py
会自动创建。CMake 会配置项目,并在此目录下生成临时的构建文件。随后,在主构建进程的运行期间,该目录还会保存临时目标文件、库文件以及最终输出的二进制文件。此目录通常不会添加到项目的源码管理系统中,也不会随项目源码一同发布。
每个组件目录都包含一个 CMakeLists.txt
文件,里面会定义一些变量以控制该组件的构建过程,以及其与整个项目的集成。
每个组件还可以包含一个 Kconfig
文件,它用于定义 menuconfig
时展示的 组件配置 选项。某些组件可能还会包含 Kconfig.projbuild
和 project_include.cmake
特殊文件,它们用于 覆盖项目的部分设置。
以下是转换为Markdown格式后的内容,放置在Markdown代码块中:
项目 CMakeLists 文件
每个项目都有一个顶层 CMakeLists.txt
文件,包含整个项目的构建设置。默认情况下,项目 CMakeLists 文件会非常小。
最小 CMakeLists 文件示例
最小项目:
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
文件只能定义一个项目。
可选的项目变量
以下这些变量都有默认值,用户可以覆盖这些变量值以自定义构建行为。
-
COMPONENT_DIRS
:组件的搜索目录,默认为IDF_PATH/components
、PROJECT_DIR/components
和EXTRA_COMPONENT_DIRS
。如果你不想在这些位置搜索组件,请覆盖此变量。 -
EXTRA_COMPONENT_DIRS
:用于搜索组件的其它可选目录列表。路径可以是相对于项目目录的相对路径,也可以是绝对路径。 -
COMPONENTS
:要构建进项目中的组件名称列表,默认为COMPONENT_DIRS
目录下检索到的所有组件。使用此变量可以“精简”项目以缩短构建时间。请注意,如果一个组件通过COMPONENT_REQUIRES
指定了它依赖的另一个组件,则会自动将其添加到COMPONENTS
中,所以COMPONENTS
列表可能会非常短。
以上变量中的路径可以是绝对路径,或者是相对于项目目录的相对路径。
请使用 cmake
中的 set
命令 来设置这些变量,如 set(VARIABLE "VALUE")
。请注意,set()
命令需放在 include(...)
之前,cmake_minimum(...)
之后。
重命名 main 组件
构建系统会对 main
组件进行特殊处理。假如 main
组件位于预期的位置(即 ${PROJECT_PATH}/main
),那么它会被自动添加到构建系统中。其他组件也会作为其依赖项被添加到构建系统中,这使用户免于处理依赖关系,并提供即时可用的构建功能。重命名 main
组件会减轻上述这些幕后工作量,但要求用户指定重命名后的组件位置,并手动为其添加依赖项。重命名 main
组件的步骤如下:
-
重命名
main
目录。 -
在项目
CMakeLists.txt
文件中设置EXTRA_COMPONENT_DIRS
,并添加重命名后的main
目录。 -
在组件的
CMakeLists.txt
文件中设置COMPONENT_REQUIRES
或COMPONENT_PRIV_REQUIRES
以指定依赖项。
覆盖默认的构建规范
构建系统设置了一些全局的构建规范(编译标志、定义等),这些规范可用于编译来自所有组件的所有源文件。
例如,其中一个默认的构建规范是编译选项 -Wextra
。假设一个用户想用 -Wno-extra
来覆盖这个选项,应在 project()
之后进行:
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(myProject)
idf_build_set_property(COMPILE_OPTIONS "-Wno-error" APPEND)
这确保了用户设置的编译选项不会被默认的构建规范所覆盖,因为默认的构建规范是在 project()
内设置的。
公众号 | FunIO
微信搜一搜 “funio”,发现更多精彩内容。
个人博客 | blog.boringhex.top