1构建系统(CMake 版)
2********************
3
4:link_to_translation:`en:[English]`
5
6本文档主要介绍 ESP-IDF 构建系统的实现原理以及 ``组件`` 等相关概念。如需您想了解如何组织和构建新的 ESP-IDF 项目或组件,请阅读本文档。
7
8.. only:: esp32
9
10   .. 注解:: 本文档将介绍基于 CMake 的构建系统,它是 ESP-IDF V4.0 及以上版本的默认系统。此外 ESP-IDF 还支持 :doc:`基于 GNU Make 的构建系统 <build-system-legacy>`,基于 GNU Make 的构建系统是 ESP-IDF V4.0 以下版本的默认系统。
11
12
13概述
14====
15
16一个 ESP-IDF 项目可以看作是多个不同组件的集合,例如一个显示当前湿度的网页服务器会包含以下组件:
17
18- ESP-IDF 基础库,包括 libc、ROM bindings 等
19- Wi-Fi 驱动
20- TCP/IP 协议栈
21- FreeRTOS 操作系统
22- 网页服务器
23- 湿度传感器的驱动
24- 负责将上述组件整合到一起的主程序
25
26ESP-IDF 可以显式地指定和配置每个组件。在构建项目的时候,构建系统会前往 ESP-IDF 目录、项目目录和用户自定义组件目录(可选)中查找所有组件,允许用户通过文本菜单系统配置 ESP-IDF 项目中用到的每个组件。在所有组件配置结束后,构建系统开始编译整个项目。
27
28概念
29----
30
31- ``项目`` 特指一个目录,其中包含了构建可执行应用程序所需的全部文件和配置,以及其他支持型文件,例如分区表、数据/文件系统分区和引导程序。
32
33- ``项目配置`` 保存在项目根目录下名为 ``sdkconfig`` 的文件中,可以通过 ``idf.py menuconfig`` 进行修改,且一个项目只能包含一个项目配置。
34
35- ``应用程序`` 是由 ESP-IDF 构建得到的可执行文件。一个项目通常会构建两个应用程序:项目应用程序(可执行的主文件,即用户自定义的固件)和引导程序(启动并初始化项目应用程序)。
36
37- ``组件`` 是模块化且独立的代码,会被编译成静态库(.a 文件)并链接到应用程序。部分组件由 ESP-IDF 官方提供,其他组件则来源于其它开源项目。
38
39- ``目标`` 特指运行构建后应用程序的硬件设备。ESP-IDF 当前仅支持 ``esp32`` 和 ``esp32s2`` 以及 ``esp32c3`` 这三个硬件目标。
40
41请注意,以下内容并不属于项目的组成部分:
42
43- ``ESP-IDF`` 并不是项目的一部分,它独立于项目,通过 ``IDF_PATH`` 环境变量(保存 ``esp-idf`` 目录的路径)链接到项目,从而将 IDF 框架与项目分离。
44
45- 交叉编译工具链并不是项目的组成部分,它应该被安装在系统 PATH 环境变量中。
46
47使用构建系统
48============
49
50.. _idf.py:
51
52idf.py
53--------
54
55``idf.py`` 命令行工具提供了一个前端,可以帮助您轻松管理项目的构建过程,它管理了以下工具:
56
57- CMake_,配置待构建的项目
58- 命令行构建工具(Ninja_ 或 `GNU Make`)
59- `esptool.py`_,烧录目标硬件设备
60
61:ref:`入门指南 <get-started-configure>` 简要介绍了如何设置 ``idf.py`` 用于配置、构建并烧录项目。
62
63``idf.py`` 应运行在 ESP-IDF 的 ``项目`` 目录下,即包含 ``CMakeLists.txt`` 文件的目录。仅包含 Makefile 的老式项目并不支持 ``idf.py``。
64
65运行 ``idf.py --help`` 查看完整的命令列表。下面总结了最常用的命令:
66
67- ``idf.py set-target <target>`` 会设置构建项目的目标(芯片)。请参考 `选择目标芯片`_。
68- ``idf.py menuconfig`` 会运行 ``menuconfig`` 工具来配置项目。
69- ``idf.py build`` 会构建在当前目录下找到的项目,它包括以下步骤:
70
71  - 根据需要创建 ``build`` 构建目录,它用于保存构建过程的输出文件,可以使用 ``-B`` 选项修改默认的构建目录。
72  - 根据需要运行 CMake_ 来配置项目,为主构建工具生成构建文件。
73  - 运行主构建工具(Ninja_ 或 `GNU Make`)。默认情况下,构建工具会被自动检测,可以使用 ``-G`` 选项显式地指定构建工具。
74
75  构建过程是增量式的,如果自上次构建以来源文件或项目配置没有发生改变,则不会执行任何操作。
76
77- ``idf.py clean`` 会把构建输出的文件从构建目录中删除,从而清理整个项目。下次构建时会强制“重新完整构建”这个项目。清理时,不会删除 CMake 配置输出及其他文件。
78- ``idf.py fullclean`` 会将整个 ``build`` 目录下的内容全部删除,包括所有 CMake 的配置输出文件。下次构建项目时,CMake 会从头开始配置项目。请注意,该命令会递归删除构建目录下的 *所有文件*,请谨慎使用。项目配置文件不会被删除。
79- ``idf.py flash`` 会在必要时自动构建项目,并将生成的二进制程序烧录进目标 {IDF_TARGET_NAME} 设备中。``-p`` 和 ``-b`` 选项可分别设置串口的设备名和烧录时的波特率。
80- ``idf.py monitor`` 用于显示目标 {IDF_TARGET_NAME} 设备的串口输出。``-p`` 选项可用于设置主机端串口的设备名,按下 ``Ctrl-]`` 可退出监视器。更多有关监视器的详情,请参阅 :doc:`tools/idf-monitor`。
81
82多个 ``idf.py`` 命令可合并成一个,例如,``idf.py -p COM4 clean flash monitor`` 会依次清理源码树,构建项目,烧录进目标 {IDF_TARGET_NAME} 设备,最后运行串口监视器。
83
84对于 ``idf.py`` 不知道的指令,``idf.py`` 会尝试将其作为构建系统的目标来执行。
85
86``idf.py`` 命令支持 bash、 zsh 以及 fish shell 的 `自动补全 <https://click.palletsprojects.com/shell-completion/>`_。
87
88要实现实现 shell `自动补全 <https://click.palletsprojects.com/shell-completion/>`_,请先确保您安装了 Python 3.5 以及 `click <https://click.palletsprojects.com/>`_ 7.1 及以上版本(:ref:`请参考这里 <get-started-get-prerequisites>`)。
89
90使用 ``export`` 命令来启用 ``idf.py`` 的自动补全功能(:ref:`请参考这里 <get-started-export>`)。按 TAB 键可实现自动补全。输入 “idf.py -” 后按 TAB 键可自动补全选项。
91
92未来我们也会支持 PowerShell 的自动补全功能。
93
94.. 注解:: 环境变量 ``ESPPORT`` 和 ``ESPBAUD`` 可分别用来设置 ``-p`` 和 ``-b`` 选项的默认值。在命令行中,重新为这两个选项赋值,会覆盖其默认值。
95
96高级命令
97^^^^^^^^
98
99- ``idf.py app``,``idf.py bootloader``,``idf.py partition-table`` 仅可用于从适用的项目中构建应用程序、引导程序或分区表。
100- ``idf.py app-flash`` 等匹配命令,仅将项目的特定部分烧录至 {IDF_TARGET_NAME}。
101- ``idf.py -p PORT erase-flash`` 会使用 esptool.py 擦除 {IDF_TARGET_NAME} 的整个 Flash。
102- ``idf.py size`` 会打印应用程序相关的大小信息,``size-components`` 和 ``size-files`` 这两个命令相似,分别用于打印每个组件或源文件的详细信息。如果您在运行 CMake(或 ``idf.py``)时定义了变量 ``-DOUTPUT_JSON=1``,那么输出的格式会变成 JSON 而不是可读文本。详情请查看 ``idf.py-size``。
103- ``idf.py reconfigure`` 命令会重新运行 CMake_ (即便无需重新运行)。正常使用时,并不需要运行此命令,但当源码树中添加/删除文件后或更改 CMake cache 变量时,此命令会非常有用,例如,``idf.py -DNAME='VALUE' reconfigure`` 会将 CMake cache 中的变量 ``NAME`` 的值设置为 ``VALUE``。
104- ``idf.py python-clean`` 会从 IDF 目录中删除生成的 Python 字节码,Python 字节码可能会在切换 IDF 和 Python 版本时引发问题,因此建议在切换 Python 后运行该命令。
105- ``idf.py docs`` 将在浏览器中直接打开项目目标芯片和对应版本的文档链接。请使用 ``idf.py docs --help`` 查看所有选项。
106
107同时调用多个 ``idf.py`` 命令时,命令的输入顺序并不重要,它们会按照正确的顺序依次执行,并保证每一条命令都生效(即先构建后烧录,先擦除后烧录等)。
108
109idf.py 选项
110^^^^^^^^^^^^^^
111
112运行 ``idf.py --help`` 命令列出所有根级选项。运行 ``idf.py <command> --help`` 命令列出针对某一子命令的选项,如 ``idf.py monitor --help``。下列是一些常用选项:
113
114- ``-C <dir>`` 可用来从默认的当前工作目录覆盖项目目录。
115- ``-B <dir>`` 可用来从项目目录默认的 ``build`` 子目录覆盖构建目录。
116- ``--ccache`` 可用来在编译源文件时启用 CCache_,安装了 CCache_ 工具后可极大缩短编译时间。
117
118请注意,一些旧版本的 CCache 在某些平台上可能会出现 bug,因此如果文件没有按预期重新构建,请尝试禁用 CCache 后再次构建。通过设置环境变量 ``IDF_CCACHE_ENABLE`` 为非零值,可以默认启用 CCache。
119
120- ``-v`` 可以让 ``idf.py`` 和编译系统产生详细的编译输出,对于调试编译问题会非常有用。
121- ``--cmake-warn-uninitialized``(或 ``-w``)会让 CMake 在项目目录内打印未初始化的变量警告(不包括在项目目录外的目录),这一选项只能控制 CMake 内部的 CMake 变量警告,不包括其它类型的编译警告。可以通过设置环境变量 ``IDF_CMAKE_WARN_UNINITIALIZED`` 为非零值,从而永久设置该选项。
122
123开始新项目
124-----------------
125
126运行 ``idf.py create-project`` 命令可以开始创建您的新项目,运行 ``idf.py create-project --help`` 命令获取更多相关信息。
127
128例如:
129
130.. code-block:: bash
131
132    idf.py create-project --path my_projects my_new_project
133
134以上命令会直接在 my_projects 目录下创建一个名为 my_new_project 的新项目。
135
136直接使用 CMake
137--------------
138
139为了方便,:ref:`idf.py` 已经封装了 CMake_ 命令,但是您愿意,也可以直接调用 CMake。
140
141.. highlight:: bash
142
143当 ``idf.py`` 在执行某些操作时,它会打印出其运行的每条命令以便参考。例如运行 ``idf.py build`` 命令与在 bash shell(或者 Windows Command Prompt)中运行以下命令是相同的::
144
145    mkdir -p build
146    cd build
147    cmake .. -G Ninja   # 或者 'Unix Makefiles'
148    ninja
149
150在上面的命令列表中,``cmake`` 命令对项目进行配置,并生成用于最终构建工具的构建文件。在这个例子中,最终构建工具是 Ninja_: 运行 ``ninja`` 来构建项目。
151
152没有必要多次运行 ``cmake``。第一次构建后,往后每次只需运行 ``ninja`` 即可。如果项目需要重新配置,``ninja`` 会自动重新调用 ``cmake``。
153
154若在 CMake 中使用 ``ninja`` 或 ``make``,则多数 ``idf.py`` 子命令也会有其对应的目标,例如在构建目录下运行 ``make menuconfig`` 或 ``ninja menuconfig`` 与运行 ``idf.py menuconfig`` 是相同的。
155
156.. Note::
157    如果您已经熟悉了 CMake_,那么可能会发现 ESP-IDF 的 CMake 构建系统不同寻常,为了减少样板文件,该系统封装了 CMake 的许多功能。请参考 :ref:`write-pure-component` 以编写更多 “CMake 风格”的组件。
158
159.. _flash-with-ninja-or-make:
160
161使用 Ninja/Make 来烧录
162^^^^^^^^^^^^^^^^^^^^^^
163
164您可以直接使用 ninja 或 make 运行如下命令来构建项目并烧录::
165
166    ninja flash
167
168或::
169
170    make app-flash
171
172可用的目标还包括:``flash``、``app-flash`` (仅用于 app)、``bootloader-flash`` (仅用于 bootloader)。
173
174以这种方式烧录时,可以通过设置 ``ESPPORT`` 和 ``ESPBAUD`` 环境变量来指定串口设备和波特率。您可以在操作系统或 IDE 项目中设置该环境变量,或者直接在命令行中进行设置::
175
176    ESPPORT=/dev/ttyUSB0 ninja flash
177
178.. Note:: 在命令的开头为环境变量赋值属于 Bash shell 的语法,可在 Linux 、macOS 和 Windows 的类 Bash shell 中运行,但在 Windows Command Prompt 中无法运行。
179
180或::
181
182    make -j3 app-flash ESPPORT=COM4 ESPBAUD=2000000
183
184.. Note:: 在命令末尾为变量赋值属于 ``make`` 的语法,适用于所有平台的 ``make``。
185
186在 IDE 中使用 CMake
187-------------------
188
189您还可以使用集成了 CMake 的 IDE,仅需将项目 ``CMakeLists.txt`` 文件的路径告诉 IDE 即可。集成 CMake 的 IDE 通常会有自己的构建工具(CMake 称之为“生成器”),它是组成 IDE 的一部分,用来构建源文件。
190
191向 IDE 中添加除 ``build`` 目标以外的自定义目标(如添加 “Flash” 目标到 IDE)时,建议调用 ``idf.py`` 命令来执行这些“特殊”的操作。
192
193有关将 ESP-IDF 同 CMake 集成到 IDE 中的详细信息,请参阅 :ref:`build_system_metadata`。
194
195.. _setting-python-interpreter:
196
197设置 Python 解释器
198------------------
199
200ESP-IDF 适用于所有支持的 Python 版本。即使您系统中默认的 ``python`` 解释器仍是 Python 2.7,ESP-IDF 也可以使用,但建议您升级至 Python 3。
201
202``idf.py`` 和其他的 Python 脚本会使用默认的 Python 解释器运行,如 ``python``。您可以通过 ``python3 $IDF_PATH/tools/idf.py ...`` 命令切换到别的 Python 解释器,或者您可以通过设置 shell 别名或其他脚本来简化该命令。
203
204如果直接使用 CMake,运行 ``cmake -D PYTHON=python3 ...``,CMake 会使用传入的值覆盖默认的 Python 解释器。
205
206如果使用集成 CMake 的 IDE,可以在 IDE 的图形用户界面中给名为 ``PYTHON`` 的 CMake cache 变量设置新的值来覆盖默认的 Python 解释器。
207
208如果想在命令行中更优雅地管理 Python 的各个版本,请查看 pyenv_ 或 virtualenv_ 工具,它们会帮助您更改默认的 python 版本。
209
210潜在问题
211^^^^^^^^^^^^^^^
212
213使用 ``idf.py`` 可能会出现如下 ``ImportError`` 错误:
214
215.. code-block:: none
216
217    Traceback (most recent call last):
218      File "/Users/user_name/e/esp-idf/tools/kconfig_new/confgen.py"、 line 27、 in <module>
219        import kconfiglib
220    ImportError: bad magic number in 'kconfiglib': b'\x03\xf3\r\n'
221
222该错误通常是由不同 Python 版本生成的 ``.pyc`` 文件引起的,可以通过运行以下命令解决该问题:
223
224.. code-block:: bash
225
226    idf.py python-clean
227
228.. _example-project-structure:
229
230示例项目
231========
232
233.. highlight:: none
234
235示例项目的目录树结构可能如下所示::
236
237    - myProject/
238                 - CMakeLists.txt
239                 - sdkconfig
240                 - components/ - component1/ - CMakeLists.txt
241                                             - Kconfig
242                                             - src1.c
243                               - component2/ - CMakeLists.txt
244                                             - Kconfig
245                                             - src1.c
246                                             - include/ - component2.h
247                 - main/       - CMakeLists.txt
248                               - src1.c
249                               - src2.c
250
251                 - build/
252
253该示例项目 "myProject" 包含以下组成部分:
254
255- 顶层项目 CMakeLists.txt 文件,这是 CMake 用于学习如何构建项目的主要文件,可以在这个文件中设置项目全局的 CMake 变量。顶层项目 CMakeLists.txt 文件会导入 :idf_file:`/tools/cmake/project.cmake` 文件,由它负责实现构建系统的其余部分。该文件最后会设置项目的名称,并定义该项目。
256
257- "sdkconfig" 项目配置文件,执行 ``idf.py menuconfig`` 时会创建或更新此文件,文件中保存了项目中所有组件(包括 ESP-IDF 本身)的配置信息。 ``sdkconfig`` 文件可能会也可能不会被添加到项目的源码管理系统中。
258
259- 可选的 "components" 目录中包含了项目的部分自定义组件,并不是每个项目都需要这种自定义组件,但它有助于构建可复用的代码或者导入第三方(不属于 ESP-IDF)的组件。或者,您也可以在顶层 CMakeLists.txt 中设置 ``EXTRA_COMPONENT_DIRS`` 变量以查找其他指定位置处的组件。有关详细信息,请参阅 :ref:`重命名 main 组件 <rename-main>`。如果项目中源文件较多,建议将其归于组件中,而不是全部放在 "main" 中。
260
261- "main" 目录是一个特殊的组件,它包含项目本身的源代码。"main" 是默认名称,CMake 变量 ``COMPONENT_DIRS`` 默认包含此组件,但您可以修改此变量。
262
263- "build" 目录是存放构建输出的地方,如果没有此目录,``idf.py`` 会自动创建。CMake 会配置项目,并在此目录下生成临时的构建文件。随后,在主构建进程的运行期间,该目录还会保存临时目标文件、库文件以及最终输出的二进制文件。此目录通常不会添加到项目的源码管理系统中,也不会随项目源码一同发布。
264
265每个组件目录都包含一个 ``CMakeLists.txt`` 文件,里面会定义一些变量以控制该组件的构建过程,以及其与整个项目的集成。更多详细信息请参阅 :ref:`component-directories`。
266
267每个组件还可以包含一个 ``Kconfig`` 文件,它用于定义 ``menuconfig`` 时展示的 :ref:`component-configuration` 选项。某些组件可能还会包含 ``Kconfig.projbuild`` 和 ``project_include.cmake`` 特殊文件,它们用于 :ref:`override_project_config`。
268
269项目 CMakeLists 文件
270====================
271
272每个项目都有一个顶层 ``CMakeLists.txt`` 文件,包含整个项目的构建设置。默认情况下,项目 CMakeLists 文件会非常小。
273
274最小 CMakeLists 文件示例
275------------------------
276
277.. highlight:: cmake
278
279最小项目::
280
281        cmake_minimum_required(VERSION 3.5)
282        include($ENV{IDF_PATH}/tools/cmake/project.cmake)
283        project(myProject)
284
285.. _project-mandatory-parts:
286
287必要部分
288--------
289
290每个项目都要按照上面显示的顺序添加上述三行代码:
291
292- ``cmake_minimum_required(VERSION 3.5)`` 必须放在 CMakeLists.txt 文件的第一行,它会告诉 CMake 构建该项目所需要的最小版本号。ESP-IDF 支持 CMake 3.5 或更高的版本。
293- ``include($ENV{IDF_PATH}/tools/cmake/project.cmake)`` 会导入 CMake 的其余功能来完成配置项目、检索组件等任务。
294- ``project(myProject)`` 会创建项目本身,并指定项目名称。该名称会作为最终输出的二进制文件的名字,即 ``myProject.elf`` 和 ``myProject.bin``。每个 CMakeLists 文件只能定义一个项目。
295
296.. _optional_project_variable:
297
298可选的项目变量
299--------------
300
301以下这些变量都有默认值,用户可以覆盖这些变量值以自定义构建行为。更多实现细节,请参阅 :idf_file:`/tools/cmake/project.cmake` 文件。
302
303- ``COMPONENT_DIRS``:组件的搜索目录,默认为 ``IDF_PATH/components``、 ``PROJECT_DIR/components``、和 ``EXTRA_COMPONENT_DIRS``。如果您不想在这些位置搜索组件,请覆盖此变量。
304
305- ``EXTRA_COMPONENT_DIRS``:用于搜索组件的其它可选目录列表。路径可以是相对于项目目录的相对路径,也可以是绝对路径。
306
307- ``COMPONENTS``:要构建进项目中的组件名称列表,默认为 ``COMPONENT_DIRS`` 目录下检索到的所有组件。使用此变量可以“精简”项目以缩短构建时间。请注意,如果一个组件通过 ``COMPONENT_REQUIRES`` 指定了它依赖的另一个组件,则会自动将其添加到 ``COMPONENTS`` 中,所以 ``COMPONENTS`` 列表可能会非常短。
308
309以上变量中的路径可以是绝对路径,或者是相对于项目目录的相对路径。
310
311请使用 `cmake 中的 set 命令 <cmake set_>`_ 来设置这些变量,如 ``set(VARIABLE "VALUE")``。请注意,``set()`` 命令需放在 ``include(...)`` 之前,``cmake_minimum(...)`` 之后。
312
313.. _rename-main:
314
315重命名 ``main`` 组件
316--------------------
317
318构建系统会对 ``main`` 组件进行特殊处理。假如 ``main`` 组件位于预期的位置(即 `${PROJECT_PATH}/main`),那么它会被自动添加到构建系统中。其他组件也会作为其依赖项被添加到构建系统中,这使用户免于处理依赖关系,并提供即时可用的构建功能。重命名 ``main`` 组件会减轻上述这些幕后工作量,但要求用户指定重命名后的组件位置,并手动为其添加依赖项。重命名 ``main`` 组件的步骤如下:
319
3201. 重命名 ``main`` 目录。
3212. 在项目 CMakeLists.txt 文件中设置 ``EXTRA_COMPONENT_DIRS``,并添加重命名后的 ``main`` 目录。
3223. 在组件的 CMakeLists.txt 文件中设置 ``COMPONENT_REQUIRES`` 或 ``COMPONENT_PRIV_REQUIRES`` 以指定依赖项。
323
324
325覆盖默认的构建规范
326---------------------------------------
327
328构建系统设置了一些全局的构建规范(编译标志、定义等),这些规范可用于编译来自所有组件的所有源文件。
329
330.. highlight:: cmake
331
332例如,其中一个默认的构建规范是编译选项 ``Wextra``。假设一个用户想用 ``Wno-extra`` 来覆盖这个选项,
333应在 ``project()`` 之后进行::
334
335
336    cmake_minimum_required(VERSION 3.5)
337    include($ENV{IDF_PATH}/tools/cmake/project.cmake)
338    project(myProject)
339
340    idf_build_set_property(COMPILE_OPTIONS "-Wno-error" APPEND)
341
342这确保了用户设置的编译选项不会被默认的构建规范所覆盖,因为默认的构建规范是在 ``project()`` 内设置的。
343
344.. _component-directories:
345
346组件 CMakeLists 文件
347====================
348
349每个项目都包含一个或多个组件,这些组件可以是 ESP-IDF 的一部分,可以是项目自身组件目录的一部分,也可以从自定义组件目录添加(:ref:`见上文 <component-directories>`)。
350
351组件是 ``COMPONENT_DIRS`` 列表中包含 ``CMakeLists.txt`` 文件的任何目录。
352
353搜索组件
354--------
355
356搜索 ``COMPONENT_DIRS`` 中的目录列表以查找项目的组件,此列表中的目录可以是组件自身(即包含 `CMakeLists.txt` 文件的目录),也可以是子目录为组件的顶级目录。
357
358当 CMake 运行项目配置时,它会记录本次构建包含的组件列表,它可用于调试某些组件的添加/排除。
359
360.. _cmake-components-same-name:
361
362同名组件
363--------
364
365ESP-IDF 在搜索所有待构建的组件时,会按照 ``COMPONENT_DIRS`` 指定的顺序依次进行,这意味着在默认情况下,首先搜索 ESP-IDF 内部组件(``IDF_PATH/components``),然后是 ``EXTRA_COMPONENT_DIRS`` 中的组件,最后是项目组件(``PROJECT_DIR/components``)。如果这些目录中的两个或者多个包含具有相同名字的组件,则使用搜索到的最后一个位置的组件。这就允许将组件复制到项目目录中再修改以覆盖 ESP-IDF 组件,如果使用这种方式,ESP-IDF 目录本身可以保持不变。
366
367.. 注解:: 如果在现有项目中通过将组件移动到一个新位置来覆盖它,项目不会自动看到新组件的路径。请运行 ``idf.py reconfigure`` 命令后(或删除项目构建文件夹)再重新构建。
368
369.. _minimum_cmakelists:
370
371最小组件 CMakeLists 文件
372--------------------------
373
374.. highlight:: cmake
375
376最小组件 ``CMakeLists.txt`` 文件通过使用 ``idf_component_register`` 将组件添加到构建系统中。
377
378  idf_component_register(SRCS "foo.c" "bar.c"
379                         INCLUDE_DIRS "include"
380                         REQUIRES mbedtls)
381
382- ``SRCS`` 是源文件列表(``*.c``、``*.cpp``、``*.cc``、``*.S``),里面所有的源文件都将会编译进组件库中。
383- ``INCLUDE_DIRS`` 是目录列表,里面的路径会被添加到所有需要该组件的组件(包括 main 组件)全局 include 搜索路径中。
384- ``REQUIRES`` 实际上并不是必需的,但通常需要它来声明该组件需要使用哪些其它组件,请参考 :ref:`component requirements`。
385
386上述命令会构建生成与组件同名的库,并最终被链接到应用程序中。
387
388上述目录通常设置为相对于 ``CMakeLists.txt`` 文件的相对路径,当然也可以设置为绝对路径。
389
390还有其它参数可以传递给 ``idf_component_register``,具体可参考 :ref:`here<cmake-component-register>`。
391
392有关更完整的 ``CMakeLists.txt`` 示例,请参阅 `组件依赖示例`_ 和 `组件 CMakeLists 示例`_。
393
394创建新组件
395-------------
396
397使用 ``idf.py create-component`` 命令创建新组件。 新组件将包含构建组件所需的一组文件。您可以将组件的头文件纳入到您的项目中,并使用其功能。请运行 ``idf.py create-component --help`` 命令获取更多信息。
398
399示例:
400
401.. code-block:: bash
402
403    idf.py -C components create-component my_component
404
405该示例将在当前工作目录下的子目录 components 中创建一个新的组件。更多关于组件的信息,请参考 :ref:`上文<component-directories>`。
406
407.. _preset_component_variables:
408
409预设的组件变量
410--------------
411
412以下专用于组件的变量可以在组件 CMakeLists 中使用,但不建议修改:
413
414- ``COMPONENT_DIR``:组件目录,即包含 ``CMakeLists.txt`` 文件的绝对路径,它与 ``CMAKE_CURRENT_SOURCE_DIR`` 变量一样,路径中不能包含空格。
415- ``COMPONENT_NAME``:组件名,与组件目录名相同。
416- ``COMPONENT_ALIAS``:库别名,由构建系统在内部为组件创建。
417- ``COMPONENT_LIB``:库名,由构建系统在内部为组件创建。
418
419以下变量在项目级别中被设置,但可在组件 CMakeLists 中使用:
420
421- ``CONFIG_*``:项目配置中的每个值在 cmake 中都对应一个以 ``CONFIG_`` 开头的变量。更多详细信息请参阅 :doc:`Kconfig </api-reference/kconfig>`。
422- ``ESP_PLATFORM``:ESP-IDF 构建系统处理 CMake 文件时,其值设为1。
423
424构建/项目变量
425-----------------
426
427以下是可作为构建属性的构建/项目变量,可通过组件 CMakeLists.txt 中的 ``idf_build_get_property`` 查询其变量值。
428
429
430- ``PROJECT_NAME``:项目名,在项目 CMakeLists.txt 文件中设置。
431- ``PROJECT_DIR``:项目目录(包含项目 CMakeLists 文件)的绝对路径,与 ``CMAKE_SOURCE_DIR`` 变量相同。
432- ``COMPONENTS``:此次构建中包含的所有组件的名称,具体格式为用分号隔开的 CMake 列表。
433- ``IDF_VER``:ESP-IDF 的 git 版本号,由 ``git describe`` 命令生成。
434- ``IDF_VERSION_MAJOR``、 ``IDF_VERSION_MINOR``、 ``IDF_VERSION_PATCH``: ESP-IDF 的组件版本,可用于条件表达式。请注意这些信息的精确度不如 ``IDF_VER`` 变量,版本号 ``v4.0-dev-*``, ``v4.0-beta1``, ``v4.0-rc1`` 和 ``v4.0`` 对应的 ``IDF_VERSION_*`` 变量值是相同的,但是 ``IDF_VER`` 的值是不同的。
435- ``IDF_TARGET``:项目的硬件目标名称。
436- ``PROJECT_VER``:项目版本号。
437
438  * 如果设置 :ref:`CONFIG_APP_PROJECT_VER_FROM_CONFIG` 选项,将会使用 :ref:`CONFIG_APP_PROJECT_VER` 的值。
439  * 或者,如果在项目 CMakeLists.txt 文件中设置了 ``PROJECT_VER`` 变量,则该变量值可以使用。
440  * 或者,如果 ``PROJECT_DIR/version.txt`` 文件存在,其内容会用作 ``PROJECT_VER`` 的值。
441  * 或者,如果项目位于某个 Git 仓库中,则使用 ``git describe`` 命令的输出作为 ``PROJECT_VER`` 的值。
442  * 否则,``PROJECT_VER`` 的值为 1。
443
444其它与构建属性有关的信息请参考 :ref:`这里<cmake-build-properties>`。
445
446.. _component_build_control:
447
448组件编译控制
449------------------
450
451.. highlight:: cmake
452
453在编译特定组件的源文件时,可以使用 `target_compile_options`_  函数来传递编译器选项::
454
455  target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-unused-variable)
456
457如果给单个源文件指定编译器标志,可以使用 CMake 的 `set_source_files_properties`_ 命令::
458
459    set_source_files_properties(mysrc.c
460        PROPERTIES COMPILE_FLAGS
461        -Wno-unused-variable
462    )
463
464如果上游代码在编译的时候发出了警告,那这么做可能会很有效。
465
466请注意,上述两条命令只能在组件 CMakeLists 文件的 ``idf_component_register`` 命令之后调用。
467
468.. _component-configuration:
469
470组件配置
471========
472
473每个组件都可以包含一个 ``Kconfig`` 文件,和 ``CMakeLists.txt`` 放在同一目录下。``Kconfig`` 文件中包含要添加到该组件配置菜单中的一些配置设置信息。
474
475运行 menuconfig 时,可以在 ``Component Settings`` 菜单栏下找到这些设置。
476
477创建一个组件的 Kconfig 文件,最简单的方法就是使用 ESP-IDF 中现有的 Kconfig 文件作为模板,在这基础上进行修改。
478
479有关示例请参阅 :ref:`add_conditional_config`。
480
481预处理器定义
482============
483
484ESP-IDF 构建系统会在命令行中添加以下 C 预处理器定义:
485
486- ``ESP_PLATFORM``:可以用来检测在 ESP-IDF 内发生了构建行为。
487- ``IDF_VER``:定义 git 版本字符串,例如:``v2.0`` 用于标记已发布的版本,``v1.0-275-g0efaa4f`` 则用于标记任意某次的提交记录。
488
489.. _component requirements:
490
491组件依赖
492========
493
494编译各个组件时,ESP-IDF 系统会递归评估其依赖项。这意味着每个组件都需要声明它所依赖的组件,即 “requires”。
495
496编写组件
497--------
498
499.. code-block:: cmake
500
501   idf_component_register(...
502                          REQUIRES mbedtls
503                          PRIV_REQUIRES console spiffs)
504
505- ``REQUIRES`` 需要包含所有在当前组件的 *公共* 头文件里 `#include` 的头文件所在的组件。
506
507- ``PRIV_REQUIRES`` 需要包含被当前组件的源文件 `#include` 的头文件所在的组件(除非已经被设置在了 ``REQUIRES`` 中)。以及是当前组件正常工作必须要链接的组件。
508
509- ``REQUIRES`` 和 ``PRIV_REQUIRES`` 的值不能依赖于任何配置选项 (``CONFIG_xxx`` 宏)。这是因为在配置加载之前,依赖关系就已经被展开。其它组件变量(比如包含路径或源文件)可以依赖配置选择。
510
511- 如果当前组件除了 `通用组件依赖项`_ 中设置的通用组件(比如 RTOS、libc 等)外,并不依赖其它组件,那么对于上述两个 ``REQUIRES`` 变量,可以选择其中一个或是两个都不设置。
512
513如果组件仅支持某些硬件目标(``IDF_TARGET`` 的值),则可以在 ``idf_component_register`` 中指定 ``REQUIRED_IDF_TARGETS`` 来声明这个需求。在这种情况下,如果构建系统导入了不支持当前硬件目标的组件时就会报错。
514
515.. 注解:: 在 CMake 中,``REQUIRES`` 和 ``PRIV_REQUIRES`` 是 CMake 函数 ``target_link_libraries(... PUBLIC ...)`` 和 ``target_link_libraries(... PRIVATE ...)`` 的近似包装。
516
517.. _example component requirements:
518
519组件依赖示例
520--------------------
521
522假设现在有一个 ``car`` 组件,它需要使用 ``engine`` 组件,而 ``engine`` 组件需要使用 ``spark_plug`` 组件:
523
524.. code-block:: none
525
526    - autoProject/
527                 - CMakeLists.txt
528                 - components/ - car/ - CMakeLists.txt
529                                         - car.c
530                                         - car.h
531                               - engine/ - CMakeLists.txt
532                                         - engine.c
533                                         - include/ - engine.h
534                               - spark_plug/  - CMakeLists.txt
535                                              - plug.c
536                                              - plug.h
537
538Car 组件
539^^^^^^^^^
540
541.. highlight:: c
542
543``car.h`` 头文件是 ``car`` 组件的公共接口。该头文件直接包含了 ``engine.h``,这是因为它需要使用 ``engine.h`` 中的一些声明::
544
545  /* car.h */
546  #include "engine.h"
547
548  #ifdef ENGINE_IS_HYBRID
549  #define CAR_MODEL "Hybrid"
550  #endif
551
552同时 car.c 也包含了 ``car.h``::
553
554  /* car.c */
555  #include "car.h"
556
557这代表文件 ``car/CMakeLists.txt`` 需要声明 ``car`` 需要 ``engine``:
558
559.. code-block:: cmake
560
561  idf_component_register(SRCS "car.c"
562                    INCLUDE_DIRS "."
563                    REQUIRES engine)
564
565- ``SRCS`` 提供 ``car`` 组件中源文件列表。
566- ``INCLUDE_DIRS`` 提供该组件公共头文件目录列表,由于 ``car.h`` 是公共接口,所以这里列出了所有包含了 ``car.h`` 的目录。
567- ``REQUIRES`` 给出该组件的公共接口所需的组件列表。由于 ``car.h`` 是一个公共头文件并且包含了来自 ``engine`` 的头文件,所以我们这里包含 ``engine``。这样可以确保任何包含 ``car.h`` 的其他组件也能递归地包含所需的 ``engine.h``。
568
569Engine 组件
570^^^^^^^^^^^^^^^^
571
572.. highlight:: c
573
574``engine`` 组件也有一个公共头文件 ``include/engine.h``,但这个头文件更为简单::
575
576  /* engine.h */
577  #define ENGINE_IS_HYBRID
578
579  void engine_start(void);
580
581在 ``engine.c`` 中执行::
582
583  /* engine.c */
584  #include "engine.h"
585  #include "spark_plug.h"
586
587  ...
588
589在该组件中,``engine`` 依赖于 ``spark_plug``,但这是私有依赖关系。编译 ``engine.c`` 需要 ``spark_plug.h`` 但不需要包含 ``engine.h``。
590
591这代表文件 ``engine/CMakeLists.txt`` 可以使用 ``PRIV_REQUIRES``:
592
593.. code-block:: cmake
594
595  idf_component_register(SRCS "engine.c"
596                    INCLUDE_DIRS "include"
597                    PRIV_REQUIRES spark_plug)
598
599因此,``car`` 组件中的源文件不需要在编译器搜索路径中添加 ``spark_plug`` include 目录。这可以加快编译速度,避免编译器命令行过于的冗长。
600
601Spark Plug 组件
602^^^^^^^^^^^^^^^^^^^^
603
604``spark_plug`` 组件没有依赖项,它有一个公共头文件 ``spark_plug.h``,但不包含其他组件的头文件。
605
606这代表 ``spark_plug/CMakeLists.txt`` 文件不需要任何 ``REQUIRES`` 或 ``PRIV_REQUIRES``:
607
608.. code-block:: cmake
609
610  idf_component_register(SRCS "spark_plug.c"
611                    INCLUDE_DIRS ".")
612
613
614源文件 Include 目录
615---------------------
616
617每个组件的源文件都是用这些 Include 路径目录编译的,这些路径在传递给 ``idf_component_register`` 的参数中指定:
618
619.. code-block:: cmake
620
621  idf_component_register(..
622                         INCLUDE_DIRS "include"
623                         PRIV_INCLUDE_DIRS "other")
624
625
626- 当前组件的 ``INCLUDE_DIRS`` 和 ``PRIV_INCLUDE_DIRS``。
627- ``REQUIRES`` 和 ``PRIV_REQUIRES`` 参数指定的所有其他组件(即当前组件的所有公共和私有依赖项)所设置的 ``INCLUDE_DIRS``。
628- 递归列出所有组件 ``REQUIRES`` 列表中 ``INCLUDE_DIRS`` 目录(如递归展开这个组件的所有公共依赖项)。
629
630主要组件依赖项
631-----------------------
632
633``main`` 组件比较特别,因为它在构建过程中自动依赖所有其他组件。所以不需要向这个组件传递 ``REQUIRES`` 或 ``PRIV_REQUIRES``。有关不再使用 ``main`` 组件时需要更改哪些内容,请参考 :ref:`重命名 main 组件<rename-main>`。
634
635通用组件依赖项
636--------------
637
638为避免重复性工作,各组件都用自动依赖一些“通用” IDF 组件,即使它们没有被明确提及。这些组件的头文件会一直包含在构建系统中。
639
640通用组件包括:cxx、newlib、freertos、esp_hw_support、heap、log、lwip、soc、hal、esp_rom、esp_common、esp_system。
641
642在构建中导入组件
643-----------------
644
645- 默认情况下,每个组件都会包含在构建系统中。
646- 如果将 ``COMPONENTS`` 变量设置为项目直接使用的最小组件列表,那么构建系统会扩展到包含所有组件。完整的组件列表为:
647
648  * ``COMPONENTS`` 中明确提及的组件。
649  * 这些组件的依赖项(以及递归运算后的组件)。
650  * 每个组件都依赖的通用组件。
651
652- 将 ``COMPONENTS`` 设置为所需组件的最小列表,可以显著减少项目的构建时间。
653
654.. _component-circular-dependencies:
655
656循环依赖
657---------------------
658
659一个项目中可能包含组件 A 和组件 B,而组件 A 依赖(``REQUIRES`` 或 ``PRIV_REQUIRES``)组件 B,组件 B 又依赖组件 A。这就是所谓的依赖循环或循环依赖。
660
661CMake 通常会在链接器命令行上重复两次组件库名称来自动处理循环依赖。然而这种方法并不总是有效,还是可能构建失败并出现关于 “Undefined reference to ...” 的链接器错误,这通常是由于引用了循环依赖中某一组件中定义的符号。如果存在较大的循环依赖关系,即 A->B->C->D->A,这种情况极有可能发生。
662
663最好的解决办法是重构组件以消除循环依赖关系。在大多数情况下,没有循环依赖的软件架构具有模块化和分层清晰的特性,并且从长远来看更容易维护。然而,移除循环依赖关系并不容易做到。
664
665要绕过由循环依赖引起的链接器错误,最简单的解决方法是增加其中一个组件库的 CMake `LINK_INTERFACE_MULTIPLICITY`_ 属性。 这会让 CMake 在链接器命令行上对此库及其依赖项重复两次以上。
666
667例如:
668
669.. code-block:: cmake
670
671    set_property(TARGET ${COMPONENT_LIB} APPEND PROPERTY LINK_INTERFACE_MULTIPLICITY 3)
672
673- 这一行应该放在组件 CMakeLists.txt 文件 ``idf_component_register`` 之后。
674- 可以的话,将此行放置在因依赖其他组件而造成循环依赖的组件中。实际上,该行可以放在循环内的任何一个组件中,但建议将其放置在拥有链接器错误提示信息中显示的源文件的组件中,或是放置在定义了链接器错误提示信息中所提到的符号的组件,先从这些组件开始是个不错的选择。
675- 通常将值增加到 3(默认值是 2)就足够了,但如果不起作用,可以尝试逐步增加这个数字。
676- 注意,增加这个选项会使链接器的命令行变长,链接阶段变慢。
677
678高级解决方法:未定义符号
679^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
680
681如果只有一两个符号导致循环依赖,而所有其他依赖都是线性的,那么有一种替代方法可以避免链接器错误:在链接时将“反向”依赖所需的特定符号指定为未定义符号。
682
683例如,如果组件 A 依赖于组件 B,但组件 B 也需要引用组件 A 的 ``reverse_ops`` (但不依赖组件 A 中的其他内容),那么你可以在组件 B 的 CMakeLists.txt 中添加如下一行,以在链接时避免这出现循环。
684
685.. code-block:: cmake
686
687    # 该符号是由“组件 A”在链接时提供
688    target_link_libraries(${COMPONENT_LIB} INTERFACE "-u reverse_ops")
689
690- ``-u`` 参数意味着链接器将始终在链接中包含此符号,而不管依赖项顺序如何。
691- 该行应该放在组件 CMakeLists.txt 文件中的 ``idf_component_register`` 之后。
692- 如果“组件 B”不需要访问“组件 A”的任何头文件,只需链接几个符号,那么这一行可以用来代替 B 对 A 的任何 “REQUIRES”。这样则进一步简化了构建系统中的组件结构。
693
694请参考 `target_link_libraries`_ 文档以了解更多关于此 CMake 函数的信息。
695
696.. _component-requirements-implementation:
697
698构建系统中依赖处理的实现细节
699----------------------------
700
701- 在 CMake 配置进程的早期阶段会运行 ``expand_requirements.cmake`` 脚本。该脚本会对所有组件的 CMakeLists.txt 文件进行局部的运算,得到一张组件依赖关系图(:ref:`此图可能会有闭环 <component-circular-dependencies>`)。此图用于在构建目录中生成 ``component_depends.cmake`` 文件。
702- CMake 主进程会导入该文件,并以此来确定要包含到构建系统中的组件列表(内部使用的 ``BUILD_COMPONENTS`` 变量)。``BUILD_COMPONENTS`` 变量已排好序,依赖组件会排在前面。由于组件依赖关系图中可能存在闭环,因此不能保证每个组件都满足该排序规则。如果给定相同的组件集和依赖关系,那么最终的排序结果应该是确定的。
703- CMake 会将 ``BUILD_COMPONENTS`` 的值以 “Component names:” 的形式打印出来。
704- 然后执行构建系统中包含的每个组件的配置。
705- 每个组件都被正常包含在构建系统中,然后再次执行 CMakeLists.txt 文件,将组件库加入构建系统。
706
707组件依赖顺序
708^^^^^^^^^^^^
709
710``BUILD_COMPONENTS`` 变量中组件的顺序决定了构建过程中的其它顺序,包括:
711
712- 项目导入 :ref:`project_include.cmake` 文件的顺序。
713- 生成用于编译(通过 ``-I`` 参数)的头文件路径列表的顺序。请注意,对于给定组件的源文件,仅需将该组件的依赖组件的头文件路径告知编译器。
714
715.. _override_project_config:
716
717覆盖项目的部分设置
718------------------
719
720.. _project_include.cmake:
721
722project_include.cmake
723^^^^^^^^^^^^^^^^^^^^^
724
725如果组件的某些构建行为需要在组件 CMakeLists 文件之前被执行,您可以在组件目录下创建名为 ``project_include.cmake`` 的文件,``project.cmake`` 在运行过程中会导入此 CMake 文件。
726
727``project_include.cmake`` 文件在 ESP-IDF 内部使用,以定义项目范围内的构建功能,比如 ``esptool.py`` 的命令行参数和 ``bootloader`` 这个特殊的应用程序。
728
729与组件 ``CMakeLists.txt`` 文件有所不同,在导入``project_include.cmake`` 文件的时候,当前源文件目录(即 ``CMAKE_CURRENT_SOURCE_DIR``和工作目录)为项目目录。如果想获得当前组件的绝对路径,可以使用 ``COMPONENT_PATH`` 变量。
730
731请注意,``project_include.cmake`` 对于大多数常见的组件并不是必需的。例如给项目添加 include 搜索目录,给最终的链接步骤添加 ``LDFLAGS`` 选项等等都可以通过 ``CMakeLists.txt`` 文件来自定义。详细信息请参考 :ref:`optional_project_variable`。
732
733``project_include.cmake`` 文件会按照 ``BUILD_COMPONENTS`` 变量中组件的顺序(由 CMake 记录)依次导入。即只有在当前组件所有依赖组件的 ``project_include.cmake`` 文件都被导入后,当前组件的 ``project_include.cmake`` 文件才会被导入,除非两个组件在同一个依赖闭环中。如果某个 ``project_include.cmake`` 文件依赖于另一组件设置的变量,则要特别注意上述情况。更多详情请参阅 :ref:`component-requirements-implementation`。
734
735在 ``project_include.cmake`` 文件中设置变量或目标时要格外小心,这些值被包含在项目的顶层 CMake 文件中,因此他们会影响或破坏所有组件的功能。
736
737KConfig.projbuild
738^^^^^^^^^^^^^^^^^
739
740与 ``project_include.cmake`` 类似,也可以为组件定义一个 KConfig 文件以实现全局的 :ref:`component-configuration`。如果要在 menuconfig 的顶层添加配置选项,而不是在 “Component Configuration” 子菜单中,则可以在 ``CMakeLists.txt`` 文件所在目录的 KConfig.projbuild 文件中定义这些选项。
741
742在此文件中添加配置时要小心,因为这些配置会包含在整个项目配置中。在可能的情况下,请为 :ref:`component-configuration` 创建 KConfig 文件。
743
744``project_include.cmake`` 文件在 ESP-IDF 内部使用,以定义项目范围内的构建功能,比如 ``esptool.py`` 的命令行参数和 ``bootloader`` 这个特殊的应用程序。
745
746.. _config_only_component:
747
748仅配置组件
749^^^^^^^^^^
750
751仅配置组件是一类不包含源文件的特殊组件,仅包含 ``Kconfig.projbuild``、``KConfig`` 和 ``CMakeLists.txt`` 文件,该 ``CMakeLists.txt`` 文件仅有一行代码,调用了 ``idf_component_register()`` 函数。此函数会将组件导入到项目构建中,但不会构建任何库,也不会将头文件添加到任何 include 搜索路径中。
752
753CMake 调试
754----------
755
756请查看 `CMake v3.5 官方文档`_ 获取更多关于 CMake_ 和 CMake 命令的信息。
757
758调试 ESP-IDF CMake 构建系统的一些技巧:
759
760- CMake 运行时,会打印大量诊断信息,包括组件列表和组件路径。
761- 运行 ``cmake -DDEBUG=1``,IDF 构建系统会生成更详细的诊断输出。
762- 运行 ``cmake`` 时指定 ``--trace`` 或 ``--trace-expand`` 选项会提供大量有关控制流信息。详情请参考 `CMake 命令行文档`_。
763
764当从项目 CMakeLists 文件导入时,``project.cmake`` 文件会定义工具模块和全局变量,并在系统环境中没有设置 ``IDF_PATH`` 时设置 ``IDF_PATH``。
765
766同时还定义了一个自定义版本的内置 CMake_ ``project`` 函数, 这个函数被覆盖,以添加所有 ESP-IDF 特定的项目功能。
767
768.. _warn-undefined-variables:
769
770警告未定义的变量
771^^^^^^^^^^^^^^^^
772
773默认情况下,``idf.py`` 在调用 CMake_ 时会给它传递 ``--warn-uninitialized`` 标志,如果在构建的过程中引用了未定义的变量,CMake_ 会打印警告。这对查找有错误的 CMake 文件非常有用。
774
775如果您不想启用此功能,可以给 ``idf.py`` 传递 ``--no-warnings`` 标志。
776
777更多信息,请参考文件 :idf_file:`/tools/cmake/project.cmake` 以及 :idf:`/tools/cmake/` 中支持的函数。
778
779.. _component_cmakelists_example:
780
781组件 CMakeLists 示例
782====================
783
784因为构建环境试图设置大多数情况都能工作的合理默认值,所以组件 ``CMakeLists.txt`` 文件可能非常小,甚至是空的,请参考 :ref:`minimum_cmakelists`。但有些功能往往需要覆盖 :ref:`preset_component_variables` 才能实现。
785
786以下是组件 CMakeLists 文件的更高级的示例。
787
788.. _add_conditional_config:
789
790添加条件配置
791------------
792
793配置系统可用于根据项目配置中选择的选项有条件地编译某些文件。
794
795.. highlight:: none
796
797``Kconfig``::
798
799    config FOO_ENABLE_BAR
800        bool "Enable the BAR feature."
801        help
802            This enables the BAR feature of the FOO component.
803
804``CMakeLists.txt``::
805
806    set(srcs "foo.c" "more_foo.c")
807
808    if(CONFIG_FOO_ENABLE_BAR)
809        list(APPEND srcs "bar.c")
810    endif()
811
812   idf_component_register(SRCS "${srcs}"
813                        ...)
814
815上述示例使用了 CMake 的 `if <cmake if_>`_ 函数和 `list APPEND <cmake list_>`_ 函数。
816
817也可用于选择或删除某一实现,如下所示:
818
819``Kconfig``::
820
821    config ENABLE_LCD_OUTPUT
822        bool "Enable LCD output."
823        help
824            Select this if your board has a LCD.
825
826    config ENABLE_LCD_CONSOLE
827        bool "Output console text to LCD"
828        depends on ENABLE_LCD_OUTPUT
829        help
830            Select this to output debugging output to the lcd
831
832    config ENABLE_LCD_PLOT
833        bool "Output temperature plots to LCD"
834        depends on ENABLE_LCD_OUTPUT
835        help
836            Select this to output temperature plots
837
838.. highlight:: cmake
839
840``CMakeLists.txt``::
841
842    if(CONFIG_ENABLE_LCD_OUTPUT)
843       set(srcs lcd-real.c lcd-spi.c)
844    else()
845       set(srcs lcd-dummy.c)
846    endif()
847
848    # 如果启用了控制台或绘图功能,则需要加入字体
849    if(CONFIG_ENABLE_LCD_CONSOLE OR CONFIG_ENABLE_LCD_PLOT)
850       list(APPEND srcs "font.c")
851    endif()
852
853    idf_component_register(SRCS "${srcs}"
854                        ...)
855
856硬件目标的条件判断
857--------------------
858
859CMake 文件可以使用 ``IDF_TARGET`` 变量来获取当前的硬件目标。
860
861此外,如果当前的硬件目标是 ``xyz`` (即 ``IDF_TARGET=xyz``),那么 Kconfig 变量 ``CONFIG_IDF_TARGET_XYZ`` 同样也会被设置。
862
863请注意,组件可以依赖 ``IDF_TARGET`` 变量,但不能依赖这个 Kconfig 变量。同样也不可在 CMake 文件的 ``include`` 语句中使用 Kconfig 变量,在这种上下文中可以使用 ``IDF_TARGET``。
864
865
866生成源代码
867----------
868
869有些组件的源文件可能并不是由组件本身提供,而必须从另外的文件生成。假设组件需要一个头文件,该文件由 BMP 文件转换后(使用 bmp2h 工具)的二进制数据组成,然后将头文件包含在名为 graphics_lib.c 的文件中::
870
871    add_custom_command(OUTPUT logo.h
872         COMMAND bmp2h -i ${COMPONENT_DIR}/logo.bmp -o log.h
873         DEPENDS ${COMPONENT_DIR}/logo.bmp
874         VERBATIM)
875
876    add_custom_target(logo DEPENDS logo.h)
877    add_dependencies(${COMPONENT_LIB} logo)
878
879    set_property(DIRECTORY "${COMPONENT_DIR}" APPEND PROPERTY
880         ADDITIONAL_MAKE_CLEAN_FILES logo.h)
881
882这个示例改编自 `CMake 的一则 FAQ <cmake faq generated files_>`_,其中还包含了一些同样适用于 ESP-IDF 构建系统的示例。
883
884这个示例会在当前目录(构建目录)中生成 logo.h 文件,而 logo.bmp 会随组件一起提供在组件目录中。因为 logo.h 是一个新生成的文件,一旦项目需要清理,该文件也应该要被清除。因此,要将该文件添加到 `ADDITIONAL_MAKE_CLEAN_FILES`_ 属性中。
885
886.. Note::
887
888   如果需要生成文件作为项目 CMakeLists.txt 的一部分,而不是作为组件 CMakeLists.txt 的一部分,此时需要使用 ``${PROJECT_PATH}`` 替代 ``${COMPONENT_DIR}``,使用 ``${PROJECT_NAME}.elf`` 替代 ``${COMPONENT_LIB}``。
889
890如果某个源文件是从其他组件中生成,且包含 ``logo.h`` 文件,则需要调用 ``add_dependencies``, 在这两个组件之间添加一个依赖项,以确保组件源文件按照正确顺序进行编译。
891
892.. _cmake_embed_data:
893
894嵌入二进制数据
895---------------------
896
897有时您的组件希望使用一个二进制文件或者文本文件,但是您又不希望将它们重新格式化为 C 源文件。
898
899这时,您可以在组件注册中指定 ``EMBED_FILES`` 参数,用空格分隔要嵌入的文件名称::
900
901  idf_component_register(...
902                         EMBED_FILES server_root_cert.der)
903
904或者,如果文件是字符串,则可以使用 ``EMBED_TXTFILES`` 变量,把文件的内容转成以 null 结尾的字符串嵌入::
905
906  idf_component_register(...
907                         EMBED_TXTFILES server_root_cert.pem)
908
909.. highlight:: c
910
911文件的内容会被添加到 Flash 的 .rodata 段,用户可以通过符号名来访问,如下所示::
912
913  extern const uint8_t server_root_cert_pem_start[] asm("_binary_server_root_cert_pem_start");
914  extern const uint8_t server_root_cert_pem_end[]   asm("_binary_server_root_cert_pem_end");
915
916符号名会根据文件全名生成,如 ``EMBED_FILES`` 中所示,字符 ``/``、``.`` 等都会被下划线替代。符号名称中的 _binary 前缀由 objcopy 命令添加,对文本文件和二进制文件都是如此。
917
918.. highlight:: cmake
919
920如果要将文件嵌入到项目中,而非组件中,可以调用 ``target_add_binary_data`` 函数::
921
922    target_add_binary_data(myproject.elf "main/data.bin" TEXT)
923
924并将这行代码放在项目 CMakeLists.txt 的 ``project()`` 命令之后,修改 ``myproject.elf`` 为你自己的项目名。如果最后一个参数是 ``TEXT``,那么构建系统会嵌入以 null 结尾的字符串,如果最后一个参数被设置为 ``BINARY``,则将文件内容按照原样嵌入。
925
926有关使用此技术的示例,请查看 file_serving 示例 :example_file:`protocols/http_server/file_serving/main/CMakeLists.txt` 中的 main 组件,两个文件会在编译时加载并链接到固件中。
927
928.. highlight:: cmake
929
930也可以嵌入生成的文件::
931
932  add_custom_command(OUTPUT my_processed_file.bin
933                    COMMAND my_process_file_cmd my_unprocessed_file.bin)
934  target_add_binary_data(my_target "my_processed_file.bin" BINARY)
935
936上述示例中,``my_processed_file.bin`` 是通过命令 ``my_process_file_cmd`` 从文件 ``my_unprocessed_file.bin`` 中生成,然后嵌入到目标中。
937
938使用 ``DEPENDS`` 参数来指明对目标的依赖性::
939
940  add_custom_target(my_process COMMAND ...)
941  target_add_binary_data(my_target "my_embed_file.bin" BINARY DEPENDS my_process)
942
943``target_add_binary_data`` 的 ``DEPENDS`` 参数确保目标首先执行。
944
945代码和数据的存放
946----------------
947
948ESP-IDF 还支持自动生成链接脚本,它允许组件通过链接片段文件定义其代码和数据在内存中的存放位置。构建系统会处理这些链接片段文件,并将处理后的结果扩充进链接脚本,从而指导应用程序二进制文件的链接过程。更多详细信息与快速上手指南,请参阅 :doc:`链接脚本生成机制 <linker-script-generation>`。
949
950.. _component-build-full-override:
951
952完全覆盖组件的构建过程
953----------------------
954
955.. highlight:: cmake
956
957当然,在有些情况下,上面提到的方法不一定够用。如果组件封装了另一个第三方组件,而这个第三方组件并不能直接在 ESP-IDF 的构建系统中工作,在这种情况下,就需要放弃 ESP-IDF 的构建系统,改为使用 CMake 的 ExternalProject_ 功能。组件 CMakeLists 示例如下::
958
959    # 用于 quirc 的外部构建过程,在源目录中运行
960    # 并生成 libquirc.a
961    externalproject_add(quirc_build
962        PREFIX ${COMPONENT_DIR}
963        SOURCE_DIR ${COMPONENT_DIR}/quirc
964        CONFIGURE_COMMAND ""
965        BUILD_IN_SOURCE 1
966        BUILD_COMMAND make CC=${CMAKE_C_COMPILER} libquirc.a
967        INSTALL_COMMAND ""
968        )
969
970    # 将 libquirc.a 添加到构建系统中
971    add_library(quirc STATIC IMPORTED GLOBAL)
972    add_dependencies(quirc quirc_build)
973
974    set_target_properties(quirc PROPERTIES IMPORTED_LOCATION
975        ${COMPONENT_DIR}/quirc/libquirc.a)
976    set_target_properties(quirc PROPERTIES INTERFACE_INCLUDE_DIRECTORIES
977        ${COMPONENT_DIR}/quirc/lib)
978
979    set_directory_properties( PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES
980        "${COMPONENT_DIR}/quirc/libquirc.a")
981
982(上述 CMakeLists.txt 可用于创建名为 ``quirc`` 的组件,该组件使用自己的 Makefile 构建 quirc_ 项目。)
983
984- ``externalproject_add`` 定义了一个外部构建系统。
985
986  - 设置 ``SOURCE_DIR``、``CONFIGURE_COMMAND``、``BUILD_COMMAND`` 和 ``INSTALL_COMMAND``。如果外部构建系统没有配置这一步骤,可以将 ``CONFIGURE_COMMAND`` 设置为空字符串。在 ESP-IDF 的构建系统中,一般会将 ``INSTALL_COMMAND`` 变量设置为空。
987  - 设置 ``BUILD_IN_SOURCE``,即构建目录与源目录相同。否则,您也可以设置 ``BUILD_DIR`` 变量。
988  - 有关 ``externalproject_add()`` 命令的详细信息,请参阅 ExternalProject_。
989
990- 第二组命令添加了一个目标库,指向外部构建系统生成的库文件。为了添加 include 目录,并告知 CMake 该文件的位置,需要再设置一些属性。
991- 最后,生成的库被添加到 `ADDITIONAL_MAKE_CLEAN_FILES`_ 中。即执行 ``make clean`` 后会删除该库。请注意,构建系统中的其他目标文件不会被删除。
992
993.. only:: esp32
994
995    .. note:: 当外部构建系统使用 PSRAM 时,请记得将 ``-mfix-esp32-psram-cache-issue`` 添加到 C 编译器的参数中。关于该标志的更多详细信息,请参考 :ref:`CONFIG_SPIRAM_CACHE_WORKAROUND`。
996
997.. _ADDITIONAL_MAKE_CLEAN_FILES_note:
998
999ExternalProject 的依赖与构建清理
1000^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1001
1002对于外部项目的构建,CMake 会有一些不同寻常的行为:
1003
1004- `ADDITIONAL_MAKE_CLEAN_FILES`_ 仅在使用 Make 构建系统时有效。如果使用 Ninja_ 或 IDE 自带的构建系统,执行项目清理时,这些文件不会被删除。
1005- ExternalProject_ 会在 clean 运行后自动重新运行配置和构建命令。
1006- 可以采用以下两种方法来配置外部构建命令:
1007
1008  1. 将外部 ``BUILD_COMMAND`` 命令设置为对所有源代码完整的重新编译。如果传递给 ``externalproject_add`` 命令的 ``DEPENDS`` 的依赖项发生了改变,或者当前执行的是项目清理操作(即运行了 ``idf.py clean``、``ninja clean`` 或者 ``make clean``),那么就会执行该命令。
1009  2. 将外部 ``BUILD_COMMAND`` 命令设置为增量式构建命令,并给 ``externalproject_add`` 传递 ``BUILD_ALWAYS 1`` 参数。即不管实际的依赖情况,每次构建时,都会构建外部项目。这种方式仅当外部构建系统具备增量式构建的能力,且运行时间不会很长时才推荐。
1010
1011构建外部项目的最佳方法取决于项目本身、其构建系统,以及是否需要频繁重新编译项目。
1012
1013.. _custom-sdkconfig-defaults:
1014
1015自定义 sdkconfig 的默认值
1016=========================
1017
1018对于示例工程或者其他您不想指定完整 sdkconfig 配置的项目,但是您确实希望覆盖 ESP-IDF 默认值中的某些键值,则可以在项目中创建 ``sdkconfig.defaults`` 文件。重新创建新配置时将会用到此文件,另外在 ``sdkconfig`` 没有设置新配置值时,上述文件也会被用到。
1019
1020如若需要覆盖此文件的名称或指定多个文件,请设置 ``SDKCONFIG_DEFAULTS`` 环境变量或在顶层 CMakeLists.txt 文件中设置 ``SDKCONFIG_DEFAULTS``。在指定多个文件时,使用分号作为分隔符。未指定完整路径的文件名将以当前项目的相对路径来解析。
1021
1022一些 IDF 示例中包含了 ``sdkconfig.ci`` 文件。该文件是 CI(持续集成)测试框架的一部分,在正常构建过程中会被忽略。
1023
1024依赖于硬件目标的 sdkconfig 默认值
1025---------------------------------
1026
1027除了 ``sdkconfig.defaults`` 之外,构建系统还将从 ``sdkconfig.defaults.TARGET_NAME`` 文件加载默认值,其中 ``IDF_TARGET`` 的值为 ``TARGET_NAME``。例如,对于 ``ESP32`` 这个硬件目标,sdkconfig 的默认值会首先从 ``sdkconfig.defaults`` 获取,然后再从 ``sdkconfig.defaults.esp32`` 获取。
1028
1029如果使用 ``SDKCONFIG_DEFAULTS`` 覆盖了 sdkconfig 默认文件的名称,则硬件目标的 sdkconfig 默认文件名也会从 ``SDKCONFIG_DEFAULTS`` 值中派生。
1030
1031.. _flash_parameters:
1032
1033Flash 参数
1034==========
1035
1036有些情况下,我们希望在没有 IDF 时也能烧写目标板,为此,我们希望可以保存已构建的二进制文件、esptool.py 和 esptool write_flash 命令的参数。可以通过编写一段简单的脚本来保存二进制文件和 esptool.py1037
1038运行项目构建之后,构建目录将包含项目二进制输出文件(``.bin`` 文件),同时也包含以下烧录数据文件:
1039
1040- ``flash_project_args`` 包含烧录整个项目的参数,包括应用程序 (app)、引导程序 (bootloader)、分区表,如果设置了 PHY 数据,也会包含此数据。
1041- ``flash_app_args`` 只包含烧录应用程序的参数。
1042- ``flash_bootloader_args`` 只包含烧录引导程序的参数。
1043
1044.. highlight:: bash
1045
1046您可以参照如下命令将任意烧录参数文件传递给 ``esptool.py``::
1047
1048    python esptool.py --chip {IDF_TARGET_PATH_NAME} write_flash @build/flash_project_args
1049
1050也可以手动复制参数文件中的数据到命令行中执行。
1051
1052构建目录中还包含生成的 ``flasher_args.json`` 文件,此文件包含 JSON 格式的项目烧录信息,可用于 ``idf.py`` 和其它需要项目构建信息的工具。
1053
1054构建 Bootloader
1055===============
1056
1057引导程序默认作为 ``idf.py build`` 的一部分被构建,也可以通过 ``idf.py bootloader`` 来单独构建。
1058
1059引导程序是 :idf:`/components/bootloader/subproject` 内部独特的“子项目”,它有自己的项目 CMakeLists.txt 文件,能够构建独立于主项目的 ``.ELF`` 和 ``.BIN`` 文件,同时它又与主项目共享配置和构建目录。
1060
1061子项目通过 :idf_file:`/components/bootloader/project_include.cmake` 文件作为外部项目插入到项目的顶层,主构建进程会运行子项目的 CMake,包括查找组件(主项目使用的组件的子集),生成引导程序专用的配置文件(从主 ``sdkconfig`` 文件中派生)。
1062
1063.. _selecting-idf-target:
1064
1065选择目标芯片
1066====================
1067
1068ESP-IDF 支持多款芯片,它们通过在软件中使用不同的 “目标” (target) 名进行区分,具体对应关系如下:
1069
1070* ``esp32`` — 适用于 ESP32-D0WD、ESP32-D2WD、ESP32-S0WD (ESP-SOLO)、ESP32-U4WDH、ESP32-PICO-D4
1071* ``esp32s2``— 适用于 ESP32-S2
1072* ``esp32c3``— 适用于 ESP32-C3
1073
1074在构建项目前,请首先根据您的芯片选择正确的软件目标,具体命令为 ``idf.py set-target <target>``。举例 ::
1075
1076    idf.py set-target esp32s2
1077
1078.. important::
1079
1080    运行 ``idf.py set-target`` 命令将清除 ``build`` 文件夹的内容,并重新生成一个 ``sdkconfig`` 文件。之前的 ``sdkconfig`` 将另存为 ``sdkconfig.old``。
1081
1082.. note::
1083
1084    运行 ``idf.py set-target`` 命令相当于分别运行以下几个命令:
1085
1086    1. 清除 ``build`` 文件夹 (``idf.py fullclean``)
1087    2. 移除 ``sdkconfig`` 文件 (``mv sdkconfig sdkconfig.old``)
1088    3. 根据选择的“目标”芯片配置项目 (``idf.py -DIDF_TARGET=esp32 reconfigure``)
1089
1090您也可以将要用的 ``IDF_TARGET`` 设置为环境变量,比如:``export IDF_TARGET=esp32s2``;或设置为 CMake 变量,比如将 ``-DIDF_TARGET=esp32s2`` 以参数形式传递给 CMake 或 idf.py。如果您大多数时间仅使用一款芯片,则将 ``IDF_TARGET`` 配置为环境变量比较方便。
1091
1092对于特定项目,您可以使用以下方式为 ``IDF_TARGET`` 配置 _default_ 值:把 ``CONFIG_IDF_TARGET`` 的值加入 ``sdkconfig.defaults``。举例而言,配置 ``CONFIG_IDF_TARGET="esp32s2"``。这样一来,除非特别设置(比如使用环境变量、CMake 变量或 ``idf.py set-target`` 命令),否则 ``IDF_TARGET`` 将默认采用 ``CONFIG_IDF_TARGET``。
1093
1094如果您从未通过以上述任何方式配置过“目标”芯片,则构建系统会默认将 ``esp32`` 设定为“目标”芯片。
1095
1096.. _write-pure-component:
1097
1098编写纯 CMake 组件
1099=================
1100
1101ESP-IDF 构建系统用“组件”的概念“封装”了 CMake,并提供了很多帮助函数来自动将这些组件集成到项目构建当中。
1102
1103然而,“组件”概念的背后是一个完整的 CMake 构建系统,因此可以制作纯 CMake 组件。
1104
1105.. highlight:: cmake
1106
1107下面是使用纯 CMake 语法为 ``json`` 组件编写的最小 CMakeLists 文件的示例::
1108
1109  add_library(json STATIC
1110  cJSON/cJSON.c
1111  cJSON/cJSON_Utils.c)
1112
1113  target_include_directories(json PUBLIC cJSON)
1114
1115- 这实际上与 IDF 中的 :idf_file:`json 组件 </components/json/CMakeLists.txt>` 是等效的。
1116- 因为组件中的源文件不多,所以这个 CMakeLists 文件非常简单。对于具有大量源文件的组件而言,ESP-IDF 支持的组件通配符,可以简化组件 CMakeLists 的样式。
1117- 每当组件中新增一个与组件同名的库目标时,ESP-IDF 构建系统会自动将其添加到构建中,并公开公共的 include 目录。如果组件想要添加一个与组件不同名的库目标,就需要使用 CMake 命令手动添加依赖关系。
1118
1119组件中使用第三方 CMake 项目
1120===========================
1121
1122CMake 在许多开源的 C/C++ 项目中广泛使用,用户可以在自己的应用程序中使用开源代码。CMake 构建系统的一大好处就是可以导入这些第三方的项目,有时候甚至不用做任何改动。这就允许用户使用当前 ESP-IDF 组件尚未提供的功能,或者使用其它库来实现相同的功能。
1123
1124.. highlight:: cmake
1125
1126假设 ``main`` 组件需要导入一个假想库 ``foo``,相应的组件 CMakeLists 文件如下所示::
1127
1128    # 注册组件
1129    idf_component_register(...)
1130
1131    # 设置 `foo` 项目中的一些 CMake 变量,以控制 `foo` 的构建过程
1132    set(FOO_BUILD_STATIC OFF)
1133    set(FOO_BUILD_TESTS OFF)
1134
1135    # 创建并导入第三方库目标
1136    add_subdirectory(foo)
1137
1138    # 将 `foo` 目标公开链接至 `main` 组件
1139    target_link_libraries(main PUBLIC foo)
1140
1141实际的案例请参考 :example:`build_system/cmake/import_lib`。请注意,导入第三方库所需要做的工作可能会因库的不同而有所差异。建议仔细阅读第三方库的文档,了解如何将其导入到其它项目中。阅读第三方库的 CMakeLists.txt 文件以及构建结构也会有所帮助。
1142
1143用这种方式还可以将第三方库封装成 ESP-IDF 的组件。例如 :component:`mbedtls` 组件就是封装了 `mbedtls 项目 <https://github.com/ARMmbed/mbedtls>`_ 得到的。详情请参考 :component_file:`mbedtls 组件的 CMakeLists.txt 文件 <mbedtls/CMakeLists.txt>`。
1144
1145每当使用 ESP-IDF 构建系统时,CMake 变量 ``ESP_PLATFORM`` 都会被设置为 1。如果要在通用的 CMake 代码加入 IDF 特定的代码时,可以采用 ``if (ESP_PLATFORM)`` 的形式加以分隔。
1146
1147外部库中使用 ESP-IDF 组件
1148--------------------------
1149
1150上述示例中假设的是外部库 ``foo`` (或 ``import_lib`` 示例中的 ``tinyxml`` 库)除了常见的 API 如 libc、libstdc++ 等外不需要使用其它 ESP-IDF API。如果外部库需要使用其它 ESP-IDF 组件提供的 API,则需要在外部 CMakeLists.txt 文件中通过添加对库目标 ``idf::<componentname>`` 的依赖关系。
1151
1152例如,在 ``foo/CMakeLists.txt`` 文件::
1153
1154  add_library(foo bar.c fizz.cpp buzz.cpp)
1155
1156  if(ESP_PLATFORM)
1157    # 在 ESP-IDF 中、 bar.c 需要包含 spi_flash 组件中的 esp_spi_flash.h
1158    target_link_libraries(foo PRIVATE idf::spi_flash)
1159  endif()
1160
1161
1162组件中使用预建库
1163=================
1164
1165.. highlight:: cmake
1166
1167还有一种情况是您有一个由其它构建过程生成预建静态库(``.a`` 文件)。
1168
1169ESP-IDF 构建系统为用户提供了一个实用函数 ``add_prebuilt_library``,能够轻松导入并使用预建库::
1170
1171  add_prebuilt_library(target_name lib_path [REQUIRES req1 req2 ...] [PRIV_REQUIRES req1 req2 ...])
1172
1173其中:
1174
1175- ``target_name``- 用于引用导入库的名称,如链接到其它目标时
1176- ``lib_path``- 预建库的路径,可以是绝对路径或是相对于组件目录的相对路径
1177
1178可选参数 ``REQUIRES`` 和 ``PRIV_REQUIRES`` 指定对其它组件的依赖性。这些参数与 ``idf_component_register`` 的参数的意义相同。
1179
1180注意预建库的编译目标需与目前的项目相同。预建库的相关参数也要匹配。如果不特别注意,这两个因素可能会导致应用程序中出现 bug。
1181
1182请查看示例 :example:`build_system/cmake/import_prebuilt`。
1183
1184在自定义 CMake 项目中使用 ESP-IDF
1185=================================
1186
1187ESP-IDF 提供了一个模板 CMake 项目,可以基于此轻松创建应用程序。然而在有些情况下,用户可能已有一个现成的 CMake 项目,或者想自己创建一个 CMake 项目,此时就希望将 IDF 中的组件以库的形式链接到用户目标(库/可执行文件)。
1188
1189可以通过 :idf_file:`tools/cmake/idf.cmake` 提供的 :ref:`build system APIs <cmake_buildsystem_api>` 实现该目标。例如:
1190
1191.. code-block:: cmake
1192
1193  cmake_minimum_required(VERSION 3.5)
1194  project(my_custom_app C)
1195
1196  # 导入提供 ESP-IDF CMake 构建系统 API 的 CMake 文件
1197  include($ENV{IDF_PATH}/tools/cmake/idf.cmake)
1198
1199  # 在构建中导入 ESP-IDF 组件,可以视作等同 add_subdirectory()
1200  # 但为 ESP-IDF 构建增加额外的构建过程
1201  # 具体构建过程
1202  idf_build_process(esp32)
1203
1204  # 创建项目可执行文件
1205  # 使用其别名 idf::newlib 将其链接到 newlib 组件
1206  add_executable(${CMAKE_PROJECT_NAME}.elf main.c)
1207  target_link_libraries(${CMAKE_PROJECT_NAME}.elf idf::newlib)
1208
1209  # 让构建系统知道项目到可执行文件是什么,从而添加更多的目标以及依赖关系等
1210  idf_build_executable(${CMAKE_PROJECT_NAME}.elf)
1211
1212:example:`build_system/cmake/idf_as_lib` 中的示例演示了如何在自定义的 CMake 项目创建一个类似于 :example:`Hello World <get-started/hello_world>` 的应用程序。
1213
1214.. only:: esp32
1215
1216   .. note:: IDF 构建系统只能为其构建的源文件设置编译器标志。当使用外部 CMakeLists.txt 文件并启用 PSRAM 时,记得在 C 编译器参数中添加 ``mfix-esp32-psram-cache-issue``。参见:ref:`CONFIG_SPIRAM_CACHE_WORKAROUND` 了解更多信息。
1217
1218.. _cmake_buildsystem_api:
1219
1220ESP-IDF CMake 构建系统 API
1221==============================
1222
1223idf 构建命令
1224------------------
1225
1226.. code-block:: none
1227
1228  idf_build_get_property(var property [GENERATOR_EXPRESSION])
1229
1230检索一个 :ref:`构建属性 <cmake-build-properties>` *property*,并将其存储在当前作用域可访问的 var 中。特定 *GENERATOR_EXPRESSION* 将检索该属性的生成器表达式字符串(不是实际值),它可与支持生成器表达式的 CMake 命令一起使用。
1231
1232.. code-block:: none
1233
1234  idf_build_set_property(property val [APPEND])
1235
1236设置 :ref:`构建属性 <cmake-build-properties>` *property* 的值为 *val*。特定 *APPEND* 将把指定的值附加到属性当前值之后。如果该属性之前不存在或当前为空,则指定的值将变为第一个元素/成员。
1237
1238.. code-block:: none
1239
1240  idf_build_component(component_dir)
1241
1242向构建系统提交一个包含组件的 *component_dir* 目录。相对路径会被转换为相对于当前目录的绝对路径。
1243所有对该命令的调用必须在`idf_build_process`之前执行。
1244
1245该命令并不保证组件在构建过程中会被处理(参见 `idf_build_process` 中 `COMPONENTS` 参数说明)
1246
1247.. code-block:: none
1248
1249  idf_build_process(target
1250                    [PROJECT_DIR project_dir]
1251                    [PROJECT_VER project_ver]
1252                    [PROJECT_NAME project_name]
1253                    [SDKCONFIG sdkconfig]
1254                    [SDKCONFIG_DEFAULTS sdkconfig_defaults]
1255                    [BUILD_DIR build_dir]
1256                    [COMPONENTS component1 component2 ...])
1257
1258为导入 ESP-IDF 组件执行大量的幕后工作,包括组件配置、库创建、依赖性扩展和解析。在这些功能中,对于用户最重要的可能是通过调用每个组件的 ``idf_component_register`` 来创建库。该命令为每个组件创建库,这些库可以使用别名来访问,其形式为 idf::*component_name*。
1259这些别名可以用来将组件链接到用户自己的目标、库或可执行文件上。
1260
1261该调用要求用 *target* 参数指定目标芯片。调用的可选参数包括:
1262
1263- PROJECT_DIR - 项目目录,默认为 CMAKE_SOURCE_DIR。
1264- PROJECT_NAME - 项目名称,默认为 CMAKE_PROJECT_NAME。
1265- PROJECT_VER - 项目的版本/版本号,默认为 "1"。
1266- SDKCONFIG - 生成的 sdkconfig 文件的输出路径,根据是否设置 PROJECT_DIR,默认为 PROJECT_DIR/sdkconfigCMAKE_SOURCE_DIR/sdkconfig1267- SDKCONFIG_DEFAULTS - 包含默认配置的文件列表(列表中必须包含完整的路径),默认为空;对于列表中的每个值 *filename*,如果存在的话,也会加载文件 *filename.target* 中的配置。对于列表中的 *filename* 的每一个值,也会加载文件 *filename.target* (如果存在的话)中的配置。
1268- BUILD_DIR - 用于放置 ESP-IDF 构建相关工具的目录,如生成的二进制文件、文本文件、组件;默认为CMAKE_BINARY_DIR。
1269- COMPONENTS - 从构建系统已知的组件中选择要处理的组件(通过 ``idf_build_component`` 添加)。这个参数用于精简构建过程。
1270  如果在依赖链中需要其它组件,则会自动添加,即自动添加这个列表中组件的公共和私有依赖项,进而添加这些依赖项的公共和私有依赖,以此类推。如果不指定,则会处理构建系统已知的所有组件。
1271
1272.. code-block:: none
1273
1274  idf_build_executable(executable)
1275
1276指定 ESP-IDF 构建的可执行文件 *executable*。这将添加额外的目标,如与 flash 相关的依赖关系,生成额外的二进制文件等。应在 ``idf_build_process`` 之后调用。
1277
1278.. code-block:: none
1279
1280  idf_build_get_config(var config [GENERATOR_EXPRESSION])
1281
1282获取指定配置的值。就像构建属性一样,特定 *GENERATOR_EXPRESSION* 将检索该配置的生成器表达式字符串,而不是实际值,即可以与支持生成器表达式的 CMake 命令一起使用。然而,实际的配置值只有在调用 ``idf_build_process`` 后才能知道。
1283
1284.. _cmake-build-properties:
1285
1286idf 构建属性
1287--------------------
1288
1289可以通过使用构建命令 ``idf_build_get_property`` 来获取构建属性的值。例如,以下命令可以获取构建过程中使用的 Python 解释器的相关信息。
1290
1291.. code-block:: none
1292
1293  idf_build_get_property(python PYTHON)
1294  message(STATUS "The Python intepreter is: ${python}")
1295
1296- BUILD_DIR - 构建目录;由 ``idf_build_process`` 的 BUILD_DIR 参数设置。
1297- BUILD_COMPONENTS - 包含在构建中的组件列表;由 ``idf_build_process`` 设置。
1298- BUILD_COMPONENT_ALIASES - 包含在构建中的组件的库别名列表;由 ``idf_build_process`` 设置。
1299- C_COMPILE_OPTIONS - 适用于所有组件的 C 源代码文件的编译选项。
1300- COMPILE_OPTIONS - 适用于所有组件的源文件(无论是 C 还是 C++)的编译选项。
1301- COMPILE_DEFINITIONS - 适用于所有组件源文件的编译定义。
1302- CXX_COMPILE_OPTIONS - 适用于所有组件的 C++ 源文件的编译选项。
1303- EXECUTABLE - 项目可执行文件;通过调用 ``idf_build_executable`` 设置。
1304- EXECUTABLE_NAME - 不含扩展名的项目可执行文件的名称;通过调用 ``idf_build_executable`` 设置。
1305- EXECUTABLE_DIR - 输出的可执行文件的路径
1306- IDF_PATH - ESP-IDF 路径;由 IDF_PATH 环境变量设置,或者从 ``idf.cmake`` 的位置推断。
1307- IDF_TARGET - 构建的目标芯片;由 ``idf_build_process`` 的目标参数设置。
1308- IDF_VER - ESP-IDF 版本;由版本文件或 IDF_PATH 仓库的 Git 版本设置。
1309- INCLUDE_DIRECTORIES - 包含所有组件源文件的目录。
1310- KCONFIGS - 构建过程中组件里的 Kconfig 文件的列表;由 ``idf_build_process`` 设置。
1311- KCONFIG_PROJBUILDS - 构建过程中组件中的 Kconfig.projbuild 文件的列表;由 ``idf_build_process`` 设置。
1312- PROJECT_NAME - 项目名称;由 ``idf_build_process`` 的 PROJECT_NAME 参数设置。
1313- PROJECT_DIR - 项目的目录;由 ``idf_build_process`` 的 PROJECT_DIR 参数设置。
1314- PROJECT_VER - 项目的版本;由 ``idf_build_process`` 的 PROJECT_VER 参数设置。
1315- PYTHON - 用于构建的 Python 解释器;如果有则从 PYTHON 环境变量中设置,如果没有,则使用 "python"。
1316- SDKCONFIG - 输出的配置文件的完整路径;由 ``idf_build_process`` SDKCONFIG 参数设置。
1317- SDKCONFIG_DEFAULTS - 包含默认配置的文件列表;由 ``idf_build_process`` SDKCONFIG_DEFAULTS 参数设置。
1318- SDKCONFIG_HEADER - 包含组件配置的 C/C++ 头文件的完整路径;由 ``idf_build_process`` 设置。
1319- SDKCONFIG_CMAKE - 包含组件配置的 CMake 文件的完整路径;由 ``idf_build_process`` 设置。
1320- SDKCONFIG_JSON - 包含组件配置的 JSON 文件的完整路径;由 ``idf_build_process`` 设置。
1321- SDKCONFIG_JSON_MENUS - 包含配置菜单的 JSON 文件的完整路径;由 ``idf_build_process`` 设置。
1322
1323idf 组件命令
1324----------------------
1325
1326.. code-block:: none
1327
1328  idf_component_get_property(var component property [GENERATOR_EXPRESSION])
1329
1330检索一个指定的 *component* 的 :ref:`组件属性<cmake-component-properties>` *property*,并将其存储在当前作用域可访问的 *var* 中。指定 *GENERATOR_EXPRESSION* 将检索该属性的生成器表达式字符串(不是实际值),它可以在支持生成器表达式的 CMake 命令中使用。
1331
1332.. code-block:: none
1333
1334  idf_component_set_property(component property val [APPEND])
1335
1336设置指定的 *component* 的 :ref:`组件属性<cmake-component-properties>`,*property* 的值为 *val*。特定 *APPEND* 将把指定的值追加到属性的当前值后。如果该属性之前不存在或当前为空,指定的值将成为第一个元素/成员。
1337
1338.. _cmake-component-register:
1339
1340.. code-block:: none
1341
1342  idf_component_register([[SRCS src1 src2 ...] | [[SRC_DIRS dir1 dir2 ...] [EXCLUDE_SRCS src1 src2 ...]]
1343                         [INCLUDE_DIRS dir1 dir2 ...]
1344                         [PRIV_INCLUDE_DIRS dir1 dir2 ...]
1345                         [REQUIRES component1 component2 ...]
1346                         [PRIV_REQUIRES component1 component2 ...]
1347                         [LDFRAGMENTS ldfragment1 ldfragment2 ...]
1348                         [REQUIRED_IDF_TARGETS target1 target2 ...]
1349                         [EMBED_FILES file1 file2 ...]
1350                         [EMBED_TXTFILES file1 file2 ...]
1351                         [KCONFIG kconfig]
1352                         [KCONFIG_PROJBUILD kconfig_projbuild])
1353
1354将一个组件注册到构建系统中。就像 ``project()`` CMake 命令一样,该命令应该直接从组件的 CMakeLists.txt 中调用(而不是通过函数或宏),且建议在其他命令之前调用该命令。下面是一些关于在 ``idf_component_register`` 之前 *不能* 调用哪些命令的指南:
1355
1356  - 在 CMake 脚本模式下无效的命令。
1357  - 在 project_include.cmake 中定义的自定义命令。
1358  - 除了 ``idf_build_get_property`` 之外,构建系统的 API 命令;但要考虑该属性是否有被设置。
1359
1360对变量进行设置和操作的命令,一般可在 ``idf_component_register`` 之前调用。
1361
1362``idf_component_register`` 的参数包括:
1363
1364  - SRCS - 组件的源文件,用于为组件创建静态库;如果没有指定,组件将被视为仅配置组件,从而创建接口库。
1365  - SRC_DIRS、 EXCLUDE_SRCS - 用于通过指定目录来 glob 源文件 (.c、.cpp、.S),而不是通过 SRCS 手动指定源文件。请注意,这受 :ref:`CMake 中通配符的限制<cmake-file-globbing>`。 在 EXCLUDE_SRCS 中指定的源文件会从被 glob 的文件中移除。
1366  - INCLUDE_DIRS - 相对于组件目录的路径,该路径将被添加到需要当前组件的所有其他组件的 include 搜索路径中。
1367  - PRIV_INCLUDE_DIRS - 必须是相对于组件目录的目录路径,它仅被添加到这个组件源文件的 include 搜索路径中。
1368  - REQUIRES - 组件的公共组件依赖项。
1369  - PRIV_REQUIRES - 组件的私有组件依赖项;在仅用于配置的组件上会被忽略。
1370  - LDFRAGMENTS - 组件链接器片段文件。
1371  - REQUIRED_IDF_TARGETS - 指定该组件唯一支持的目标。
1372  - KCONFIG - 覆盖默认的 Kconfig 文件。
1373  - KCONFIG_PROJBUILD - 覆盖默认的 Kconfig.projbuild 文件。
1374
1375以下内容用于 :ref:`将数据嵌入到组件中<cmake_embed_data>`,并在确定组件是否仅用于配置时被视为源文件。这意味着,即使组件没有指定源文件,如果组件指定了以下其中之一,仍然会在内部为组件创建一个静态库。
1376
1377  - EMBED_FILES - 嵌入组件的二进制文件
1378  - EMBED_TXTFILES - 嵌入组件的文本文件
1379
1380.. _cmake-component-properties:
1381
1382idf 组件属性
1383------------------------
1384
1385组件的属性值可以通过使用构建命令 ``idf_component_get_property`` 来获取。例如,以下命令可以获取 ``freertos`` 组件的目录。
1386
1387.. code-block:: cmake
1388
1389  idf_component_get_property(dir freertos COMPONENT_DIR)
1390  message(STATUS "The 'freertos' component directory is: ${dir}")
1391
1392- COMPONENT_ALIAS - COMPONENT_LIB 的别名,用于将组件链接到外部目标;由 ``idf_build_component`` 设置,别名库本身由 ``idf_component_register`` 创建。
1393- COMPONENT_DIR - 组件目录;由 ``idf_build_component`` 设置。
1394- COMPONENT_OVERRIDEN_DIR - 如果 :ref:`这个组件覆盖了另一个组件<cmake-components-same-name>`,则包含原组件的目录。
1395- COMPONENT_LIB - 所创建的组件静态/接口库的名称;由 ``idf_build_component`` 设置,库本身由 ``idf_component_register`` 创建。
1396- COMPONENT_NAME - 组件的名称;由 ``idf_build_component`` 根据组件的目录名设置。
1397- COMPONENT_TYPE - 组件的类型(LIBRARY 或 CONFIG_ONLY)。如果一个组件指定了源文件或嵌入了一个文件,那么它的类型就是 LIBRARY。
1398- EMBED_FILES - 要嵌入组件的文件列表;由 ``idf_component_register`` EMBED_FILES 参数设置。
1399- EMBED_TXTFILES - 要嵌入组件的文本文件列表;由 ``idf_component_register`` EMBED_TXTFILES 参数设置。
1400- INCLUDE_DIRS - 组件 include 目录列表;由 ``idf_component_register`` INCLUDE_DIRS 参数设置。
1401- KCONFIG - 组件 Kconfig 文件;由 ``idf_build_component`` 设置。
1402- KCONFIG_PROJBUILD - 组件 Kconfig.projbuild;由 ``idf_build_component`` 设置。
1403- LDFRAGMENTS - 组件链接器片段文件列表;由 ``idf_component_register`` LDFRAGMENTS 参数设置。
1404- PRIV_INCLUDE_DIRS - 组件私有 include 目录列表;在 LIBRARY 类型的组件 ``idf_component_register`` PRIV_INCLUDE_DIRS 参数中设置。
1405- PRIV_REQUIRES - 私有组件依赖关系列表;由 ``idf_component_register`` PRIV_REQUIRES 参数设置。
1406- REQUIRED_IDF_TARGETS - 组件支持的目标列表;由 ``idf_component_register``  EMBED_TXTFILES 参数设置。
1407- REQUIRES - 公共组件依赖关系列表;由 ``idf_component_register`` REQUIRES 参数设置。
1408- SRCS - 组件源文件列表;由 ``idf_component_register`` 的 SRCS 或 SRC_DIRS/EXCLUDE_SRCS 参数设置。
1409
1410.. _cmake-file-globbing:
1411
1412文件通配 & 增量构建
1413=====================
1414
1415.. highlight:: cmake
1416
1417在 ESP-IDF 组件中添加源文件的首选方法是在 ``COMPONENT_SRCS`` 中手动列出它们::
1418
1419  idf_component_register(SRCS library/a.c library/b.c platform/platform.c
1420                         ...)
1421
1422这是在 CMake 中手动列出源文件的 `最佳实践 <https://gist.github.com/mbinna/c61dbb39bca0e4fb7d1f73b0d66a4fd1/>`_。然而,当有许多源文件都需要添加到构建中时,这种方法就会很不方便。ESP-IDF 构建系统因此提供了另一种替代方法,即使用 ``SRC_DIRS`` 来指定源文件::
1423
1424  idf_component_register(SRC_DIRS library platform
1425                         ...)
1426
1427后台会使用通配符在指定的目录中查找源文件。但是请注意,在使用这种方法的时候,如果组件中添加了一个新的源文件,CMake 并不知道重新运行配置,最终该文件也没有被加入构建中。
1428
1429如果是自己添加的源文件,这种折衷还是可以接受的,因为用户可以触发一次干净的构建,或者运行 ``idf.py reconfigure`` 来手动重启 CMake_。但是,如果你需要与其他使用 Git 等版本控制工具的开发人员共享项目时,问题就会变得更加困难,因为开发人员有可能会拉取新的版本。
1430
1431ESP-IDF 中的组件使用了第三方的 Git CMake 集成模块(:idf_file:`/tools/cmake/third_party/GetGitRevisionDescription.cmake`),任何时候源码仓库的提交记录发生了改变,该模块就会自动重新运行 CMake。即只要拉取了新的 ESP-IDF 版本,CMake 就会重新运行。
1432
1433对于不属于 ESP-IDF 的项目组件,有以下几个选项供参考:
1434
1435- 如果项目文件保存在 Git 中,ESP-IDF 会自动跟踪 Git 修订版本,并在它发生变化时重新运行 CMake。
1436- 如果一些组件保存在第三方 Git 仓库中(不在项目仓库或 ESP-IDF 仓库),则可以在组件 CMakeLists 文件中调用 ``git_describe`` 函数,以便在 Git 修订版本发生变化时自动重启 CMake。
1437- 如果没有使用 Git,请记住在源文件发生变化时手动运行 ``idf.py reconfigure``。
1438- 使用 ``idf_component_register`` 的 ``SRCS`` 参数来列出项目组件中的所有源文件则可以完全避免这一问题。
1439
1440具体选择哪一方式,就要取决于项目本身,以及项目用户。
1441
1442.. _build_system_metadata:
1443
1444构建系统的元数据
1445================
1446
1447为了将 ESP-IDF 集成到 IDE 或者其它构建系统中,CMake 在构建的过程中会在 ``build/`` 目录下生成大量元数据文件。运行 ``cmake`` 或 ``idf.py reconfigure`` (或任何其它 ``idf.py`` 构建命令),可以重新生成这些元数据文件。
1448
1449- ``compile_commands.json`` 是标准格式的 JSON 文件,它描述了在项目中参与编译的每个源文件。CMake 其中的一个功能就是生成此文件,许多 IDE 都知道如何解析此文件。
1450- ``project_description.json`` 包含有关 ESP-IDF 项目、已配置路径等的一些常规信息。
1451- ``flasher_args.json`` 包含 esptool.py 工具用于烧录项目二进制文件的参数,此外还有 ``flash_*_args`` 文件,可直接与 esptool.py 一起使用。更多详细信息请参阅 :ref:`flash_parameters`。
1452- ``CMakeCache.txt`` 是 CMake 的缓存文件,包含 CMake 进程、工具链等其它信息。
1453- ``config/sdkconfig.json`` 包含 JSON 格式的项目配置结果。
1454- ``config/kconfig_menus.json`` 是在 menuconfig 中显示菜单的 JSON 格式版本,用于外部 IDE 的 UI。
1455
1456JSON 配置服务器
1457---------------
1458
1459.. highlight :: json
1460
1461``confserver.py`` 工具可以帮助 IDE 轻松地与配置系统的逻辑进行集成,它运行在后台,通过使用 stdin 和 stdout 读写 JSON 文件的方式与调用进程交互。
1462
1463您可以通过 ``idf.py confserver`` 或 ``ninja confserver`` 从项目中运行 ``confserver.py``,也可以使用不同的构建生成器来触发类似的目标。
1464
1465有关 confserver.py 的更多信息,请参阅 :idf_file:`tools/kconfig_new/README.md`
1466
1467构建系统内部
1468=======================
1469
1470构建脚本
1471-------------
1472
1473ESP-IDF 构建系统的列表文件位于 :idf:`/tools/cmake` 中。实现构建系统核心功能的模块如下
1474
1475    - build.cmake - 构建相关命令,即构建初始化、检索/设置构建属性、构建处理。
1476    - component.cmake - 组件相关的命令,如添加组件、检索/设置组件属性、注册组件。
1477    - kconfig.cmake - 从 Kconfig 文件中生成配置文件(sdkconfig、sdkconfig.hsdkconfig.cmake 等)。
1478    - ldgen.cmake - 从链接器片段文件生成最终链接器脚本。
1479    - target.cmake - 设置构建目标和工具链文件。
1480    - utilities.cmake - 其它帮助命令。
1481
1482 除了这些文件,还有两个重要的 CMake 脚本在 :idf:`/tools/cmake` 中:
1483
1484    - idf.cmake - 设置构建参数并导入上面列出的核心模块。之所以包括在 CMake 项目中,是为了方便访问 ESP-IDF 构建系统功能。
1485    - project.cmake - 导入 ``idf.cmake``,并提供了一个自定义的``project()``命令,该命令负责处理建立可执行文件时所有的繁重工作。包含在标准 ESP-IDF 项目的顶层 CMakeLists.txt 中。
1486
1487:idf:`/tools/cmake` 中的其它文件都是构建过程中的支持性文件或第三方脚本。
1488
1489构建过程
1490-------------
1491
1492本节介绍了标准的 ESP-IDF 应用构建过程。构建过程可以大致分为四个阶段:
1493
1494.. blockdiag::
1495    :scale: 100%
1496    :caption: ESP-IDF Build System Process
1497    :align: center
1498
1499    blockdiag idf-build-system-process {
1500        初始化 -> 枚举
1501        枚举 -> 处理
1502        处理 -> 完成
1503    }
1504
1505初始化
1506^^^^^^^
1507
1508 该阶段为构建设置必要的参数。
1509
1510    - 在将 ``idf.cmake`` 导入 ``project.cmake`` 后,将执行以下步骤:
1511        - 在环境变量中设置 ``IDF_PATH`` 或从顶层 CMakeLists.txt 中包含的 ``project.cmake`` 路径推断相对路径。
1512        - 将 :idf:`/tools/cmake` 添加到 ``CMAKE_MODULE_PATH`` 中,并导入核心模块和各种辅助/第三方脚本。
1513        - 设置构建工具/可执行文件,如默认的 Python 解释器。
1514        - 获取 ESP-IDF git 修订版,并存储为 ``IDF_VER``。
1515        - 设置全局构建参数,即编译选项、编译定义、包括所有组件的 include 目录。
1516        - 将 :idf:`components` 中的组件添加到构建中。
1517    - 自定义 ``project()`` 命令的初始部分执行以下步骤:
1518        - 在环境变量或 CMake 缓存中设置 ``IDF_TARGET`` 以及设置相应要使用的``CMAKE_TOOLCHAIN_FILE``。
1519        - 添加 ``EXTRA_COMPONENTS_DIRS`` 中的组件至构建中
1520        - 从 ``COMPONENTS``/``EXCLUDE_COMPONENTS``、``SDKCONFIG``、``SDKCONFIG_DEFAULTS`` 等变量中为调用命令 ``idf_build_process()`` 准备参数。
1521
1522调用 ``idf_build_process()`` 命令标志着这个阶段的结束。
1523
1524枚举
1525^^^^^^^^^^^
1526  这个阶段会建立一个需要在构建过程中处理的组件列表,该阶段在 ``idf_build_process()`` 的前半部分进行。
1527
1528    - 检索每个组件的公共和私有依赖。创建一个子进程,以脚本模式执行每个组件的 CMakeLists.txt。``idf_component_register`` REQUIRES 和 PRIV_REQUIRES 参数的值会返回给父进程。这就是所谓的早期扩展。在这一步中定义变量 ``CMAKE_BUILD_EARLY_EXPANSION``。
1529    - 根据公共和私有的依赖关系,递归地导入各个组件。
1530
1531处理
1532^^^^^^^
1533
1534  该阶段处理构建中的组件,是 ``idf_build_process()`` 的后半部分。
1535
1536  - 从 sdkconfig 文件中加载项目配置,并生成 sdkconfig.cmakesdkconfig.h 头文件。这两个文件分别定义了可以从构建脚本和 C/C++ 源文件/头文件中访问的配置变量/宏。
1537  - 导入各组件的 ``project_include.cmake``。
1538  - 将每个组件添加为一个子目录,处理其 CMakeLists.txt。组件 CMakeLists.txt 调用注册命令 ``idf_component_register`` 添加源文件、导入目录、创建组件库、链接依赖关系等。
1539
1540完成
1541^^^^^^^
1542  该阶段是 ``idf_build_process()`` 剩余的步骤。
1543
1544  - 创建可执行文件并将其链接到组件库中。
1545  - 生成 project_description.json 等项目元数据文件并且显示所建项目等相关信息。
1546
1547请参考 :idf_file:`/tools/cmake/project.cmake` 获取更多信息。
1548
1549从 ESP-IDF GNU Make 构建系统迁移到 CMake 构建系统
1550=================================================
1551
1552ESP-IDF CMake 构建系统与旧版的 GNU Make 构建系统在某些方面非常相似,开发者都需要提供 include 目录、源文件等。然而,有一个语法上的区别,即对于 ESP-IDF CMake 构建系统,开发者需要将这些作为参数传递给注册命令 ``idf_component_register``。
1553
1554自动转换工具
1555------------
1556
1557.. highlight:: bash
1558
1559:idf_file:`/tools/cmake/convert_to_cmake.py` 中提供了一个项目自动转换工具。运行此命令时需要加上项目路径,如下所示::
1560
1561    $IDF_PATH/tools/cmake/convert_to_cmake.py /path/to/project_dir
1562
1563项目目录必须包含 Makefile 文件,并确保主机已安装 GNU Make (``make``) 工具,并且被添加到了 PATH 环境变量中。
1564
1565该工具会将项目 Makefile 文件和所有组件的 ``component.mk`` 文件转换为对应的 ``CMakeLists.txt`` 文件。
1566
1567转换过程如下:该工具首先运行 ``make`` 来展开 ESP-IDF 构建系统设置的变量,然后创建相应的 CMakelists 文件来设置相同的变量。
1568
1569.. important:: 当转换工具转换一个 ``component.mk`` 文件时,它并不能确定该组件依赖于哪些其他组件。这些信息需要通过编辑新的组件 ``CMakeLists.txt`` 文件并添加 ``REQUIRES`` 和/或 ``PRIV_REQUIRES`` 子句来手动添加。否则,组件中的源文件会因为找不到其他组件的头文件而编译失败。请参考 :ref:`component requirements` 获取更多信息。
1570
1571转换工具并不能处理复杂的 Makefile 逻辑或异常的目标,这些需要手动转换。
1572
1573CMake 中不可用的功能
1574--------------------
1575
1576有些功能已从 CMake 构建系统中移除,或者已经发生很大改变。GNU Make 构建系统中的以下变量已从 CMake 构建系统中删除:
1577
1578- ``COMPONENT_BUILD_DIR``:由 ``CMAKE_CURRENT_BINARY_DIR`` 替代。
1579- ``COMPONENT_LIBRARY``:默认为 ``$(COMPONENT_NAME).a`` 但是库名可以被组件覆盖。在 CMake 构建系统中,组件库名称不可再被组件覆盖。
1580- ``CC``、``LD``、``AR``、``OBJCOPY``:gcc xtensa 交叉工具链中每个工具的完整路径。CMake 使用 ``CMAKE_C_COMPILER``、``CMAKE_C_LINK_EXECUTABLE`` 和 ``CMAKE_OBJCOPY`` 进行替代。完整列表请参阅 `CMake 语言变量 <cmake language variables_>`_。
1581- ``HOSTCC``、``HOSTLD``、``HOSTAR``:宿主机本地工具链中每个工具的全名。CMake 系统不再提供此变量,外部项目需要手动检测所需的宿主机工具链。
1582- ``COMPONENT_ADD_LDFLAGS``:用于覆盖链接标志。CMake 中使用 `target_link_libraries`_ 命令替代。
1583- ``COMPONENT_ADD_LINKER_DEPS``:链接过程依赖的文件列表。`target_link_libraries`_ 通常会自动推断这些依赖。对于链接脚本,可以使用自定义的 CMake 函数 ``target_linker_scripts``。
1584- ``COMPONENT_SUBMODULES``:不再使用。CMake 会自动枚举 ESP-IDF 仓库中所有的子模块。
1585- ``COMPONENT_EXTRA_INCLUDES``:曾是 ``COMPONENT_PRIV_INCLUDEDIRS`` 变量的替代版本,仅支持绝对路径。CMake 系统中统一使用 ``COMPONENT_PRIV_INCLUDEDIRS`` (可以是相对路径,也可以是绝对路径)。
1586- ``COMPONENT_OBJS``:以前,可以以目标文件列表的方式指定组件源,现在,可以通过 ``COMPONENT_SRCS`` 以源文件列表的形式指定组件源。
1587- ``COMPONENT_OBJEXCLUDE``:已被 ``COMPONENT_SRCEXCLUDE`` 替换。用于指定源文件(绝对路径或组件目录的相对路径)。
1588- ``COMPONENT_EXTRA_CLEAN``:已被 ``ADDITIONAL_MAKE_CLEAN_FILES`` 属性取代,注意,:ref:`CMake 对此项功能有部分限制 <ADDITIONAL_MAKE_CLEAN_FILES_note>`。
1589- ``COMPONENT_OWNBUILDTARGET`` & ``COMPONENT_OWNCLEANTARGET``:已被 CMake `外部项目 <ExternalProject>`_ 替代,详细内容请参阅 :ref:`component-build-full-override`。
1590- ``COMPONENT_CONFIG_ONLY``:已被 ``register_config_only_component()`` 函数替代,请参阅 :ref:`config_only_component`。
1591- ``CFLAGS``、``CPPFLAGS``、``CXXFLAGS``:已被相应的 CMake 命令替代,请参阅 :ref:`component_build_control`。
1592
1593无默认值的变量
1594--------------
1595
1596以下变量不再具有默认值:
1597
1598- 源目录(Make 中的 ``COMPONENT_SRCDIRS`` 变量,CMake 中 ``idf_component_register`` 的 ``SRC_DIRS`` 参数)
1599- include 目录(Make中的 ``COMPONENT_ADD_INCLUDEDIRS`` 变量,CMake中 ``idf_component_register`` 的 ``INCLUDE_DIRS`` 参数)
1600
1601不再需要的变量
1602--------------
1603
1604在 CMake 构建系统中,如果设置了 ``COMPONENT_SRCS``,就不需要再设置 ``COMPONENT_SRCDIRS``。实际上,CMake 构建系统中如果设置了 ``COMPONENT_SRCDIRS``,那么 ``COMPONENT_SRCS`` 就会被忽略。
1605
1606从 Make 中烧录
1607--------------
1608
1609仍然可以使用 ``make flash`` 或者类似的目标来构建和烧录,但是项目 ``sdkconfig`` 不能再用来指定串口和波特率。可以使用环境变量来覆盖串口和波特率的设置,详情请参阅 :ref:`flash-with-ninja-or-make`。
1610
1611.. _esp-idf-template: https://github.com/espressif/esp-idf-template
1612.. _Cmake: https://cmake.org
1613.. _ninja: https://ninja-build.org
1614.. _esptool.py: https://github.com/espressif/esptool/#readme
1615.. _CMake v3.5 官方文档: https://cmake.org/cmake/help/v3.5/index.html
1616.. _cmake 命令行文档: https://cmake.org/cmake/help/v3.5/manual/cmake.1.html#options
1617.. _cmake add_library: https://cmake.org/cmake/help/v3.5/command/add_library.html
1618.. _cmake if: https://cmake.org/cmake/help/v3.5/command/if.html
1619.. _cmake list: https://cmake.org/cmake/help/v3.5/command/list.html
1620.. _cmake project: https://cmake.org/cmake/help/v3.5/command/project.html
1621.. _cmake set: https://cmake.org/cmake/help/v3.5/command/set.html
1622.. _cmake string: https://cmake.org/cmake/help/v3.5/command/string.html
1623.. _cmake faq generated files: https://gitlab.kitware.com/cmake/community/-/wikis/FAQ#how-can-i-generate-a-source-file-during-the-build
1624.. _ADDITIONAL_MAKE_CLEAN_FILES: https://cmake.org/cmake/help/v3.5/prop_dir/ADDITIONAL_MAKE_CLEAN_FILES.html
1625.. _ExternalProject: https://cmake.org/cmake/help/v3.5/module/ExternalProject.html
1626.. _cmake language variables: https://cmake.org/cmake/help/v3.5/manual/cmake-variables.7.html#variables-for-languages
1627.. _set_source_files_properties: https://cmake.org/cmake/help/v3.5/command/set_source_files_properties.html
1628.. _target_compile_options: https://cmake.org/cmake/help/v3.5/command/target_compile_options.html
1629.. _target_link_libraries: https://cmake.org/cmake/help/v3.5/command/target_link_libraries.html#command:target_link_libraries
1630.. _cmake_toolchain_file: https://cmake.org/cmake/help/v3.5/variable/CMAKE_TOOLCHAIN_FILE.html
1631.. _LINK_INTERFACE_MULTIPLICITY: https://cmake.org/cmake/help/v3.5/prop_tgt/LINK_INTERFACE_MULTIPLICITY.html
1632.. _quirc: https://github.com/dlbeer/quirc
1633.. _pyenv: https://github.com/pyenv/pyenv#readme
1634.. _virtualenv: https://virtualenv.pypa.io/en/stable/
1635.. _CCache: https://ccache.dev/
1636