makefile变量使⽤及其条件判断
在Makefile中的定义的变量,就像是C/C++语⾔中的宏⼀样,他代表了⼀个⽂本字串,在Makefile中执⾏的时候其会⾃动原模原样地展开在所使⽤的地⽅。其与C/C++所不同的是,你可以在Makefile中改变其值。在Makefile中,变量可以使⽤在“⽬标”,“依赖⽬标”,“命令”或是Makefile的其它部分中。
变量的命名字可以包含字符、数字,下划线(可以是数字开头),但不应该含有“:”、“#”、“=”或是空字符(空格、回车等)。变量是⼤⼩写敏感的,“foo”、“Foo”和“FOO”是三个不同的变量名。传统的Makefile的变量名是全⼤写的命名⽅式,但我推荐使⽤⼤⼩写搭配的变量名,如:MakeFlags。这样可以避免和系统的变量冲突,⽽发⽣意外的事情。
有⼀些变量是很奇怪字串,如“$<”、“$@”等,这些是⾃动化变量,我会在后⾯介绍。
⼀、变量的基础
变量在声明时需要给予初值,⽽在使⽤时,需要给在变量名前加上“$”符号,但最好⽤⼩括号“()”或是⼤括号“{}”把变量给包括起来。如果你要使⽤真实的“$”字符,那么你需要⽤“$$”来表⽰。
变量可以使⽤在许多地⽅,如规则中的“⽬标”、“依赖”、“命令”以及新的变量中。先看⼀个例⼦:
objects = program.o foo.o utils.o
program : $(objects)
cc -o program $(objects)
$(objects) : defs.h
变量会在使⽤它的地⽅精确地展开,就像C/C++中的宏⼀样,例如:
foo = c
欢送会祝福语
prog.o : prog.$(foo)
$(foo)$(foo) -$(foo) prog.$(foo)
展开后得到:
prog.o : prog.c
cc -c prog.c
当然,千万不要在你的Makefile中这样⼲,这⾥只是举个例⼦来表明Makefile中的变量在使⽤处展开的真实样⼦。可见其就是⼀个“替代”的原理。
另外,给变量加上括号完全是为了更加安全地使⽤这个变量,在上⾯的例⼦中,如果你不想给变量加上括号,那也可以,但我还是强烈建议你给变量加上括号。
⼆、变量中的变量
小小猫和老鼠在定义变量的值时,我们可以使⽤其它变量来构造变量的值,在Makefile中有两种⽅式来在⽤变量定义变量的值。
先看第⼀种⽅式,也就是简单的使⽤“=”号,在“=”左侧是变量,右侧是变量的值,右侧变量的值可以定义在⽂件的任何⼀处,也就是说,右侧中的变量不⼀定⾮要是已定义好的值,其也可以使⽤后⾯定义的值。如:
foo = $(bar)
bar = $(ugh)
ugh = Huh?
all:
echo $(foo)
我们执⾏“make all”将会打出变量$(foo)的值是“Huh?”( $(foo)的值是$(bar),$(bar)的值是$(ugh),$(ugh)的值是“Huh?”)可见,变量是可以使⽤后⾯的变量来定义的。
这个功能有好的地⽅,也有不好的地⽅,好的地⽅是,我们可以把变量的真实值推到后⾯来定义,如:
CFLAGS = $(include_dirs) -O
include_dirs = -Ifoo -Ibar
当“CFLAGS”在命令中被展开时,会是“-Ifoo -Ibar -O”。但这种形式也有不好的地⽅,那就是递归定义,如:
CFLAGS = $(CFLAGS) -O
或:
A = $(B)
B = $(A)
这会让make陷⼊⽆限的变量展开过程中去,当然,我们的make是有能⼒检测这样的定义,并会报错。还有就是如果在变量中使⽤函数,那么,这种⽅式会让我们的make运⾏时⾮常慢,更糟糕的是,他会使⽤得两个make的函数“wildcard”和“shell”发⽣不可预知的错误。因为你不会知道这两个函数会被调⽤多少次。
为了避免上⾯的这种⽅法,我们可以使⽤make中的另⼀种⽤变量来定义变量的⽅法。这种⽅法使⽤的是“:=”操作符,如:
x := foo
y := $(x) bar
x := later
其等价于:
y := foo bar
x := later
值得⼀提的是,这种⽅法,前⾯的变量不能使⽤后⾯的变量,只能使⽤前⾯已定义好了的变量。如果是这样:
y := $(x) bar
x := foo
那么,y的值是“bar”,⽽不是“foo bar”。
上⾯都是⼀些⽐较简单的变量使⽤了,让我们来看⼀个复杂的例⼦,其中包括了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
关于条件表达式和函数,我们在后⾯再说,对于系统变量“MAKELEVEL”,其意思是,如果我们的make有⼀个嵌套执⾏的动作(参见前⾯的“嵌套使⽤make”),那么,这个变量会记录了我们的当前Makefile的调⽤层数。
下⾯再介绍两个定义变量时我们需要知道的,请先看⼀个例⼦,如果我们要定义⼀个变量,其值是⼀个空格,那么我们可以这样来:
nullstring :=
space := $(nullstring) # end of the line
nullstring是⼀个Empty变量,其中什么也没有,⽽我们的space的值是⼀个空格。因为在操作符的右边是很难描述⼀个空格的,这⾥采⽤的技术很管⽤,先⽤⼀个Empty变量来标明变量的值开始了,⽽后⾯采⽤“#”注释符来表⽰变量定义的终⽌,这样,我们可以定义出其值是⼀个空格的变量。请注意这⾥关于“#”的使⽤,注释符“#”的这种特性值得我们注意,如果我们这样定义⼀个变量:
dir := /foo/bar # directory to put the frobs in
dir这个变量的值是“/foo/bar”,后⾯还跟了4个空格,如果我们这样使⽤这样变量来指定别的⽬录——“$(dir)/file”那么就完蛋了。
还有⼀个⽐较有⽤的操作符是“?=”,先看⽰例:
FOO ?= bar
其含义是,如果FOO没有被定义过,那么变量FOO的值就是“bar”,如果FOO先前被定义过,那么这条语将什么也不做,其等价于:
ifeq ($(origin FOO), undefined)
FOO = bar
endif
三、变量⾼级⽤法
这⾥介绍两种变量的⾼级使⽤⽅法,第⼀种是变量值的替换。
我们可以替换变量中的共有的部分,其格式是“$(var:a=b)”或是“${var:a=b}”,其意思是,把变量“var”
中所有以“a”字串“结尾”的“a”替换成“b”字串。这⾥的“结尾”意思是“空格”或是“结束符”。
还是看⼀个⽰例吧:
foo := a.o b.o c.o
bar := $(foo:.o=.c)
这个⽰例中,我们先定义了⼀个“$(foo)”变量,⽽第⼆⾏的意思是把“$(foo)”中所有以“.o”字串“结尾”全部替换成“.c”,所以我们的“$(bar)”的值就是“a.c b.c c.c”。
另外⼀种变量替换的技术是以“静态模式”(参见前⾯章节)定义的,如:
foo := a.o b.o c.o
bar := $(foo:%.o=%.c)
这依赖于被替换字串中的有相同的模式,模式中必须包含⼀个“%”字符,这个例⼦同样让$(bar)变量的值为“a.c b.c c.c”。
第⼆种⾼级⽤法是——“把变量的值再当成变量”。先看⼀个例⼦:
x = y
y = z
a := $($(x))
在这个例⼦中,$(x)的值是“y”,所以$($(x))就是$(y),于是$(a)的值就是“z”。(注意,是“x=y”,⽽不是“x=$(y)”)
我们还可以使⽤更多的层次:
x = y
y = z
z = u
a := $($($(x)))
这⾥的$(a)的值是“u”,相关的推导留给读者⾃⼰去做吧。
让我们再复杂⼀点,使⽤上“在变量定义中使⽤变量”的第⼀个⽅式,来看⼀个例⼦:
x = $(y)
y = z
z = Hello
a := $($(x))
这⾥的$($(x))被替换成了$($(y)),因为$(y)值是“z”,所以,最终结果是:a:=$(z),也就是“Hello”。
再复杂⼀点,我们再加上函数:
x = variable1
variable2 := Hello
y = $(subst 1,2,$(x))
z = y
a := $($($(z)))
这个例⼦中,“$($($(z)))”扩展为“$($(y))”,⽽其再次被扩展为“$($(subst 1,2,$(x)))”。$(x)的值是“variable1”,subst函数把“variable1”中的所
有“1”字串替换成“2”字串,于是,“variable1”变成“variable2”,再取其值,所以,最终,$(a)的值就是$(variable2)的值——“Hello”。(喔,好不容易)
在这种⽅式中,或要可以使⽤多个变量来组成⼀个变量的名字,然后再取其值:
first_cond = Hello
a = first
b = cond
all = $($a_$b)
这⾥的“$a_$b”组成了“first_cond”,于是,$(all)的值就是“Hello”。
再来看看结合第⼀种技术的例⼦:
a_objects := a.o b.o c.o
1_objects := 1.o 2.o 3.o
sources := $($(a1)_objects:.o=.c)
这个例⼦中,如果$(a1)的值是“a”的话,那么,$(sources)的值就是“a.c b.c c.c”;如果$(a1)的值是“1”,那么$(sources)的值是“1.c 2.c 3.c”。
再来看⼀个这种技术和“函数”与“条件语句”⼀同使⽤的例⼦:
ifdef do_sort
func := sort
el
func := strip
endif王拱辰
bar := a d b g q c
foo := $($(func) $(bar))
这个⽰例中,如果定义了“do_sort”,那么:foo := $(sort a d b g q c),于是$(foo)的值就是“a b c d g q”,⽽如果没有定义“do_sort”,那么:
foo := $(sort a d b g q c),调⽤的就是strip函数。
当然,“把变量的值再当成变量”这种技术,同样可以⽤在操作符的左边:
dir = foo
$(dir)_sources := $(wildcard $(dir)/*.c)
define $(dir)_print
lpr $($(dir)_sources)
endef
这个例⼦中定义了三个变量:“dir”,“foo_sources”和“foo_print”。
四、追加变量值
京东发展历程我们可以使⽤“+=”操作符给变量追加值,如:
objects = main.o foo.o bar.o utils.o
objects += another.o
于是,我们的$(objects)值变成:“main.o foo.o bar.o utils.o another.o”(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
由于前次的赋值符是“=”,所以“+=”也会以“=”来做为赋值,那么岂不会发⽣变量的递补归定义,这是很不好的,所以make会⾃动为我们解决这个问题,我们不必担⼼这个问题。
我的计划五、override 指⽰符
如果有变量是通常make的命令⾏参数设置的,那么Makefile中对这个变量的赋值会被忽略。如果你想在Makefile中设置这类参数的值,那么,你可以使⽤“override”指⽰符。其语法是:
override <variable> = <value>
override <variable> := <value>
当然,你还可以追加:
override <variable> += <more text>
对于多⾏的变量定义,我们⽤define指⽰符,在define指⽰符前,也同样可以使⽤ovveride指⽰符,如:
override define foo
bar
endef
六、多⾏变量
还有⼀种设置变量值的⽅法是使⽤define关键字。使⽤define关键字设置变量的值可以有换⾏,这有利于定义⼀系列的命令(前⾯我们讲100days
过“命令包”的技术就是利⽤这个关键字)。
define指⽰符后⾯跟的是变量的名字,⽽重起⼀⾏定义变量的值,定义是以endef关键字结束。其⼯作
⽅式和“=”操作符⼀样。变量的值可以包含函数、命令、⽂字,或是其它变量。因为命令需要以[Tab]键开头,所以如果你⽤define定义的命令变量中没有以[Tab]键开头,那么make就不会把其认为是命令。
下⾯的这个⽰例展⽰了define的⽤法:
define two-lines
echo foo
echo $(bar)
endef
七、环境变量
make运⾏时的系统环境变量可以在make开始运⾏时被载⼊到Makefile⽂件中,但是如果Makefile中已定义了这个变量,或是这个变量由make命令⾏带⼊,那么系统的环境变量的值将被覆盖。(如果make指定了“-e”参数,那么,系统环境变量将覆盖Makefile中定义的变量)因此,如果我们在环境变量中设置了“CFLAGS”环境变量,那么我们就可以在所有的Makefile中使⽤这个变量了。这对于我们使⽤统⼀的编译参数有⽐较⼤的好处。如果Makefile中定义了CFLAGS,那么则会使⽤Makefile中的这个
变量,如果没有定义则使⽤系统环境变量的值,⼀个共性和个性的统⼀,很像“全局变量”和“局部变量”的特性。
当make嵌套调⽤时(参见前⾯的“嵌套调⽤”章节),上层Makefile中定义的变量会以系统环境变量的⽅式传递到下层的Makefile中。当然,默认情况下,只有通过命令⾏设置的变量会被传递。⽽定义在⽂件中的变量,如果要向下层Makefile传递,则需要使⽤exprot关键字来声明。(参见前⾯章节)
当然,我并不推荐把许多的变量都定义在系统环境中,这样,在我们执⾏不⽤的Makefile时,拥有的是同⼀套系统变量,这可能会带来更多的⿇烦。
⼋、⽬标变量
前⾯我们所讲的在Makefile中定义的变量都是“全局变量”,在整个⽂件,我们都可以访问这些变量。当然,“⾃动化变量”除外,如“$<”等这种类量的⾃动化变量就属于“规则型变量”,这种变量的值依赖于规则的⽬标和依赖⽬标的定义。
当然,我样同样可以为某个⽬标设置局部变量,这种变量被称为“Target-specific Variable”,它可以和“全局变量”同名,因为它的作⽤范围只在这条规则以及连带规则中,所以其值也只在作⽤范围内有效。⽽不会影响规则链以外的全局变量的值。
其语法是:
<target ...> : <variable-assignment>
<target ...> : overide <variable-assignment>
<variable-assignment>可以是前⾯讲过的各种赋值表达式,如“=”、“:=”、“+=”或是“?=”。第⼆个语法是针对于make命令⾏带⼊的变量,或是系统环境变量。
这个特性⾮常的有⽤,当我们设置了这样⼀个变量,这个变量会作⽤到由这个⽬标所引发的所有的规则中去。如:
prog : CFLAGS = -g
prog : prog.o foo.o bar.o
$(CC) $(CFLAGS) prog.o foo.o bar.o
prog.o : prog.c
$(CC) $(CFLAGS) prog.c
foo.o : foo.c
$(CC) $(CFLAGS) foo.c
bar.o : bar.c
$(CC) $(CFLAGS) bar.c
在这个⽰例中,不管全局的$(CFLAGS)的值是什么,在prog⽬标,以及其所引发的所有规则中(prog.o foo.o bar.o的规则),$(CFLAGS)的值都是“-g”
九、模式变量
在GNU的make中,还⽀持模式变量(Pattern-specific Variable),通过上⾯的⽬标变量中,我们知道,变量可以定义在某个⽬标上。模式变量的好处就是,我们可以给定⼀种“模式”,可以把变量定义在符合这种模式的所有⽬标上。
我们知道,make的“模式”⼀般是⾄少含有⼀个“%”的,所以,我们可以以如下⽅式给所有以[.o]结尾的⽬标定义⽬标变量:
%.o : CFLAGS = -O
同样,模式变量的语法和“⽬标变量”⼀样:
<pattern ...> : <variable-assignment>
<pattern ...> : override <variable-assignment>
override同样是针对于系统环境传⼊的变量,或是make命令⾏指定的变量。