前言
变量,是在 makefile 中定义的名字,其用来代替一个文本字符串,该文本字符串称为该变量的值。在具体要求下,这些值可以代替目标、依赖、命令以及 makefile 文件中其它部分。(在其它版本的make中,变量称为宏(macros)。)
在 makefile 文件读入时,除规则中的 shell 命令、使用‘=’定义的‘=’右边的变量、以及使用 define 指令定义的变量体此时不扩展外,makefile 文件其它各个部分的变量和函数都将扩展。
变量可以代替文件列表、传递给编译器的选项、要执行的程序、查找源文件的目录、输出写入的目录,或您可以想象的任何文本。
变量名是不包括 ‘:’、’#’、’=’ 、前导或结尾空格的任何字符串。然而变量名包含字母、数字以及下划线以外的其它字符的情况应尽量避免,因为它们可能在将来被赋予特别的含义,而且对于一些shell 它们也不能通过环境传递给子 make。变量名是大小写敏感的,例如变量名 ‘foo’,‘FOO’, 和 ‘Foo’ 代表不同的变量。
使用大写字母作为变量名是以前的习惯,但我们推荐在 makefile 内部使用小写字母作为变量名,预留大写字母作为控制隐含规则参数或用户重载命令选项参数的变量名。
一少部分的变量使用一个标点符号或几个字符作为变量名,这些变量是自动变量,它们有特定的用途。
一、变量引用基础
写一个美元符号,后跟用圆括号或大括号括住变量名则可引用变量的值:’$(foo)’ 和 ‘${foo}’ 都是对变量 ‘foo’ 的有效引用。’$’ 的这种特殊作用是您在命令或文件名中必须写 ‘$$’ 才有单个 ‘$’ 的效果的原因。即两个 $ 美元符才可以代表一个 $ 字面美元符。
变量引用,可以用在上下文的任何地方:目标、依赖、命令、绝大多数指令以及新变量的值等等。这里有一个常见的例子,在 Makefile 文件中,变量保存着所有 OBJ 文件的文件名:
[root@localhost demo_002]# cat Makefile
objects = program.o foo.o utils.o
program : $(objects)
cc -o program $(objects)
$(objects) : defs.h
[root@localhost demo_002]#
[root@localhost demo_002]#
[root@localhost demo_002]# ls -lh
total 8.0K
-rw-r--r--. 1 root root 0 Nov 30 20:01 defs.h
-rw-r--r--. 1 root root 0 Nov 30 20:00 foo.c
-rw-r--r--. 1 root root 104 Nov 30 20:09 Makefile
-rw-r--r--. 1 root root 70 Nov 30 2021 program.c
-rw-r--r--. 1 root root 0 Nov 30 20:00 utils.c
[root@localhost demo_002]#
[root@localhost demo_002]# make
cc -c -o program.o program.c
cc -c -o foo.o foo.c
cc -c -o utils.o utils.c
cc -o program program.o foo.o utils.o
美元符号后面跟一个字符但不是美元符号、圆括号、大括号,则该字符将被处理为单字符的变量名。因此可以使用 ‘$x’ 引用变量 x 。然而,这除了在使用自动变量的情况下,在其它实际工作中应该完全避免。
二、变量的两个特色
在 GNU make 中可以使用两种方式为变量赋值,我们将这两种方式称为变量的两个特色(two flavors)。两个特色的区别在于它们的定义方式和扩展时的方式不同。
变量的第一个特色是递归调用扩展型变量。这种类型的变量定义方式:在命令行中使用 ‘=’ 定义或使用 define 指令定义。变量替换对于您所指定的值是逐字进行替换的;如果它包含对其它变量的引用,这些引用在该变量替换时(或在扩展为其它字符串的过程中)才被扩展。这种扩展方式称为递归调用型扩展。例如:
[root@localhost demo_002]# cat Makefile
foo = $(bar)
bar = $(ugh)
ugh = Huh?
all :;@echo $(foo)
[root@localhost demo_002]#
[root@localhost demo_002]# make
Huh?
[root@localhost demo_002]#
将回显 ‘Huh?’,’$(foo)’ 扩展为 ‘$(bar)’,进一步扩展为 ‘$(ugh)’,最终扩展为 ‘Huh?’。
这种特色的变量是其它版本make支持的变量类型,有缺点也有优点。大多数人认为的该类型的变量的优点是:
CFLAGS = $(include_dirs) -O
include_dirs = -Ifoo -Ibar
即能够完成希望它完成的任务:当 ‘CFLAGS’ 在命令中扩展时,它将最终扩展为 ‘-Ifoo -Ibar’。其最大的缺点是不能在变量后追加内容,如在
CFLAGS = $(CFLAGS) -O
在变量扩展过程中可能导致无穷循环(实际上 make 侦测到无穷循环就会产生错误信息)。
它的另一个缺点是在定义中引用的任何函数时,变量一旦展开函数就会立即执行。这可导致 make 运行变慢,性能变坏;并且导致通配符与 shell 函数(因不能控制何时调用或调用多少次)产生不可预测的结果。
为避免该问题和递归调用扩展型变量的不方便性,出现了另一个特色变量:简单扩展型变量。
简单扩展型变量在命令行中用 ‘:=’ 定义。简单扩展型变量的值是一次扫描永远使用,对于引用的其它变量和函数,在定义的时候就已经展开了。简单扩展型变量的值,实际就是您写的文本扩展完成后的结果。因此它不包含任何对其它变量的引用;在该变量定义时就包含了它们的值。所以:
x := foo
y := $(x) bar
x := later
等同于
y := foo bar
x := later
引用一个简单扩展型变量时,它的值也是逐字替换的。这里有一个稍复杂的例子,说明了 ‘:=’ 和shell函数连接用法(参阅*函数 shell *)。该例子也表明了变量 MAKELEVEL 的用法,该变量在层与层之间传递时值发生变化。(参阅*与子make通讯的变量*,可获得变量MAKELEVEL关于的信息。)
ifeq (0,${MAKELEVEL})
cur-dir := $(shell pwd)
whoami := $(shell whoami)
host-type := $(shell arch)
MAKE := ${MAKE} host-type=${host-type} whoami=${whoami}
endif
按照这种方法使用 ‘:=’ 的优点是看起来象下述的典型的 ‘下降到目录’ 的命令:
${subdirs}:
${MAKE} cur-dir=${cur-dir}/$@ -C $@ all
简单扩展型变量因为在绝大多数程序设计语言中可以象变量一样工作,因此它能够使复杂的 makefile 程序更具有预测性。它们允许您使用它自己的值重新定义(或它的值可以被一个扩展函数以某些方式处理),它们还允许您使用更有效的扩展函数(参阅 *文本转换函数*)。
另一个给变量赋值的操作符是 ‘?=’,它称为条件变量赋值操作符,因为它仅仅在变量还没有定义的情况下有效。这声明:
FOO ?= bar
和下面的语句严格等同(参阅*函数origin*)
ifeq ($(origin FOO), undefined)
FOO = bar
endif
注意,一个变量即使是空值,它仍然已被定义,所以使用 ‘?=’ 定义无效。
三、变量引用高级技术
3.1 替换引用
替换引用是用您指定的变量替换一个变量的值。它的形式 ‘$(var:a=b)’ 或 ‘${var:a=b}’,它的含义是把变量 var 的值中的每一个字结尾的a用b替换。
我们说 ’在一个字的结尾‘,我们的意思是a一定在一个字的结尾出现,且a的后面要么是空格要么是该变量值的结束,这时的a被替换,值中其它地方的a不被替换。例如:
[root@localhost demo_002]# cat Makefile
foo := a.o b.o c.o
bar := $(foo:.o=.c)
all : ;@echo $(bar)
[root@localhost demo_002]#
[root@localhost demo_002]# make
a.c b.c c.c
[root@localhost demo_002]#
[root@localhost demo_002]# cat Makefile
foo := a.o b.o c.o
bar := ${foo:.o=.c}
all : ;@echo $(bar)
[root@localhost demo_002]#
[root@localhost demo_002]# make
a.c b.c c.c
[root@localhost demo_002]#
将变量 ‘bar’ 的值设为 ‘a.c b.c c.c’。
替换引用实际是使用扩展函数patsubst的简写形式。我们提供替换引用也是使扩展函数 patsubst 与 make 的其它实现手段兼容的措施。
另一种替换引用是使用强大的扩展函数patsubst。它的形式和上述的 ‘$(var:a=b)’ 一样,不同在于它必须包含单个 ‘%’ 字符,其实这种形式等同于 ‘$(patsubst a,b,$(var))’。例如:
[root@localhost demo_002]#
[root@localhost demo_002]# cat Makefile
foo := a.o b.o c.o
bar := ${foo:%.o=%.c}
all : ;@echo $(bar)
[root@localhost demo_002]#
[root@localhost demo_002]# make
a.c b.c c.c
[root@localhost demo_002]#
3.2 嵌套变量引用(计算的变量名)
[root@localhost demo_002]# cat Makefile
x = y
y = z
a := $($(x))
all : ;@echo $(a)
[root@localhost demo_002]#
[root@localhost demo_002]# make
z
[root@localhost demo_002]#
‘$(x)’ 在 ‘$($(x))’ 中扩展为 ‘y’,因此 ‘$($(x))’ 扩展为 ‘$(y)’,最终扩展为 ‘z’。
前一个例子表明了两层嵌套,但是任何层次数目的嵌套都是允许的,例如,这里有一个三层嵌套的例子:
[root@localhost demo_002]#
[root@localhost demo_002]# cat Makefile
x = y
y = z
z = u
a := $($($(x)))
all : ;@echo $(a)
[root@localhost demo_002]#
[root@localhost demo_002]# make
u
[root@localhost demo_002]#
最里面的 ‘$(x)’ 扩展为 ‘y’,因此 ‘$($(x))’ 扩展为 ‘$(y)’,’$(y)’ 扩展为 ‘z’,最终扩展为 ‘u’。
在一个变量名中引用递归调用扩展型变量,则按通常的风格再扩展。例如:
[root@localhost demo_002]# cat Makefile
x = $(y)
y = z
z = hello
a := $($(x))
all : ;@echo $(a)
[root@localhost demo_002]#
[root@localhost demo_002]# make
hello
[root@localhost demo_002]#
‘$($(x))’ 扩展为 ‘$($(y))’,’$($(y))’ 变为 ‘$(z)’, ‘$(z)’ 最终扩展为 ‘Hello’。
嵌套变量引用和其它引用一样也可以包含修改引用和函数调用。例如,使用函数 subst:
[root@localhost demo_002]# cat Makefile
x = variable1
variable2 := hello
y = $(subst 1,2,$(x))
z = y
a := $($($(z)))
all : ;@echo $(a)
[root@localhost demo_002]#
[root@localhost demo_002]# make
hello
[root@localhost demo_002]#
任何人也不会写象这样令人费解的嵌套引用程序,但它确实可以工作:’$($($(z)))’ 扩展为 ‘$($(y))’,’$($(y))’ 变为 ‘$(subst 1,2,$(x))’ 。它从变量 ‘x’ 得到值 ‘variable1’,变换替换为 ‘variable2’,所以整个字符串变为 ‘$( variable2)’,一个简单的变量引用,它的值为 ‘Hello’。
嵌套变量引用不都是简单的变量引用,它可以包含好几个变量引用,同样也可包含一些固定文本。例如,
[root@localhost demo_002]# cat Makefile
use_a := yes
use_dirs := no
a_dirs := dira dirb
1_dirs := dir1 dir2
a_files := filea fileb
1_files := file1 file2
ifeq "$(use_a)" "yes"
a1 := a
else
a1 := 1
endif
ifeq "$(use_dirs)" "yes"
df := dirs
else
df := files
endif
dirs := $($(a1)_$(df))
all : ;@echo $(dirs)
[root@localhost demo_002]#
[root@localhost demo_002]# make
filea fileb
[root@localhost demo_002]#
根据设置的 use_a 和 use_dirs 的输入可以将 dirs 这个相同的值分别赋给 a_dirs, 1_dirs, a_files 或 1_files。
嵌套变量引用也可以用于替换引用:
[root@localhost demo_002]# cat Makefile
a1 := 1
a_objects := a.o b.o c.o
1_objects := 1.o 2.o 3.o
sources := $($(a1)_objects:.o=.c)
all : ;@echo $(sources)
[root@localhost demo_002]#
[root@localhost demo_002]# make
1.c 2.c 3.c
[root@localhost demo_002]#
根据a1的值,定义的sources可以是 ‘a.c b.c c.c’ 或 ‘1.c 2.c 3.c’。
使用嵌套变量引用,唯一的限制是它们不能只部分指定要调用的函数名,这是因为用于识别函数名的测试在嵌套 变量引用扩展之前完成。例如:
[root@localhost demo_002]# cat Makefile
ifdef do_sort
func := sort
else
func := strip
endif
bar := a d b g q c
foo := $($(func) $(bar))
all : ;@echo $(foo)
[root@localhost demo_002]#
[root@localhost demo_002]# make
[root@localhost demo_002]#
则给变量 ‘foo’ 的值赋为 ‘sort a d b g q c’ 或 ‘strip a d b g q c’,而不是将 ‘a d b g q c’ 作 为函数 sort 或 strip 的参数。
3.3 设置变量
在 makefile 文件中设置变量,编写以变量名开始,后跟 ‘=’ 或 ‘:=’ 的一行即可。任何跟在 ‘=’ 或 ‘:=’ 后面的内容就为变量的值。例如:
objects = main.o foo.o bar.o utils.o
定义一个名为 objects 的变量,变量名前后的空格和紧跟 ‘=’ 的空格将被忽略。
使用 ‘=’ 定义的变量是递归调用扩展型变量;以 ‘=’ 定义的变量是简单扩展型变量。简单扩展型变量定义可以包含变量引用,而且变量引用在定义的同时就被立即扩展(参阅 *变量的两种特色*)。
变量名中也可以包含变量引用和函数调用,它们在该行读入时扩展,这样可以计算出能够实际使用的变量名。 变量值的长度没有限制,但受限于计算机中的实际交换空间。当定义一个长变量时,在合适的地方插入反斜杠,把变量值分为多个文本行是不错的选择。这不影响make的功能,但可使makefile文件更加易读。
绝大多数变量,如果您不为它设置值,空字符串将自动作为它的初值。虽然一些变量有内建的非空的初始化值, 但您可随时按照通常的方式为它们赋值。另外一些变量可根据规则自动设定新值,它们被称为自动变量。
如果您喜欢仅对没有定义过的变量赋给值,您可以使用速记符 ‘?=’ 代替 ‘=’ 。下面两种设置变量的方式完全等同:
FOO ?= bar
和
ifeq ($(origin FOO), undefined)
FOO = bar
endif
3.4 为变量值追加文本
为已经定义过的变量的值追加更多的文本,一般比较有用。您可以在独立行中使用 ‘+=’ 来实现上述设想。如:
objects += another.o
这为变量 objects 的值添加了文本 ‘another.o’(其前面有一个前导空格)。这样:
[root@localhost demo_002]# cat Makefile
objects = main.o foo.o bar.o utils.o
objects += another.o
all : ;@echo $(objects)
[root@localhost demo_002]#
[root@localhost demo_002]# make
main.o foo.o bar.o utils.o another.o
[root@localhost demo_002]#
变量 objects 设置为 ‘main.o foo.o bar.o utils.o another.o’。
使用 ‘+=’ 相同于:
[root@localhost demo_002]# cat Makefile
objects = main.o foo.o bar.o utils.o
objects := $(objects) another.o
all : ;@echo $(objects)
[root@localhost demo_002]#
[root@localhost demo_002]# make
main.o foo.o bar.o utils.o another.o
[root@localhost demo_002]#
对于使用复杂的变量值,不同方法的差别非常重要。
如变量在以前没有定义过,则 ‘+=’ 的作用和 ‘=’ 相同:它定义一个递归调用型变量。然而如果在以前有定义,’+=’ 的作用依赖于您原始定义的变量的特色,详细内容参阅 *变量的两种特色*。
当您使用 ‘+=’ 为变量值附加文本时,make 的作用就好象您在初始定义变量时就包含了您要追加的文本。
如果开始时,您使用 ‘:=’ 定义一个简单扩展型变量,再用 ‘+=’ 对该简单扩展型变量值追加文本,则该变量按新的文本值扩展,好像在原始定义时就将追加文本定义上一样。实际上,
variable := value
variable += more
等同于:
variable := value
variable := $(variable) more
另一方面,当您把 ‘+=’ 和首次使用无符号 ‘=’ 定义的递归调用型变量一起使用时,make的运行方式会有所差异。在您引用递归调用型变量时,make并不立即在变量引用和函数调用时扩展您设定的值;而是将它逐字储存起来,将变量引用和函数调用也储存起来,以备以后扩展。当您对于一个递归调用型变量使用 ‘+=’ 时,相当于对一个不扩展的文本追加新文本。
variable = value
variable += more
粗略等同于:
temp = value
variable = $(temp) more
当然,您从没有定义过叫做 temp 的变量。如您在原始定义变量时,变量值中就包含变量引用,此时可以更为深 刻地体现使用不同方式定义的的重要性。拿下面常见的例子,
CFLAGS = $(includes) -o
...
CFLAGS += -pg # enable profiling
第一行定义了变量 CFLAGS,而且变量 CFLAGS 引用了其它变量,includes。由于定义时使用 ‘=’,所以变量CFLAGS 是递归调用型变量,意味着 ‘$(includes) -o’ 在 make 处理变量 CFLAGS 定义时是不扩展的;也就是变量 includes 在生效之前不必定义,它仅需要在任何引用变量 CFLAGS 之前定义即可。如果我们试图不使用‘+=’为变量CFLAGS追加文本,我们可能按下述方式:
CFLAGS := $(CFLAGS) -pg # enable profiling
这似乎很好,但结果绝不是我们所希望的。使用 ‘:=’ 重新定义变量CFLAGS为简单扩展型变量,意味着 make 在设置变量CFLAGS之前扩展了 ‘$(CFLAGS) -pg’。如果变量 includes 此时没有定义,我们将得到 ‘-0 - pg’,并且以后对变量 includes 的定义也不会有效。相反,使用 ‘+=’ 设置变量 CFLAGS 我们得到没有扩展的 ‘$(CFLAGS) –0 -pg’,这样保留了对变量 includes 的引用,在后面一个地方如果变量 includes 得到定 义,’$(CFLAGS)’ 仍然可以使用它的值。
[root@localhost demo_002]# cat Makefile
CFLAGS = $(includes) -o
#CFLAGS := $(includes) -o
CFLAGS += -pg
includes := include
all : ;@echo $(CFLAGS)
[root@localhost demo_002]#
[root@localhost demo_002]# make
include -o -pg
[root@localhost demo_002]#
[root@localhost demo_002]# cat Makefile
#CFLAGS = $(includes) -o
CFLAGS := $(includes) -o
CFLAGS += -pg
includes := include
all : ;@echo $(CFLAGS)
[root@localhost demo_002]# make
-o -pg
[root@localhost demo_002]#
[root@localhost demo_002]#
[root@localhost demo_002]# cat Makefile
CFLAGS = $(includes) -o
#CFLAGS := $(includes) -o
includes := include
CFLAGS := $(CFLAGS) -pg
all : ;@echo $(CFLAGS)
[root@localhost demo_002]#
[root@localhost demo_002]# make
include -o -pg
[root@localhost demo_002]#
[root@localhost demo_002]#
[root@localhost demo_002]# cat Makefile
#CFLAGS = $(includes) -o
CFLAGS := $(includes) -o
includes := include
CFLAGS := $(CFLAGS) -pg
all : ;@echo $(CFLAGS)
[root@localhost demo_002]#
[root@localhost demo_002]# make
-o -pg
[root@localhost demo_002]#