最早接触到Kconfig
是在zephyr
项目中,之后陆续知道linux
和RT-Thread
等项目都是用Kconfig
来管理编译的,而自己也陆续有大型项目开发需要,了解过后对其使用愈发感兴趣起来。
在实际项目开发中,通常会有需要去使能/关闭一些代码模块或者修改一些配置参数。
项目代码都放在GitHub上,bobwenstudy/test_kconfig_system (github.com)
使能或者关闭代码,可以通过#define+#ifdef
就可以实现这一目的。
#define CONFIG_TEST_ENABLE
#ifdef CONFIG_TEST_ENABLE
... ... ...
#endif
调整配置参数,直接通过#define
就可以实现这一目的。
#define CONFIG_TEST_SHOW_STRING "Test 123"
#define CONFIG_TEST_SHOW_INT (123)
但是通过这个方式有个问题,就是不直观,如果就几个参数还好,但是当参数越来越多,并之前存在先后关系时,管理难度会呈指数上升。
如下面的例子,CONFIG_TEST_SUB_1_ENABLE
的开启前提是CONFIG_TEST_TOP_ENABLE
开启,当CONFIG_TEST_SUB_0_ENABLE
和CONFIG_TEST_SUB_1_ENABLE
都开启的情况下CONFIG_TEST_SHOW_INT = 123
,否则CONFIG_TEST_SHOW_INT = 456
。
这些宏之间的关系都用代码来描述,需要开发人员熟悉所有代码行为,才能很好的配置这些功能。
#define CONFIG_TEST_TOP_ENABLE
#define CONFIG_TEST_SUB_0_ENABLE
#ifdef CONFIG_TEST_TOP_ENABLE
#define CONFIG_TEST_SUB_1_ENABLE
#endif
#if defined(CONFIG_TEST_SUB_0_ENABLE) && defined(CONFIG_TEST_SUB_1_ENABLE)
#define CONFIG_TEST_SHOW_SUB_INT (123)
#else
#define CONFIG_TEST_SHOW_SUB_INT (456)
#endif
除了在代码中配置外,也可以通过-D预编译来管理,然后编译全局都有这些配置参数了,对应代码中的宏定义。
gcc xxx -DCONFIG_TEST_ENABLE -DCONFIG_TEST_SHOW_INIT=123
#define CONFIG_TEST_ENABLE
#define CONFIG_TEST_SHOW_INIT (123)
如果是小型项目通过上述两种方式管理都还好,当项目越来越大时,所需配置的参数越来越多时,希望有一个工具专门来管理这些配置参数,并且能够可视化这些编译配置。
而Kconfig就是这样一个通用的工具来解决这一问题。
Kconfig
是一个配置描述文件,而其配置界面程序需要通过GUI来完成,不然看到的只是一些文本文件,并且这个文件并不能被c所识别。
这里推荐用python的kconfiglib · PyPI库来使用。
直接python安装即可。由于要使用menuconfig,在windows环境下还需要安装windows-curses
。
python -m pip install windows-curses
python -m pip install kconfiglib
安装完成后,会在python路径下生成menuconfig.exe
执行程序。输入menuconfig -h
,出现下面的信息说明已经安装好了。
使用Kconfiglib
生成的是.config
文件,而c代码要使用,必须要提供头文件。其实生成功能主要还是依靠kconfiglib
中提供的class Kconfig
中的write_autoconf
方法,但是也需要一个脚本来调用这个库。
源码所在路径为:Kconfiglib/kconfiglib.py at master · ulfalizer/Kconfiglib (github.com)
kconfig 实例1: 基于 python 的 kconfig 系统 - 简书 (jianshu.com)这个里面提供了一个简单的实现版本,但是有点太简单了,不符合我们实际项目的需要。
这里还是推荐用zephyr项目的版本:zephyr/kconfig.py at main · zephyrproject-rtos/zephyr (github.com)。这个使用需要输入如下参数:
- kconfig_file,顶层的Kconfig配置文件路径
- config_out,输出.config文件路径
- header_out,输出c头文件路径
- kconfig_list_out,日志文件
- configs_in,1个或多个输入.config文件
这里调试用的是windows环境,直接自行下载msys2+mingw即可。
https://blog.csdn.net/qq_31985307/article/details/114235846
Kconfig
语言定义了一套完整的规则来表述配置项及配置项间的关系。
之前安装的Kconfiglib
只是用来显示Kconfig的,实际工程中通常会包含Kconfig
和.config
文件。
- Kconfig,是配置项的描述文件,支持设置配置项及其默认值,依赖关系等等,该文件还会继续依赖各个模块的Kconfig文件。
- .config,产品配置文件,提供配置项及在产品中这些配置项的设置值,可能和Kconfig配置项的默认取值不一致,属于产品对配置项的定制。这些配置文件在可以在makefile文件中使用。
- autoconfig.h,生成的C语言头文件,提供配置项的宏定义版,在C语言程序中使用。
由于本文重点是讲Kconfig环境的搭建,所以语法就不展开。
linux的原版可以看这个:Kconfig Language — The Linux Kernel documentation
zephyr项目的介绍:Configuration System (Kconfig) — Zephyr Project Documentation
中文的一些说明:
Kconfig 语法 - fluidog - 博客园 (cnblogs.com)
kconfig语法整理 - 简书 (jianshu.com)
这里围绕实际使用的几个场景来进行说明
需要提供main.c,Makefile,Kconfig。下面分别进行描述:
这里我们将最开始背景说明中的宏定义配置改成Kconfig写法。
mainmenu "Kconfig Demo"
menu "Test Params setting"
config TEST_ENABLE
bool "Enable test work"
default n
help
Will print debug information if enable.
config TEST_SHOW_STRING
string "The show string info"
default "Test 123"
config TEST_SHOW_INT
int "The show int info"
range 0 255
default 123
config TEST_TOP_ENABLE
bool "Test Top Func"
default n
help
Function Test Top
config TEST_SUB_0_ENABLE
bool "Test Sub 0 Func"
default n
help
Function Test Sub 0
config TEST_SUB_1_ENABLE
bool "Test Sub 1 Func"
default n
depends on TEST_TOP_ENABLE
help
Function Test Sub 1
config TEST_SHOW_SUB_INT
int
default 456 if TEST_SUB_0_ENABLE && TEST_SUB_1_ENABLE
default 123
endmenu
相比于传统的makefile,加入了autoconfig.h
、.config
和menuconfig
编译目标。
menuconfig,用于显示menuconfig页面,让用户通过GUI选择当前配置参数。
.config,如果用户第一次没有.config
文件时,调用menuconfig
来配置Kconfig
并生成.config
。
autoconfig.h,如果.config
有更新就需要执行,实际是调用kconfig.py
脚本来生成autoconfig.h
头文件。
all: main.o
gcc main.o -o main
main.o: main.c autoconfig.h
gcc main.c -c -o main.o
clean:
del main.o main.exe
autoconfig.h:.config
python ../scripts/kconfig.py Kconfig .config autoconfig.h log.txt .config
.config:
menuconfig
menuconfig:
menuconfig
比较简单,一个是引用生成的autoconfig.h头文件,然后再根据不同的配置打印当前的信息
#include <stdio.h>
#include "autoconfig.h"
int main()
{
printf("hello, world\n");
#ifdef CONFIG_TEST_ENABLE
printf("CONFIG_TEST_ENABLE\n");
#endif
printf("CONFIG_TEST_SHOW_STRING: %s\n", CONFIG_TEST_SHOW_STRING);
printf("CONFIG_TEST_SHOW_INT: %d\n", CONFIG_TEST_SHOW_INT);
#ifdef CONFIG_TEST_TOP_ENABLE
printf("CONFIG_TEST_TOP_ENABLE\n");
#endif
#ifdef CONFIG_TEST_SUB_0_ENABLE
printf("CONFIG_TEST_SUB_0_ENABLE\n");
#endif
#ifdef CONFIG_TEST_SUB_1_ENABLE
printf("CONFIG_TEST_SUB_1_ENABLE\n");
#endif
printf("CONFIG_TEST_SHOW_SUB_INT: %d\n", CONFIG_TEST_SHOW_SUB_INT);
return 0;
}
直接键入make all
,由于初始环境没有.config文件,会调用menuconfig进行配置生成.config文件。
如下图配置完成后输入Q,会提示保存.config
文件,直接保存即可。
有.config
文件后,就会执行python脚本生成autoconfig.h
文件,最后调用gcc
生成main.exe
。
执行编译之后,生成的autoconfig.h
文件信息如下。如上图所示,main.exe
的执行结果和锁配置的autoconfig.h
参数相同。
#define CONFIG_TEST_ENABLE 1
#define CONFIG_TEST_SHOW_STRING "Test 567"
#define CONFIG_TEST_SHOW_INT 123
#define CONFIG_TEST_TOP_ENABLE 1
#define CONFIG_TEST_SHOW_SUB_INT 123
生成的.config
文件如下,可以看到默认值通过#
注释了,其他就是我们在menuconfig
所指定的值。
#
# Test Params setting
#
CONFIG_TEST_ENABLE=y
CONFIG_TEST_SHOW_STRING="Test 567"
CONFIG_TEST_SHOW_INT=123
CONFIG_TEST_TOP_ENABLE=y
# CONFIG_TEST_SUB_0_ENABLE is not set
# CONFIG_TEST_SUB_1_ENABLE is not set
CONFIG_TEST_SHOW_SUB_INT=123
# end of Test Params setting
如上图所示的执行完程序之后,生成了很多中间文件,和目标文件。真正有用的是.config
和autoconfig.h
文件。
第一次编译完毕以后,之后我们想修改参数可以通过输入make menuconfig
进配置页面,需要注意的是,这时候打开时,不再是默认值,而是我们之前调整后的值。
按照如下配置后。
修改后,再键入make all
,由于autoconfig.h
所依赖的.config
变化了,会触发再次运行python脚本生成autoconfig.h
,再生成main.exe
。
生成的autoconfig.h
文件如下所示,从上图的执行结果可以看出行为一致。
#define CONFIG_TEST_ENABLE 1
#define CONFIG_TEST_SHOW_STRING "Test 567"
#define CONFIG_TEST_SHOW_INT 78
#define CONFIG_TEST_TOP_ENABLE 1
#define CONFIG_TEST_SUB_0_ENABLE 1
#define CONFIG_TEST_SUB_1_ENABLE 1
#define CONFIG_TEST_SHOW_SUB_INT 456
生成的.config
文件如下所示,和我们GUI配置的参数一致。
#
# Test Params setting
#
CONFIG_TEST_ENABLE=y
CONFIG_TEST_SHOW_STRING="Test 567"
CONFIG_TEST_SHOW_INT=78
CONFIG_TEST_TOP_ENABLE=y
CONFIG_TEST_SUB_0_ENABLE=y
CONFIG_TEST_SUB_1_ENABLE=y
CONFIG_TEST_SHOW_SUB_INT=456
# end of Test Params setting
上述的方案算是一个最常用的方式了,发布的时候只提供Kconfig
,.config
为默认值生成的,用户需要改的时候自己通过make menuconfig
来修改,以便生成不同的应用程序。
这个方式已经可以满足绝大数应用场景的需要了。
虽然上述版本已经满足大多数场景需要了,但是对于像Zephyr这种项目,项目非常大,有多个驱动,并且其会提供很多例程,这些例程都是预先配置好参数给客户直接编译下载的。
不同例程之间所使用的参数各不相同,而且还想让客户可以通过GUI调整某个例程的参数信息,这时候如何管理呢。
这对应芯片厂来讲是一个很普遍的需求,芯片厂所提供的SDK一般包含多个例程,不同例程公用驱动库,只是所使用参数不同。
Zephyr原文:Configuration System (Kconfig) — Zephyr Project Documentation
Zephyr持久化方案实现代码:zephyr/kconfig.cmake at main · zephyrproject-rtos/zephyr (github.com)
好的Zephyr Kconfig使用思路文档:
- Zephyr Devicetree 与 Kconfig 配置指南 — PAN1080 DK Documentation (panchip.com)
- Zephyr-系统配置(Kconfig)_只想.静静的博客-CSDN博客_zephyr如何配置
Zephyr项目有点大,他的一些理念可以学习,但本文会给大家准备一个精简版的例程,借鉴其思路来实现类似其工作效果(学习zephyr可以参考下)。
假定有2个例程,每个例程有一个main函数,共同使用一个模块Test。Makefile通过APP参数来指定不同的编译目标,每个应用有其配置参数。项目的目录结构如下图所示。
-
app,应用路径,下面包含2个例程,分别为test1和test2。每个应用有其独立的配置参数prj.conf。
-
driver,公用的驱动路径,例程会调用这个驱动。
-
output,输出路径,生成的一些文件都放在这,.o为了省事就不放了。
-
Kconfig,kconfig的配置文件,和上一个一样。
-
Makefile,makefile文件。
main.c文件比较简单,就是打印一个printf,而后就是调用驱动库的函数。
#include <stdio.h>
#include "driver_test.h"
int main()
{
printf("hello, test1\n");
test_driver();
return 0;
}
prj.conf文件设定了针对test1的配置参数
CONFIG_TEST_ENABLE=y
CONFIG_TEST_SHOW_STRING="Test 444"
main.c
文件比较简单,就是打印一个printf(注意:输出是hello, test2
),而后就是调用驱动库的函数。
#include <stdio.h>
#include "driver_test.h"
int main()
{
printf("hello, test2\n");
test_driver();
return 0;
}
prj.conf
文件设定了针对test2的配置参数
CONFIG_TEST_TOP_ENABLE=y
CONFIG_TEST_SUB_0_ENABLE=y
CONFIG_TEST_SUB_1_ENABLE=n
其实和上一个例程差不多,就是将配置参数打印出来。
#include <stdio.h>
#include "autoconfig.h"
void test_driver()
{
#ifdef CONFIG_TEST_ENABLE
printf("CONFIG_TEST_ENABLE\n");
#endif
printf("CONFIG_TEST_SHOW_STRING: %s\n", CONFIG_TEST_SHOW_STRING);
printf("CONFIG_TEST_SHOW_INT: %d\n", CONFIG_TEST_SHOW_INT);
#ifdef CONFIG_TEST_TOP_ENABLE
printf("CONFIG_TEST_TOP_ENABLE\n");
#endif
#ifdef CONFIG_TEST_SUB_0_ENABLE
printf("CONFIG_TEST_SUB_0_ENABLE\n");
#endif
#ifdef CONFIG_TEST_SUB_1_ENABLE
printf("CONFIG_TEST_SUB_1_ENABLE\n");
#endif
printf("CONFIG_TEST_SHOW_SUB_INT: %d\n", CONFIG_TEST_SHOW_SUB_INT);
}
没什么东西,就是一个函数声明罢了。
#ifndef _DRIVER_TEST_H_
#define _DRIVER_TEST_H_
void test_driver(void);
#endif //_DRIVER_TEST_H_
和之前一模一样,就不重复占页数了。
整个文件如下所示,相比上一个东西多了不少,需要大家具备一定的makefile的功底,下面简单进行分析。
APP ?= app/test1
OUTPUT_PATH := output
INCLUDE_PATH := -I$(APP) -Idriver -I$(OUTPUT_PATH)
# define user .config setting
USER_CONFIG_SET :=
USER_CONFIG_SET += $(APP)/prj.conf
# define menuconfig .config path
DOTCONFIG_PATH := $(OUTPUT_PATH)/.config
# define user merged path
USER_RECORD_CONFIG_PATH := $(OUTPUT_PATH)/user_record.conf
# define autoconfig.h path
AUTOCONFIG_H := $(OUTPUT_PATH)/autoconfig.h
#define Kconfig path
KCONFIG_ROOT_PATH := Kconfig
#For windows work.
FIXPATH = $(subst /,\,$1)
all: $(APP)/main.o driver/driver_test.o
gcc $^ -o $(OUTPUT_PATH)/main.exe
$(APP)/main.o: $(APP)/main.c
gcc $< $(INCLUDE_PATH) -c -o $@
driver/driver_test.o: driver/driver_test.c $(AUTOCONFIG_H)
gcc $< $(INCLUDE_PATH) -c -o $@
clean:
del /q /s $(call FIXPATH, $(APP)/main.o driver/driver_test.o $(OUTPUT_PATH))
$(AUTOCONFIG_H):$(DOTCONFIG_PATH)
python ../scripts/kconfig.py $(KCONFIG_ROOT_PATH) $(DOTCONFIG_PATH) $(AUTOCONFIG_H) $(OUTPUT_PATH)/log.txt $(DOTCONFIG_PATH)
$(USER_RECORD_CONFIG_PATH): $(USER_CONFIG_SET)
@echo Using user config.
# create user_record.conf to record current setting.
@copy $(call FIXPATH, $^) $(call FIXPATH, $@)
# create .config by user config setting.
python ../scripts/kconfig.py --handwritten-input-configs $(KCONFIG_ROOT_PATH) $(DOTCONFIG_PATH) $(AUTOCONFIG_H) $(OUTPUT_PATH)/log.txt $(USER_CONFIG_SET)
export KCONFIG_CONFIG=$(DOTCONFIG_PATH)
$(DOTCONFIG_PATH):$(USER_RECORD_CONFIG_PATH)
@echo .config updated
menuconfig:$(DOTCONFIG_PATH)
# set KCONFIG_CONFIG=$(DOTCONFIG_PATH)
menuconfig $(KCONFIG_ROOT_PATH)
将生成autoconfig.h
的部分给删除掉,可以看到还是比较清晰的。由于APP可能不一样,默认选中app/test1
,后续可以调用make的时候调整。
- all,依赖2个.o文件,最终生成
main.exe
。 - main.o,依赖
main.c
,最终生成main.o
。 - driver_test.o,依赖
driver_test.c
和**autoconfig.h
**,最终生成driver_test.o
。 - clean,删除中间文件
APP ?= app/test1
OUTPUT_PATH := output
INCLUDE_PATH := -I$(APP) -Idriver -I$(OUTPUT_PATH)
# define autoconfig.h path
AUTOCONFIG_H := $(OUTPUT_PATH)/autoconfig.h
#For windows work.
FIXPATH = $(subst /,\,$1)
all: $(APP)/main.o driver/driver_test.o
gcc $^ -o $(OUTPUT_PATH)/main.exe
$(APP)/main.o: $(APP)/main.c
gcc $< $(INCLUDE_PATH) -c -o $@
driver/driver_test.o: driver/driver_test.c $(AUTOCONFIG_H)
gcc $< $(INCLUDE_PATH) -c -o $@
clean:
del /q /s $(call FIXPATH, $(APP)/main.o driver/driver_test.o $(OUTPUT_PATH))
生成autoconfig.h
相关的代码也不少,先对基本的参数做一些说明。
USER_CONFIG_SET,用户定义的初始化prj.conf
,不同例程配置的参数各不相同,当然也可以分成多个文件,这里只使用一个。
DOTCONFIG_PATH,配置用户menuconfig
和kconfig.py
来使用的文件,.h
文件的生成都是用这个文件进行的。
USER_RECORD_CONFIG_PATH,记录用户定义的config参数,本质就是为了利用makefile
的文件依赖关系,来记录USER_CONFIG_SET
有没有改变,如果有改变,就记录改变后的文件,并基于这些配置生成新的.config
文件。
AUTOCONFIG_H,最终要生成的autoconfig.h
文件路径。
KCONFIG_ROOT_PATH,Kconfig
的路径。
FIXPATH,windows的反斜杠相关问题处理。
autoconfig.h
,就会去看.config
目标是否存在或更新,最终调用kconfig.py
使用.config
来生成autoconfig.h
。
makefile
的规则来记录用户配置的config文件是否有更新,如果有更新会利用windows的copy指令记录当前用户的config配置,并调用kconfig.py
,将用户的config组合生成最终用于生成autoconfig.h
的.config
。--handwritten-input-configs是zephyr
项目kconfig.py
的配置参数,一些检查overwrite相关的处理。
export KCONFIG_CONFIG=$(DOTCONFIG_PATH),menuconfig
所需的参数,如果要修改.config
的路径,必须配置该环境变量。
.config
。当然如果刚使用时,没有默认的.config
,就用用户config来生成.config
。
menuconfig:$(DOTCONFIG_PATH),调用menuconfig
来临时配置参数。之前没写这个依赖,是因为所有的配置都是用menuconfig
生成和管理的。而在这里所有的配置参数需要围绕于用户配置参数来进行,所以第一次打开没有.config
时需要用用户当前配置作为menuconfig
的显示参数,要临时修改也要基于当前应用配置来调整。
OUTPUT_PATH := output
# define user .config setting
USER_CONFIG_SET :=
USER_CONFIG_SET += $(APP)/prj.conf
# define menuconfig .config path
DOTCONFIG_PATH := $(OUTPUT_PATH)/.config
# define user merged path
USER_RECORD_CONFIG_PATH := $(OUTPUT_PATH)/user_record.conf
# define autoconfig.h path
AUTOCONFIG_H := $(OUTPUT_PATH)/autoconfig.h
#define Kconfig path
KCONFIG_ROOT_PATH := Kconfig
#For windows work.
FIXPATH = $(subst /,\,$1)
$(AUTOCONFIG_H):$(DOTCONFIG_PATH)
python ../scripts/kconfig.py $(KCONFIG_ROOT_PATH) $(DOTCONFIG_PATH) $(AUTOCONFIG_H) $(OUTPUT_PATH)/log.txt $(DOTCONFIG_PATH)
$(USER_RECORD_CONFIG_PATH): $(USER_CONFIG_SET)
@echo Using user config.
# create user_record.conf to record current setting.
@copy $(call FIXPATH, $^) $(call FIXPATH, $@)
# create .config by user config setting.
python ../scripts/kconfig.py --handwritten-input-configs $(KCONFIG_ROOT_PATH) $(DOTCONFIG_PATH) $(AUTOCONFIG_H) $(OUTPUT_PATH)/log.txt $(USER_CONFIG_SET)
export KCONFIG_CONFIG=$(DOTCONFIG_PATH)
$(DOTCONFIG_PATH):$(USER_RECORD_CONFIG_PATH)
@echo .config updated
menuconfig:$(DOTCONFIG_PATH)
# set KCONFIG_CONFIG=$(DOTCONFIG_PATH)
menuconfig $(KCONFIG_ROOT_PATH)
上面讲了一堆的参数,相信大家有点晕了,那回到我们的主题,设计这个复杂的makefile
最终的目的的参考zephyr
的持久化版本以及解决多应用版本的需要。
- 一方面,我们希望多个例程之间有不同的配置参数,是在
Kconfig
的初始值基础上的不同配置。 - 另一方面,我们希望用户可以直接通过
menuconfig
看当前的应用最终的配置参数,并且可以通过menuconfig
来基于当前应用配置参数进行调整。
为了解决上述功能需要才设计了上述那么复杂的东西。
简单来讲,autoconfig.h
总的有两个修改路径。
- 一个是直接修改用户config文件,这个是持久化修改方案,此时再编译会冲刷掉
menuconfig
修改的值。 - 一个是通过
menuconfig
修改.config
文件,这个是临时修改方案,只对当前编译结果有效,会被用户配置给冲刷掉。
目标文件 | 依赖 | 备注 |
---|---|---|
autoconfig.h | $(APP)/prj.conf | 先生成merge.conf,而后用kconfig.py生成.config,最后再使用kconfig.py利用生成的.config生成autoconfig.h |
autoconfig.h | menuconfig直接修改 | 1. 没有.config,先用prj.conf输入kconfig.py生成.config;2. GUI呈现.config的信息,调整后更新.config;3. 使用kconfig.py利用生成的.config生成autoconfig.h |
直接make all
。默认选中的是test1例程,编译过程如下。
由于刚开始没有.config
文件,会使用prj.conf
文件生成.config
文件,而后再用生成.config
文件来生成autoconfig.h
(注意,从图中可以看到,之前就已经生成好了autoconfig.h,后面并没有改变。这里是因为笔者暂时没办法将第一步只做merge动作并生成.config文件,所以只能这样了)。
执行结果如下,会产生一些中间文件,直接使用的是test1的配置。
直接make menuconfig
。显示的配置参数就是默认test1的当前配置。
尝试调整值如下,输入Q,会提示保存.config,保存即可。
这时候编译结果如下,可以看出和我们GUI配置的参数一致。
直接文本操作,将test1目录下的prj.conf进行修改,修改后再通过make all
编译,过程如下图。
可以看出刚刚通过menuconfig配置的.config被覆盖了,使用的是最新的用户配置参数,结果如下所示。
test1的编译结果如上,clean完工程后,我们输入make all APP=app\test2
。得到的结果如下:
可以看出这时候用的是test2的配置参数,执行结果也符合预期。
从上面的分析可以看出,之后发布给客户的时候不同应用就有其独特的配置了。
但是问题来了,要如何生成prj.conf
文件呢,menuconfig
提供了一个保存差异的功能,进入GUI修改完参数后,可以输入D就可以输出差异配置了,这个差异配置放到prj.conf
中即可。
注意:这里生成的是相对于Kconfig
的差异文件,并不是基于当前配置的差异。