#使用变量
在Makefile中定义的变量, 与C中的宏相同, 他代表了一个文本串,在Makefile中执行的时候
其会自动地展开在使用的地方. 与C所不同的是, 你可以在Makefile中改变其值. 在Makefile中,
变量可以使用在 "目标", "依赖目标", "命令" 或是Makefile的其它部分中.
变量的命名可以包含字符, 数字, 下划线(可以是数字开头), 但不应该含有 ':', '#', '='
或是空字符(空格, 回车等). 变量是大小写敏感的, "foo", "Foo" 和 "FOO" 是三个不同的变量
传统Makefile的变量名是全大写的命名方式. 但推荐使用大小写搭配的变量名, 如: MakeFlags.
这样可以避免和系统的变量冲突, 而发生意外的事情.
有一些变量是很奇怪字串, 如 '$<', '$@' 等, 这些是自动变量, 后面将会介绍.
一. 变量的基础
变量在声明时需要给予初值, 而在使用时,需要给在变量名前加上 '$' 符号. 但最好用小括
号 "()" 或是大括号 "{}" 把变量给包括起来. 如果使用真实的 '$' 字符,那么需要用 "$$" 来
表示.
变量可以使用在许多地方, 如规则中的 "目标", "依赖", "命令" 以及新的变量中. 例如:
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#Exampke make start
objects = program.o foo.o utils.o
program: $(objects)
cc -o program $(objects)
$(objects): defs.h
#Example make end
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
变量会在使用它的地方精确地展开, 就像C中的宏, 例如:
foo = c
prog.o: prog.$(foo)
$(foo)$(foo) -$(foo) prog.$(foo)
展开后得到:
prog.o: prog.c
cc -c prog.c
给变量加上括号完全是为了更加安全地使用这个变量.
二. 变量中的变量
在定义变量的值时, 我们可以使用其它变量来构造变量的值, 在Makefile中有两种方式来在
用变量定义变量的值.
1. 就是简单的使用 '=' 号
在 '=' 左侧是变量, 右侧是变量的值, 右侧变量
的值可以定义在文件的任何一处, 也就是说, 右侧中的变量不一定非要是已定义好的值, 其也可
以使用后面定义的值. 如:
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#Exampke make start
foo = $(bar)
bar = $(ugh)
ugh = Huh?
all:
@echo $(foo)
#Example make end
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
执行make, 可以看到输出结果是 "Huh?", 可见, 变量是可以使用后面的变量来定义的, 这是有别
于C的地方.
关于这种用法的利弊分析如下:
利用该方法, 我们可以把变量值放到后面来定义, 如:
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#Exampke make start
CFLAGS = $(include_dirs) -O
include_dirs = -Ifoo -Ibar
# 当 "CFLAGS" 被展开时, 会是 "-Ifoo -Ibar -O".
#Example make end
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
但这种形式也有不好的地方, 就是产生递归定义.
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#Exampke make start
CFLAGS = $(CFLAGS) -O
#或:
A = $(B)
B = $(A)
#Example make end
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
这让make陷入无限的变量展开过程中, 当然, make可以检测这样的定义, 并会报错. 如果在变量
中使用函数, 这种方式会让我们的make运行时非常慢, 更糟糕的是, 他会使用得两个make的函数
"wildcard" 和 "shell" 发生不可预知的错误. 因为无法预知这两个函数会被调用多少次.
为了避免上面这种情况的发生, 我们可以使用make中的另一种用变量来定义变量的方法.
2. 使用 ":=" 操作符
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#Exampke make start
x := foo
y := $(x) bar
x := later
#等价于:
y := foo bar
x := later
#Example make end
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
使用这种方法, 前面的变量不能使用后面的变量, 只能使用前面已定义好了的变量, 如果是这样
y := $(x) bar
x := foo
那么, y的值是 "bar", 而不是 "foo bar".
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#Exampke make start
#该例包括了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
#Example make end
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
系统变量 "MAKELEVEL" 记录了我们的当前Makefile的调用层数.
如果我们要定义一个变量, 其值是一个空格, 那么我们可以这样来:
nullstring :=
space := $(nullstring) ##
nullstring是一个Empty变量, 我们的space的值是一个空格. 因为在操作符的右边是很难描述一
个空格的, 以先用一个Empty变量来标明变量的值开始了, 而后面采用 '#' 注释符来表示变量
定义的终止, 这样, 我们可以定义出其值是一个空格的变量. 请注意这里关于 '#' 的使用,注释
符 '#' 的这种特性值得我们注意, 如果我们这样定义一个变量:
dir := /foo/bar ##
dir变量值是 "/foo/bar", 后面还跟了4个空格, 使用该变量来指定别的目录 "$(dir)/file" 那
么结果便是错误的.
3. 比较有用的操作符是 "?="
FOO ?= bar,含义是, 如果FOO没有被定义过, 那么变量FOO的值就是 "bar", 如果FOO先前被
定义过, 那么这条语句无效. 其等价于:
ifeq ($(origin FOO), undefined)
FOO = bar
endif
三. 变量高级用法
1. 变量值的替换
我们可以替换变量中的共有的部分,其格式是 "$(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)
##注意函数操作中间部内包括空格.
2. 把变量的值再当成变量
例3-1:
x = y
y = z
a := $($(x))
在这个例子中, $(x)的值是 'y', 所以$($(x))就是$(y), 于是$(a)的值就是 'z'.
我们还可以使用更多的层次:
x = y
y = z
z = u
a := $($($(x)))
这里的$(a)的值是 'u'.
例3-2: 使用 "在变量定义中使用变量" 的第一个方式
x = $(y)
y = z
z = Hello
a := $($(x))
这里的$($(x))被替换成了$($(y)), 因为$(y)值是 'z', 所以, 最终结果是: a:=$(z),也就
是 "Hello".
例3-3: 使用函数
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_second = Hello
a = first
b = second
all = $($a_$b)
这里的 "$a_$b" 组成了 "first_second", 于是, $(all)的值就是 "Hello".
例3-4: 结合第一种技术
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".
例3-5: "函数" 与 "条件语句" 一同使用
ifdef do_sort
func := sort
else
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 := $(strip a d b g q c),调
用的就是strip函数.
例3-6: "把变量的值再当成变量" 同样可以用在操作符的左边
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"
使用 "+=" 操作符, 可以模拟为下面的这种例子:
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关键字设置变量的值可以有换
行, 这有利于定义一系列的命令, 前面我们讲过 "命令包" 的技术就是利用这个关键字.
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命令行带入的变量, 或是系统环境变量.
这个特性非常的有用, 当我们设置了这样一个变量: 这个变量会作用到由这个目标所引发的
所有的规则中去. 如:
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#Exampke make start
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
#Example make end
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
在这个示例中, 不管全局的$(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命令行指定的变量.
【2006-12-25】
文章评论(0条评论)
登录后参与讨论