前一篇
讨论了 Make 变量从哪里来、怎样覆盖。接下来还要弄清楚:配置文件中的开关,怎样影响 C 源码里的 #ifdef?
一、从变量到宏的传递过程
| 程序 | 主要读取什么 | 在构建中负责什么 |
|---|---|---|
| Make | Makefile、.mk 文件 | 根据配置安排构建,并组织传给编译器的参数 |
| C 编译器 | .c、.h 等源码文件 | 根据宏和源码内容,生成目标文件 |
写在
.mk文件中的变量,是给 Make 使用的。C 编译器不会自动读取这些变量。
先用一个简单的 MY_FEATURE 开关说明。
1.1 定义 Make 变量
假设 .mk 文件里写了:
MY_FEATURE = 1
MY_FEATURE 是 Make 变量,Make 用 $(MY_FEATURE) 取值。例如,判断这个开关是否等于 1:
ifeq ($(MY_FEATURE),1)
# 在这里安排开启功能时的构建操作
endif
1.2 export:传递给子 Make
如果顶层 Make 另外启动子 Make 来构建模块,后面的子 Make 也可能需要这个开关。这时可以前面加 export 将变量传过去:
export MY_FEATURE = 1
export 会把变量带给后续子进程,子 Make 因而也能读取 MY_FEATURE。
这里说的子 Make,是另外启动的 Make 进程。如果是同一个 Make, 通过 include 读入多个配置文件时,变量可以在该进程内使用,无需为了 include 单独加 export。
注意:变量传到了子 Make,C 编译器仍然不会自动得到同名宏,所以 cpp文件仍然无法使用mk 中定义的宏
1.3 -D:定义编译宏
要让 .mk 中的开关影响 C 编译,需要把对应的宏定义选项加入模块编译参数。这个 SDK 会用 ccflags-y 收集这些参数,例如:
ifeq ($(MY_FEATURE),1)
ccflags-y += -DMY_FEATURE
endif
开关为 1 时,模块参数 ccflags-y 就会多一个 -DMY_FEATURE。
这里的 -D 才是编译器识别的宏定义选项。-DMY_FEATURE 表示本次编译时定义 MY_FEATURE,效果相当于:
#define MY_FEATURE 1
ccflags-y 是这套 SDK 用来收集模块编译参数的变量;构建规则会把它的内容并入编译命令,再交给编译器。实际命令可以简化看成下面这样:
arm-none-eabi-gcc -DMY_FEATURE -c demo.c -o demo.o
其中 demo.c 是输入源码,demo.o 是编译生成的目标文件。编译器处理 demo.c 时会带着 MY_FEATURE 这个宏,C 源码就可以用条件编译判断是否保留相关代码:
#ifdef MY_FEATURE
/* 这部分功能代码参与编译 */
#endif
%%{init: {"flowchart": {"htmlLabels": true, "wrappingWidth": 1000, "rankSpacing": 30}, "themeVariables": {"lineColor": "#71829a"}}}%%
flowchart LR
N0["Make 变量<br/>MY_FEATURE=1"]
N1["模块添加<br/>-DMY_FEATURE"]
N2["条件分支参与编译<br/>#ifdef MY_FEATURE"]
N0 -->|"ifeq 条件成立"| N1
N1 -->|"编译器定义宏"| N2
class N0,N1,N2 blue;
classDef blue fill:#e8f2ff,stroke:#7ba4d8,color:#23456b,stroke-width:1.5px;
classDef green fill:#e7f4ec,stroke:#78aa8b,color:#24563a,stroke-width:1.5px;
classDef amber fill:#fff3df,stroke:#d5a25b,color:#72501e,stroke-width:1.5px;二、在 BES 中追踪开关与宏的对应关系
回到工程里的 EQ 开关,Make 变量和 C 宏用的是两个名字:
图 1:audio_process/Makefile 第 113~114 行添加编译宏
第 113 行判断 HW_DAC_IIR_EQ_PROCESS,第 114 行添加 -D__HW_DAC_IIR_EQ_PROCESS__。开关为 1,编译器就会定义后一个宏。
C 源码中的 #ifdef此时就要用后一个名字,Makefile 只是负责把两边对应起来。
三、检查配置是否生效
这里要确认的是:前面加到 ccflags-y 的 -D__HW_DAC_IIR_EQ_PROCESS__,有没有真的出现在编译 audio_process.c 的命令里。
执行下面的构建命令:
make T=best1306p V=1 -j8
其中,T=best1306p 选择本次构建目标;V=1 让构建过程打印完整的编译命令;-j8 表示并行使用 8 个任务,只影响构建速度。
在输出中找到编译 audio_process.c 的那条命令,确认里面有 -D__HW_DAC_IIR_EQ_PROCESS__。省略其他编译参数后,大致会看到:
arm-none-eabi-gcc ... -D__HW_DAC_IIR_EQ_PROCESS__ ... -c audio_process.c -o audio_process.o
判断时看这三种情况:
- 有
audio_process.c的编译命令,且带有-D__HW_DAC_IIR_EQ_PROCESS__:宏参与了这次编译。 - 有该文件的编译命令,但没有这个
-D参数:这条配置添加的宏参数没有传入本次编译。 - 没有该文件的编译命令:这次构建没有重新编译
audio_process.c。增量构建会跳过输入文件和编译命令都没有变化的目标;
如果想从头编译 best1306p 这个目标,可以先清理它的构建输出,再重新编译:
make T=best1306p allclean
make T=best1306p V=1 -j8
allclean 会删除 out/best1306p 中该目标的生成文件,第二条命令会重新构建。完整构建会比增量构建花更长时间,V=1 也会打印大量编译命令。
这一步只能确认宏参数进入了编译命令。要确认功能在设备上实际运行,还要烧录本次生成的文件,再结合初始化、触发条件和设备日志判断。