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 vs 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