在定义变量的值时,我们可以使用其它变量来构造变量的值,在 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
有一个嵌套执行的动作,那么,这个变量会记录了我们的当前 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