makefile 变量赋值
GNU make中赋值语法分为:
= 将右侧的值赋值给左侧。延迟赋值
:= 将右侧的值赋值给左侧。立即赋值
::= 将右侧的值赋值给左侧。立即赋值
:::= 转义立即赋值运算符,右侧的值会立即展开但进行转义处理(即展开结果中所有$符号都会被替换为$$)。
+= 追加运算,若变量先前被定义为简单变量(使用:=或::=),则右侧被视为立即赋值,否则视为延迟赋值。
?= 检查左侧变量是否被赋值,如果没有被赋值则为此变量赋值。延迟赋值
!= 将右侧内容当作shell指令执行,并把结果赋值给变量。延迟赋值
引用变量可以使用$()或者${},例如$(test)就是引用test变量
makefile进行变量赋值时都是将内容视为成字符串
延迟赋值 (=)
对于延迟赋值的变量,在引用变量的时候会进行递归展开。
在接下来的例子中我会用info函数,来打印变量的值,调用函数的语法是$(函数名 参数1,参数2,参数3...)
例如:
#makefile
test = test$(a)
a = 1
$(info $(test))
a = 2
$(info $(test))
如果按照正常的编程思路来讲,test
变量应该是存储的test字符串,因为在此之前a没有值。引用a应该是空的
但是当你在命令行执行make
时这个程序的结果是
test1
test2
make: *** No targets. Stop.
这看起来很反直觉,这就是延迟赋值的特点
在这个例子中test = test$(a)
,使test
变量赋值了一个test$(a)
的表达式,此时$(a)并没有立即展开
接下来执行a = 1
,将a赋值为1。在执行第一个info函数时$(test)
进行了递归展开,首先会展开成test$(a)
,
紧接着再把$(a)
再次展开得到test1,同样的道理也能解释为什么第二个info函数打印test2
立即赋值 (:= 和 ::=)
与 =
不同,使用 :=
或 ::=
的赋值是立即发生的
:=
: 将右侧的值立即展开后,赋值给左侧变量
::=
: 这是 :=
的同义词,在 GNU make 中两者功能完全一样
特点:使用立即赋值,变量在定义的那一刻,其值就被固定下来了,后续其他变量的改变不会影响它
让我们通过一个例子来理解它和延迟赋值的区别:
# makefile
a = 1
# 立即赋值
immediate := test$(a)
# 延迟赋值
deferred = test$(a)a = 2$(info a is now: $(a))
$(info immediate value: $(immediate))
$(info deferred value: $(deferred))
执行make
后,输出结果是:
a is now: 2
immediate value: test1
deferred value: test2
make: *** No targets. Stop.
在这个例子中:
a is now: 2: 最后 a 的值是 2
immediate value: test1: 变量 immediate
在定义时使用的是 :=
。在定义它的那一刻,$(a)
被立即展开。当时 a
的值是 1,所以 immediate
的值被固定为 test1。之后 a
变为 2 对它没有任何影响
deferred value: test2: 变量 deferred
在定义时使用的是 =
。它只是记录了一个表达式 test$(a)
,直到被 $(info ...)
引用时才进行展开。此时它查找 a
的当前值,也就是 2,所以最终结果是 test2
转义立即赋值 (:::=)
:::=
运算符用于立即赋值并对结果进行转义。
功能:它会立即展开右侧的表达式,然后将展开结果中的所有 $ 字符替换为 $$。
用途:这在你需要将一个本身包含 $ 的字符串(例如,一个包含了变量引用的命令脚本)保存起来,并希望它在后续被使用(例如被 eval 函数处理)时再被展开的情况下非常有用。初学者可以稍后再深入理解这个运算符。有时你可能需要第一次立即赋值,之后追加时进行延迟赋值这种情况下也可以用此赋值。
# makefile
some_var = hello
escaped_value :::= $(some_var)$$(SHELL)$(info escaped_value is: $(escaped_value))
输出结果为:
escaped_value is: hello$(SHELL)
make: *** No targets. Stop.
注意: 因为makefile的$
一般用来引用变量,所以一个$
不能表示$本身,如果想仅仅表示字符$,你需要写$$
追加赋值 (+=)
+=
运算符用于向一个已存在的变量追加内容。
行为:它的具体行为取决于变量之前的定义方式。
如果变量之前是用 :=
或 ::=
(立即变量)定义的,那么 +=
的右侧会立即展开,然后追加。
如果变量之前是用 =
(延迟变量)定义的,或者未定义,那么 +=
的右侧会保持延迟,作为一个表达式被追加。
向延迟变量追加
# makefile
deferred_var = first
deferred_var += second$(a) # 因为deferred_var是延迟变量,所以‘second$(a)’不会立即展开a = word$(info $(deferred_var))
输出结果为:
first secondword
make: *** No targets. Stop.
second$(a)
作为表达式被追加,在最终展开时得到了 secondword。
向立即变量追加
# makefile
immediate_var := first
immediate_var += second$(a) # 因为immediate_var是立即变量,‘second$(a)’被立即展开a = word$(info $(immediate_var))
输出结果为:
first second
make: *** No targets. Stop.
在追加时,$(a)
还未定义,所以被展开为空字符串,最终 immediate_var
的值是 first second。
条件赋值 (?=)
?=
运算符只在变量尚未被赋值的情况下才会为其赋值
行为:它是延迟赋值。如果变量已经有值(即使是空值),则这次赋值什么也不做。
# makefile
# 第一次定义,所以赋值成功
assigned_with ?= first
# 已经定义过了,所以这行无效
assigned_with ?= second
# 从未定义,所以赋值成功
not_assigned ?= third$(info assigned_with: $(assigned_with))
$(info not_assigned: $(not_assigned))
结果为:
assigned_with: first
not_assigned: third
make: *** No targets. Stop.
它通常用于为用户提供自定义变量的机会,例如在 Makefile 开头设置CC ?= gcc
,用户可以在命令行用make CC=clang
来覆盖默认值。
Shell 赋值 (!=)
!=
运算符用于将Shell 命令的输出作为变量的值。
行为:它是延迟赋值。它的效果类似于使用:=
和$(shell ...)
函数,但语法更简洁。注意:命令的输出结果的末尾换行符会被替换为空格。
# makefile
# 使用 != 运算符
files != ls *.c
# 功能等效的写法
# files := $(shell ls *.c)$(info Source files are: $(files))
假设当前目录下有 main.c 和 tool.c,输出为:
Source files are: main.c tool.c
make: *** No targets. Stop.
注意:由于它是延迟赋值,如果你在赋值后修改了文件系统(例如创建了新的 .c 文件),再次执行make
时,files变量会被重新执行并获取最新的结果。但如果用 := $(shell ...)
,命令在 Makefile 解析阶段就只执行一次