@:抑制回显(echo)标志
一般来说,一个Make rule的格式如下:
targets : prerequisites
command
...
比如,
foo.o : foo.c defs.h # module for twiddling the frobs
cc -c -g foo.c
ar foo.a foo.o
当Make对这条rule进行处理时,会执行下面的命令,在执行之前,会把命令本身输出到屏幕上,比如:
$make
cc –c –g foo.c
ar foo.a foo.o
….
有时候,我们并不想让这些命令输出到屏幕上,这一点,可以通过在每一个命令之前加上抑制回显标志“@”来实现。对于之前的例子,
foo.o : foo.c defs.h # module for twiddling the frobs
@cc -c -g foo.c
@ar foo.a foo.o
这样,当make对这条规则进行处理的时候,这两个命令就不会被输出到屏幕上。
函数(function)
Makefile的函数调用语法为:
$(function arguments) 或者 ${function arguments}
函数名和参数之间由至少1个空格或Tab分开,如果参数有多个,参数之间由逗号分割。例如:
$(subset EE,ee,I feel painful with my feet)
foreach函数
foreach函数就像C语言中的for循环,或者sh中的foreach一样,用来遍历一个list,并对list中的每一个元素进行相同的操作。
foreach函数的格式如下:
$(foreach var,list,text)
var就是进行迭代遍历的iterator,list是被foreach操作的对象,每次迭代,var被赋予 list中的一个item。text就是要进行的操作,它可以是一个function call,也可以是一组命令。一般来说,text中的操作需要引用var。例如:
dirs = a b c d
files = $(foreach dir, $(dirs),$(wildcard $(dir)/*))
经过这个计算后,files的值为a,b,c,d四个目录下的所有文件组成的list,这些文件之间以空格分开。
dir函数
dir函数的格式为 $(dir names...)
names是一个包含多个文件名的list,这些文件名可以是带有路径的,也可以仅仅是一个文件名。dir函数对这个list中的每一个文件进行操作,依次取出其路径部分,并组成一个list作为返回值。如果names中的某个文件包含路径,则被认为是当前路径"./"。需要注意的是,如果这些路径有重复的,dir函数并不会进行合并。例如:
dirs = $(dir a/b a/c d)
则dirs的值为"a/ a/ ./"
notdir函数
notdir函数的格式为$(notdir names...)
notdir函数和dir函数的功能正好相反,它将names list中每一个文件名的路径部分去掉,返回文件部分。例如:
files = $(notdir a/b b/c d)
则files的值为"b c d"
basename函数
basename函数的格式为 $(basename names...)
names是一个包含多个文件名的list,这些文件名可以是带有路径的,也可以仅仅是一个文件名。basename函数会把这些文件名的扩展名去掉,返回处理后的list。比如
files_prefix = $(basename a.c/b.c a.c/b.c.c c.c a.c/d)
执行后,files_prefix的值为"a.c/b a.c/b.c c a.c/d"
word函数
word函数的格式为 $(word n,text)
word函数去text list中的第n个word。如果n大于text中word的数量,则返回空字符串。例如:
word = $(word 2, male female unknown)
则word为female。
words函数
words函数的格式为:$(words text)
words函数返回text中word的数量。比如,$(word $(words, text), text)则返回text list中的最后一个word。
“=”,“:=”和“?=”
在一些Makefile中,会看到有些变量使用“=”赋值,有些变量使用“:=”赋值,比如:
FOO = foo
BAR := bar
它们之间到底有什么不同?
对于上面的例子来说,它们没有任何不同。但对于下面这个例子来说,情况则大不相同。
C_OPTIONS = $(C_EXTRA_OPTION) -O2
C_EXTRA_OPTION = -g
CPP_OPTIONS := $(CPP_EXTRA_OPTION) -O2
CPP_EXTRA_OPTION = -g
all: c_foo cpp_foo
cfoo: foo.c exam.c
gcc $(C_OPTIONS) -o $@ $^
cppfoo: foo.cpp exam.cpp
g++ $(CPP_OPTIONS) -o $@ $^
这个例子中,编译cfoo时的编译命令实际上是:
gcc -g -O2 -o cfoo foo.c exam.c
编译cppfoo的编译命令实际上是:
g++ -O2 -o cppfoo foo.cpp exam.cpp
为什么会这样?
答案是:使用“=”赋值的变量在使用时才被展开,并且每使用一次就会展开一次,其值每次展开的时候有可能是不同的。而使用“:=”进行赋值的变量,则在赋值的时候就被展开,并且仅仅展开一次,从此以后其值将不会发生任何变化。这有点像C++的动态绑定和编译时绑定之间的不同。
我们还是用例子来说明问题,先看一个“=”的例子,加入一个Makefile的内容如下:
FOO = what is $(BAR)
FOO += , huh
show_1:
@echo $(FOO)
BAR = bar
show_2:
@echo $(FOO)
FOO += ?
无论我们执行make show_1还是make show_2,结果均为“what is bar,huh?”。因为Make首先读取了整个Makefile,然后在执行某个规则的时候,即时对这个规则所引用到的以“=”赋值的变量进行展开。
如果一个以“=”赋值的变量所引用的变量,以及这些变量所引用的变量……一直递归下去,最终都是静态赋值的,即没有调用任何可能引起不确定结果的函数,比如shell或wildcard函数,也没有引用任何自动变量。尽管即每次被引用时都会被扩展一次,但其值在全局范围内是一致的。如果不是这样,则其值在不同的引用中有可能是不同的。比如:
DEPENDS = $<
show_1: a.c
@echo $(DEPENDS)
show_2: d.c
@echo $(DEPENDS)
在这个例子中,如果我们执行make show_1,结果为a.c,如果我们执行make show_2,结果为d.c。
如果我们把上例中的DEPENDS = $< 改为 DEPENDS := $<,则无论我们执行make show_1还是make show_2,结果都是空。
对于以 “=”赋值的变量,我们称之为“递归扩展”(recursively expanded)变量,对于以“:=”赋值的变量,我们称之为“简单扩展”(simply expanded)变量。
递归扩展变量的主要特点是,引用时扩展。这一点允许它所引用的变量可以被定义在任何位置。如果它所引用的变量是静态的,则扩展时这个变量最终被确定的值。如果这个变量来自自动变量或者来自函数调用,则引用它在扩展时计算的的值。
引用时扩展是一把双刃剑,它的缺点和优点一样鲜明。它的主要缺点之一是性能。由于它每次被引用时都会扩展一次,所以对于那些你很明确它的值就是在它被定义那一刻的值的变量来说,引用时扩展是一笔很大的开销。另外一个缺点就是,如果它引用了shell函数或wildcard函数,则其值有时候是不可预测的。
所以,两种赋值方法各有其优缺点,但正想我们在使用C++时,决定一个类函数是否声明为virtual一样,我们需要考虑清楚我们究竟需要一个什么类型的变量。
除了以上两种赋值方法以外,还可以用“?=”来对一个变量进行赋值。它事实上带了条件判断,即如果需要被赋值的那个变量值之前被有被定义,则将其赋为后面的值。比如:
FOO ?= foo
等价于:
ifeq($(origin FOO), undefined)
FOO = bar
endif
Phony Targets
所谓Phony Targets指那些Target Name并不是一个真正文件名的Target。比如:
$(TARGETS) = foo
all: $(TARGETS)
clean:
rm –f *.o core* $(TARGETS)
foo: foo.c other.c
$(CC) $(CFLAGS) –o $@ $^
其中all,clean都是Phony Targets,因为当你执行make all或者make clean时,并不会生成名为all或clean的文件。
对于all,由于其依赖$(TARGETS),在执行make all的时候,make会自动检测$(TARGETS)是否有更新,由于$(TARGET)依赖foo.c 和 other.c,make会继续检测这两个文件是否有更新(这事实上是一个递归检测的过程),如果有,则重新执行相关规则中的命令。如果没有,则什么都不做。
对于clean,由于其没有任何依赖,所以,每次执行make clean的时候,都会造成rm –f *.o core* foo 的执行。
有一个问题是,如果当前目录中已经存在一个名为clean的文件,当你执行make clean的时候,由于目标clean没有任何依赖,所以make认为clean没有任何更新,所以目标clean所相关的命令不会得到执行。
解决这个问题的方法是使用一个特殊的名为.PHONY的目标,并把目标clean放入其依赖列表中。如下:
.PHONY: clean
这样,make就知道clean不是一个文件名,所以它也不会去检测是否存在名为clean的文件以及它是否更新了。而是直接去检测其依赖列表中的item是否有更新并决定是否执行其相关命令,如果根本没有任何依赖,则直接执行其命令。