3. 变量
这一节我们详细看看 Makefile 中关于变量的语法规则。先看一个简单的例子:
foo = $(bar)
bar = Huh?
all:
@echo $(foo)
我们执行 make 将会打出 Huh? 。当 make 读到 foo = $(bar) 时,确定 foo 的值是 $(bar) ,但并不立即展开 $(bar) ,然后读到 bar = Huh? ,确定 bar 的值是 Huh? ,然后在执行规则 all: 的命令列表时才需要展开 $(foo) ,得到 $(bar) ,再展开 $(bar) ,得到 Huh? 。因此,虽然 bar 的定义写在 foo 之后, $(foo) 展开还是能够取到 $(bar) 的值。
这种特性有好处也有坏处。好处是我们可以把变量的值推迟到后面定义,例如:
main.o: main.c
$(CC) $(CFLAGS) $(CPPFLAGS) -c $<
CC = gcc
CFLAGS = -O -g
CPPFLAGS = -Iinclude
编译命令可以展开成 gcc -O -g -Iinclude -c main.c 。通常把 CFLAGS 定义成一些编译选项,例如 -O 、 -g 等,而把 CPPFLAGS 定义成一些预处理选项,例如 -D 、 -I 等。用 = 号定义变量的延迟展开特性也有坏处,就是有可能写出无穷递归的定义,例如 CFLAGS = $(CFLAGS) -O ,或者:
A = $(B)
B = $(A)
当然, make 有能力检测出这样的错误而不会陷入死循环。有时候我们希望 make 在遇到变量定义时立即展开,可以用 := 运算符,例如:
x := foo
y := $(x) bar
all:
@echo "-$(y)-"
当 make 读到 y := $(x) bar 定义时,立即把 $(x) 展开,使变量 y 的取值是 foo bar ,如果把这两行颠倒过来:
y := $(x) bar
x := foo
那么当 make 读到 y := $(x) bar 时, x 还没有定义,展开为空值,所以 y 的取值是 ␣bar ,注意 bar 前面有个空格。一个变量的定义从 = 后面的第一个非空白字符开始(从 $(x) 的 $ 开始),包括后面的所有字符,直到注释或换行之前结束。如果要定义一个变量的值是一个空格,可以这样:
nullstring :=
space := $(nullstring) # end of the line
nullstring 的值为空,space 的值是一个空格,后面写个注释是为了增加可读性,如果不写注释就换行,则很难看出$(nullstring) 后面有个空格。
还有一个比较有用的赋值运算符是 ?= ,例如 foo ?= $(bar) 的意思是:如果 foo 没有定义过,那么 ?= 相当于 = ,定义 foo 的值是 $(bar) ,但不立即展开;如果先前已经定义了 foo ,则什么也不做,不会给 foo 重新赋值。
+= 运算符可以给变量追加值,例如:
objects = main.o
objects += $(foo)
foo = foo.o bar.o
object 是用= 定义的,+= 仍然保持= 的特性,objects 的值是main.o $(foo) (注意$(foo) 前面自动添一个空格),但不立即展开,等到后面需要展开$(objects) 时会展开成main.o foo.o bar.o 。
再比如:
objects := main.o
objects += $(foo)
foo = foo.o bar.o
object 是用:= 定义的,+= 保持:= 的特性,objects 的值是main.o $(foo) ,立即展开得到main.o (这时foo 还没定义),注意main.o 后面的空格仍保留。
如果变量还没有定义过就直接用 += 赋值,那么 += 相当于 = 。
上一节我们用到了特殊变量 $@ 和 $< ,这两个变量的特点是不需要给它们赋值,在不同的上下文中它们自动取不同的值。常用的特殊变量有:
-
$@,表示规则中的目标。 -
$<,表示规则中的第一个条件。 -
$?,表示规则中所有比目标新的条件,组成一个列表,以空格分隔。 -
$^,表示规则中的所有条件,组成一个列表,以空格分隔。
例如前面写过的这条规则:
main: main.o stack.o maze.o
gcc main.o stack.o maze.o -o main
可以改写成:
main: main.o stack.o maze.o
gcc $^ -o $@
这样即使以后又往条件里添加了新的目标文件,编译命令也不需要修改,减少了出错的可能。
$? 变量也很有用,有时候希望只对更新过的条件进行操作,例如有一个库文件libsome.a 依赖于几个目标文件:
libsome.a: foo.o bar.o lose.o win.o
ar r libsome.a $?
ranlib libsome.a
这样,只有更新过的目标文件才需要重新打包到 libsome.a 中,没更新过的目标文件原本已经在 libsome.a 中了,不必重新打包。
在上一节我们看到 make 的隐含规则数据库中用到了很多变量,有些变量没有定义(例如 CFLAGS ),有些变量定义了缺省值(例如 CC ),我们写 Makefile 时可以重新定义这些变量的值,也可以在缺省值的基础上追加。以下列举一些常用的变量,请读者体会其中的规律。
-
AR静态库打包命令的名字,缺省值是
ar。 -
ARFLAGS静态库打包命令的选项,缺省值是
rv。 -
AS汇编器的名字,缺省值是
as。 -
ASFLAGS汇编器的选项,没有定义。
-
CC
C 编译器的名字,缺省值是
cc。 -
CFLAGS
C 编译器的选项,没有定义。
-
CXX
C++编译器的名字,缺省值是
g++。 -
CXXFLAGS
C++编译器的选项,没有定义。
-
CPP
C 预处理器的名字,缺省值是
$(CC) -E。 -
CPPFLAGS
C 预处理器的选项,没有定义。
-
LD
链接器的名字,缺省值是
ld。 -
LDFLAGS
链接器的选项,没有定义。
-
TARGET_ARCH
和目标平台相关的命令行选项,没有定义。
-
OUTPUT_OPTION
输出的命令行选项,缺省值是
-o $@。 -
LINK.o
把
.o文件链接在一起的命令行,缺省值是$(CC) $(LDFLAGS) $(TARGET_ARCH)。 -
LINK.c
把
.c文件链接在一起的命令行,缺省值是$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)。 -
LINK.cc
把
.cc文件(C++源文件)链接在一起的命令行,缺省值是$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)。 -
COMPILE.c
编译
.c文件的命令行,缺省值是$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c。 -
COMPILE.cc
编译
.cc文件的命令行,缺省值是$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c。 -
RM
删除命令的名字,缺省值是
rm -f。