1 前言
在Makefile中,变量的使用类似于C语言中的宏,在 Makefile 的目标、依赖、命令中引用变量的地方,变量会被它的值所取代
Makefile中的变量有以下几个特征
- 展开是在make读取Makefile文件时进行的(包括使用"="和"define"定义的)
- 变量可以代表文件名列表,编译选项列表,程序运行的选项参数列表,搜索源文件的目录列表等
- 变量名不包括":","#","=",前置空白和尾空白的任何字符串(尽量使用数字,字母和下划线,不要用其他字符,以免出现未知错误)
- 变量名是大小写敏感的
- 有一些变量名只包含了一个或者很少的几个特殊的字符(符号)。称它们为自动化变量(例如:“@”、“$?”、“$*“)
2 变量的引用
在定义好一个变量之后,就可以在Makefile的很多地方使用,使用方式是
$(VARIABLE_NAME)
或者
${ VARIABLE_NAME }
例如:"$(foo) "或者"${foo}”就是取变量“foo”的值,对一个变量的引用可以在Makefile的任何上下文中,目标、依赖、命令、绝大多数指示符和新变量的赋值中
注意:美元符号"$"在Makefile中有特殊的含义,所有在命令或者文件名中使用"$"时需要用两个美元符号"$$"来表示
变量引用的展开过程是严格的文本替换过程,就是说变量值的字符串被精确的展开在变量被引用的地方,例如
oo = c
prog.o : prog.$(foo)
$(foo) $(foo) -$(foo) prog.$(foo)
就是
prog.c : prog.c
cc -c prog.c
Makefile中在对一些简单变量的引用,我们也可以不使用“()”和“{}”来 标记变量名,而直接使用“$x”的格式来实现,此种用法仅限于变量名为单字符的情况“$PATH”实际上是“$(P)ATH”)这一点和shell中变量的引用方式不同
2.1 变量书写建议
在我们书写Makefile时,各部分变量引用的格式我们建议如下
- make变量(Makefile中定义的或者是make的环境变量)的引用使用“$(VAR)”格式,无论“VAR”是单字符变量名还是多字符变量名
- 出现在规则命令行中shell变量(一般为执行命令过程中的临时变量,它不属于Makefile变量,而是一个shell变量)引用使用shell的“$tmp”格式
- 对出现在命令行中的make变量我们同样使用“$(CMDVAR)”格式来引用
例如
SUBDIRS := src foo
.PHONY : subdir
Subdir :
@for dir in $(SUBDIRS); do \
$(MAKE) –C $$dir || exit 1; \
done
......
3 变量是定义
在GNU make中,变量的定义有两种方式
- 递归展开式变量
- 直接展开式变量
它们的区别在于定义方式和展开时机
3.1 递归展开式变量
递归展开式变量是通过“=”或者指示符“define”定义的,这种变量的引用,在引用的地方是严格的文本替换过程。如果此变量定义中存在对其他变量的引用,那么在变量定义时这些变量不会被展开,而是在变量引用它的地方被一同展开
这种类型的变量称为“递归展开”式变量,
优点是: 这种类型变量在定义时,可以引用其它的之前没有定义的变量
缺点是: 可能会由于出现变量的递归定义而导致make陷入到无限的变量展开过程中,最终使 make执行失败(即循环引用),另外,如果使用了函数,那么包含在变量值中的函数总会在变量被引用的地方执行(变量被展开时)
3.2 直接展开式变量
直接展开式变量是为了避免递归展开式变量而来的。这种风格的变量使用“:=”定义。在使用“:=”定义变量时,变量值中对其他量或者函数的引用在定义变量时被展开
和递归展开式变量不同:此风格变量在定义时就完成了对所引用变量和函数的展开,因此不能实现对其后定义变量的引用
例如
x := foo
y := $(x) bar
x := later
等价于
y := foo bar
x := later
3.2.1 直接展开式中空格的使用
使用直接展开式定义空格的时候,我们可以这样做
nullstring :=
space := $(nullstring) # end of the line
这样,变量“space”就表示一个空格,这样做的好处是,如果直接展开式中包含空格,使用这种方式可以很明确的表示出来,而避免发生一些错误,例如
dir := /foo/bar # directory to put the frobs in
变量“dir”的值是“/foo/bar ”(后面有4个空格)
这样在使用变量dir的时候,就会出现错误,例如“$(dir)/file”就可能不是我们想要的结果
3.2.2 "?="操作符
操作符“?=”在GNU make中被称为条件赋值,表示只有此变量在之前没有赋值的情况下才会对这个变量进行赋值
例如
FOO ?= bar
表示如果变量"FOO"在之前没有定义,那么久给他赋值"bar",否则就不赋值
等价于
ifeq ($(origin FOO), undefined)
FOO = bar
endif
4 变量的高级用法
4.1 变量的替换引用
对于一个已经定义的变量,可以使用"替换引用"将其值中的后续字符使用指定的字符串替换,格式为"$(var:A=B)"
表示变量"VAR"中所有的A字符结尾的字符替换为B,例如
foo := a.o b.o c.o
bar := $(foo:.o=.c)
就是将变量中以"O"结尾的字符替换为"c"
注意:字符是以空格相隔的,并且只替换结尾,如果存在"o.o"那么替换后就是"o.c"
4.2 变量的嵌套引用
一个变量名中可以包含其他变量的引用,称为“变量的嵌套引用”
例如
x=y
y=z
a := $($(x))
递归变量的套嵌引用过程,也可以包含变量的修改引用和函数调用,例如
x = variable1
variable2 := Hello
y = $(subst 1,2,$(x))
z=y
a := $($($(z)))
注意:实际使用中,不要这样使用,变量的嵌套最好不超过两层,避免造成混乱
- 一个变量也可以对其他多个变量进行引用,就是一个变量名可以由多个变量名的引用加字符串组成
5 变量取值
一个变量可以通过几种方式来赋值
- 在make时通过命令行选项来取代一个已经定义好的变量值
- 在makefile文件中通过赋值的方式或者使用“define”来为一个变量赋值
- 将变量设置为系统环境变量。所有系统环境变量都可以被make使用
- 自动化变量,在不同的规则中自动化变量会被赋予不同的值。它们每一个都
有单一的习惯性用法 - 一些变量具有固定的值
6 如何设置变量
Makefile中变量的设置(也可以称之为定义)是通过“=”(递归方式)或者“:=”(静态方式)来实现的。“=”和“:=”左边的是变量名,右边是变量的值
定义一个变量需要注意以下几点
- 变量名之中可以包含函数或者其它变量的引用,make在读入此行时根据已定义
情况进行替换展开而产生实际的变量名 - 变量的定义值在长度上没有限制,多行通过反斜杠连接
- 当引用一个没有定义的变量时,make默认它的值为空
- 一些特殊的变量在make中有内嵌固定的值(隐含变量)
- 一些由两个符号组成的特殊变量,称之为自动环变量,这些变量不能在Makefile中显示修改
- 如果你希望实现这样一个操作,仅对一个之前没有定义过的变量进行赋值,那么最好使用"?="
7 追加变量
一个变量在定义之后,还可以对其进行追加,在Makefile中使用"+="操作
例如
objects = main.o foo.o bar.o utils.o
objects += another.o
等于
objects = main.o foo.o bar.o utils.o
objects := $(objects) another.o
但是,这两种方式还是存在差异
- 如果被追加值的变量之前没有定义,那么,“+=”会自动变成“=”,此变量就被定义为一个递归展开式的变量
- 直接展开式变量的追加过程
variable := value
variable += more
它的过程就是首先替换展开之前此变量的值,然后在末尾添加需要追加的值,最后使用":="连接
所以等价于
variable := value
variable := $(variable) more
- 递归展开式变量的追加过程
variable = value
variable += more
它的过程是,首先一个变量使用"="定义,之后"+="操作时不对之前变量进行展开,直接替换文本,并且添加追加值,然后使用“=”进行赋值
等价于
temp = value
variable = $(temp) more
8 override指示符
如果在执行make时定义了一个变量,那么它将代替Makefile中出现的同名变量。如果不希望make命令指定的变量代替Makefile中的变量定义,那么需要在Makefile中使用"override"来对变量进行声明
例如
override VARIABLE = VALUE
注意:如果变量在定义时使用了”override“,那么在后续对它值进行追加时,也需要使用"override",否则变量值追加不生效
9 多行定义
使用“define”指示可以定义一个包含多行字符串的变量,利用它的这个特点可以实现一个完整命令包的定义
关于"define"的语法,有以下几点
- “define”定义变量的语法格式:以指示符“define”开始,“endif”结束,之间的所有内容就是所定义变量的值。变量名和“define”在同一行,下一行到endif”上一行都是变量值
例如
define two-lines
echo foo
echo $(bar)
endef
将变量“two-lines”作为命令包执行时,等价于
two-lines = echo foo; echo $(bar)
- 变量的风格:使用“define”定义的变量和使用“=”定义的变量一样,属于“递归展开”式的变量,两者只是在语法上不同
- 可以套嵌引用。因为是递归展开式变量,所以在嵌套引用时“$(x)”将是变量的值的一部分
- 变量值中可以包含:换行符、空格等特殊符号(注意如果定义中某一行是以[Tab]字符开始时,当引用此变量时这一行会被作为命令行来处理)
- 可以使用“override”在定义时声明变量:这样可以防止变量的值被命令行指定的值替代
例如
override define two-lines
foo
$(bar)
endef
10 系统环境变量
在make运行时,系统中所有的环境变量对它都是可见的,在Makefile中,可以引用任何已定义的系统环境变量
这里我们区分系统环境变量和 make 的环境变量,系统环境变量是这个系统所有用户所拥有的,而make的环境变量只是对于make的一次执行过程有效,以下正文中出现没有限制的“环境变量”时默认指的是“系统环境变量”,
我们可以设置一个命名为“CFLAGS” 的环境变量,用它来指定一个默认的编译选项。就可以在所有的Makefile中直接使用这个变量来对c源代码就行编译
使用系统环境变量需要注意以下几点
- 在Makefile中对一个变量的定义或者以make命令行形式对一个变量的定义,
都将覆盖同名的环境变量 - make的递归调用中,所有的系统环境变量会被传递给下一级make
- 一个比较特殊的是环将变量“SHELL”。
在系统中这个环境变量的用途是用来指定用户和系统的交互接口,显然对于make是不合适的。因此make的执行环境 变量“SHELL”没有使用同名的环境变量定义,而是“/bin/sh”。make默认“/bin/sh”作为它的命令行解释程序,make在执行之前将变量“SHELL”设置为“/bin/sh”
除非必须,否则在你的 Makefile 中不要重置环境变量“SHELL”的值。因 为一个不正确的命令行解释程序可能会导致规则定义的命令执行失败,甚至是无法执 行!当需要重置它时,必须有充分的理由和配套的规则命令来适应这个新指定的命令行解释程序
11 目标指定变量
在Makefile中定义一个变量,那么这个变量对此Makefile的所有规则都是有效的。它就像是一个“全局的”变量(仅限于定义它的那个Makefile中的所有规则,如果需要对其它的Makefile中的规则有效,就需要使用“export”对它进行声明。类似于c语言中的全局静态变量,使用static声明的全局变量),自动化变量”除外
一个特殊的变量定义就是所谓的“目标指定变量,此特性允许对于相同变量根据目标指定不同的值。目标指定的变量值只在指定它的目标的上下文中有效,对于其他的目标没有影响。就是说目标指定的变量具有只对此目标上下文有效的“局部性”
11.1 目标变量的特点
- 可以使用任何一个有效的赋值方式,“=”(递归)、“:=”(静态)、“+=”(追加)或者“?=”(条件)
- 使用目标指定变量值时,目标指定的变量值不会影响同名的那个全局变量的值
- 目标指定变量和普通变量具有相同的优先级
- 目标指定的变量和同名的全局变量属于两个不同的变量,它们在定义的风格(递归展开式和直接展开式)上可以不同
- 目标指定的变量变量会作用到由这个目标所引发的所有的规则中去
关于第三点:
当我们使用make命令行的方式定义变量时,命令行中的定义将替代目标指定的同名变量定义(和普通的变量一样会被覆盖),当使用make的“-e”选项时,同名的环境变量也将覆盖目标指定的变量定义
所以为了防止目标指定的变量定义被覆盖,可以使用第二种格式,使用指符“override”对目标指定的变量进行声明
例如:
prog : CFLAGS = -g
prog : prog.o foo.o bar.o
无论Makefile中的全局变量“CFLAGS”的定义是什么。对于目 标“prog”以及其所引发的所有(包含目标为“prog.o”、“foo.o”和“bar.o”的所有 规则)规则,变量“CFLAGS”值都是“-g”
使用目标指定变量可以实现对于不同的目标文件使用不同的编译参数
12 模式指定变量
模式指定变量定义是将一个变量值指定到所有符合此模式的目标上,设置一个模式指定变量的语法和设置目标变量的语法相似。例如
%.o : CFLAGS += -O
它指定了所有.o 文件的编译选项包含“-O”选项,不改变对其它类型文件的编译选项
在使用模式指定的变量定义时。目标文件一般除了模式字符(%)以外需要包含某种文件名的特征字符(例如:“a%”、“%.o”、“%.a”等)。当单独使用 “%”作为目标时,指定的变量会对所有类型的目标文件有效