Kconfig 宏语言¶
概念¶
基本思想来源于 Make。当我们查看 Make 时,我们会注意到它实际上是两种语言的混合。一种语言描述由目标和先决条件组成的依赖关系图。另一种是用于执行文本替换的宏语言。
这两个语言阶段之间有明显的区别。例如,您可以编写如下的 Makefile:
APP := foo
SRC := foo.c
CC := gcc
$(APP): $(SRC)
$(CC) -o $(APP) $(SRC)
宏语言会将变量引用替换为它们的展开形式,并将处理结果视为如下的源文件输入:
foo: foo.c
gcc -o foo foo.c
然后,Make 会分析依赖关系图并确定需要更新的目标。
Kconfig 中的概念非常相似 - 可以像这样描述 Kconfig 文件:
CC := gcc
config CC_HAS_FOO
def_bool $(shell, $(srctree)/scripts/gcc-check-foo.sh $(CC))
Kconfig 中的宏语言会将源文件处理为以下中间状态:
config CC_HAS_FOO
def_bool y
然后,Kconfig 进入评估阶段,以解决符号间的依赖关系,如 Kconfig 语言 中所述。
变量¶
与 Make 中一样,Kconfig 中的变量充当宏变量。宏变量会在“原地”展开,生成一个文本字符串,然后可以进一步展开。要获取变量的值,请将变量名括在 $( ) 中。即使是单字母变量名也需要括号;$X 是语法错误。也不支持像 ${CC} 这样的花括号形式。
有两种类型的变量:简单展开变量和递归展开变量。
简单展开变量使用 := 赋值运算符定义。其右侧会在从 Kconfig 文件读取该行时立即展开。
递归展开变量使用 = 赋值运算符定义。其右侧只是作为变量的值存储,而不会以任何方式展开。相反,展开会在使用该变量时执行。
还有另一种类型的赋值运算符;+= 用于向变量追加文本。如果左侧最初定义为简单变量,则 += 的右侧会立即展开。否则,它的评估会延迟。
变量引用可以采用参数,形式如下:
$(name,arg1,arg2,arg3)
您可以将参数化引用视为一个函数。(更准确地说,是“用户定义的函数”,与下面列出的“内置函数”相对)。
有用的函数必须在使用时展开,因为如果传递不同的参数,则同一个函数会以不同的方式展开。因此,用户定义的函数使用 = 赋值运算符定义。参数在主体定义中使用 $(1)、$(2) 等引用。
实际上,递归展开的变量和用户定义的函数在内部是相同的。(换句话说,“变量”是“零参数函数”。)当我们广义地谈论“变量”时,它包括“用户定义的函数”。
内置函数¶
与 Make 一样,Kconfig 提供了几个内置函数。每个函数都接受特定数量的参数。
在 Make 中,每个内置函数至少接受一个参数。Kconfig 允许内置函数使用零参数,例如 $(filename)、$(lineno)。您可以将它们视为“内置变量”,但这只是我们如何称呼它的问题。让我们在这里称之为“内置函数”,以指代本机支持的功能。
Kconfig 当前支持以下内置函数。
$(shell,command)
“shell” 函数接受一个参数,该参数会被展开并传递给子 shell 执行。然后读取命令的标准输出,并将其作为函数的值返回。输出中的每个换行符都会被替换为空格。任何尾随的换行符都会被删除。标准错误不会被返回,任何程序的退出状态也不会被返回。
$(info,text)
“info” 函数接受一个参数并将其打印到标准输出。它的计算结果为空字符串。
$(warning-if,condition,text)
“warning-if” 函数接受两个参数。如果 condition 部分为 “y”,则 text 部分会发送到标准错误。文本会附加当前 Kconfig 文件名和当前行号作为前缀。
$(error-if,condition,text)
“error-if” 函数类似于 “warning-if”,但如果 condition 部分为 “y”,则它会立即终止解析。
$(filename)
‘filename’ 不接受参数,$(filename) 会被展开为正在解析的文件名。
$(lineno)
‘lineno’ 不接受参数,$(lineno) 会被展开为正在解析的行号。
Make 与 Kconfig¶
Kconfig 采用类似于 Make 的宏语言,但函数调用语法略有不同。
Make 中的函数调用如下所示:
$(func-name arg1,arg2,arg3)
函数名和第一个参数之间至少由一个空格分隔。然后,会从第一个参数中删除前导空格,而其他参数中的空格则会保留。您需要使用某种技巧才能使第一个参数以空格开头。例如,如果您想让 “info” 函数打印 “ hello”,您可以像下面这样编写:
empty :=
space := $(empty) $(empty)
$(info $(space)$(space)hello)
Kconfig 仅使用逗号作为分隔符,并保留函数调用中的所有空格。有些人喜欢在每个逗号分隔符后放置一个空格:
$(func-name, arg1, arg2, arg3)
在这种情况下,“func-name” 将接收 “ arg1”、“ arg2”、“ arg3”。前导空格的存在与否可能取决于函数。Make 也同样如此 - 例如,$(subst .c, .o, $(sources)) 是一个典型的错误;它会将 “.c” 替换为 “ .o”。
在 Make 中,用户定义的函数通过使用内置函数 ‘call’ 来引用,如下所示:
$(call my-func,arg1,arg2,arg3)
Kconfig 以相同的方式调用用户定义的函数和内置函数。省略 ‘call’ 使语法更简洁。
在 Make 中,某些函数将逗号视为文字,而不是参数分隔符。例如,$(shell echo hello, world) 运行命令 “echo hello, world”。同样,$(info hello, world) 会将 “hello, world” 打印到标准输出。您可以说这是一种 _有用的_ 不一致性。
在 Kconfig 中,为了更简单的实现和语法一致性,出现在 $( ) 上下文中的逗号始终是分隔符。这意味着:
$(shell, echo hello, world)
是一个错误,因为它传递了两个参数,而 ‘shell’ 函数只接受一个参数。要在参数中传递逗号,您可以使用以下技巧:
comma := ,
$(shell, echo hello$(comma) world)
注意事项¶
变量(或函数)不能跨标记展开。因此,您不能使用变量作为由多个标记组成的表达式的简写。以下代码有效:
RANGE_MIN := 1
RANGE_MAX := 3
config FOO
int "foo"
range $(RANGE_MIN) $(RANGE_MAX)
但是,以下代码不起作用:
RANGES := 1 3
config FOO
int "foo"
range $(RANGES)
变量不能展开为 Kconfig 中的任何关键字。以下代码不起作用:
MY_TYPE := tristate
config FOO
$(MY_TYPE) "foo"
default y
显然,从设计上来说,$(shell command) 在文本替换阶段展开。您不能将符号传递给 ‘shell’ 函数。
以下代码不会按预期工作:
config ENDIAN_FLAG
string
default "-mbig-endian" if CPU_BIG_ENDIAN
default "-mlittle-endian" if CPU_LITTLE_ENDIAN
config CC_HAS_ENDIAN_FLAG
def_bool $(shell $(srctree)/scripts/gcc-check-flag ENDIAN_FLAG)
相反,您可以像下面这样操作,以便静态展开任何函数调用:
config CC_HAS_ENDIAN_FLAG
bool
default $(shell $(srctree)/scripts/gcc-check-flag -mbig-endian) if CPU_BIG_ENDIAN
default $(shell $(srctree)/scripts/gcc-check-flag -mlittle-endian) if CPU_LITTLE_ENDIAN