target ... : prerequisites ...
recipe
...
...
A recipe is an action that make carries out.
target过期的时候,recipe会执行,下列任何一种情况被认为是过期的:
1 target对应的文件(或文件夹)不存在。
2 target是PHONY类型。
3 target存在依赖,存在一个依赖是过期的。
4 target存在依赖,存在一个依赖的时间戳比target的时间戳新。
除了rule,makefile也可以包含其他语法,比如变量赋值等。
目标
Goals are the targets that make strives ultimately to update。default goal是makefile中的第一个target。
makefile包含5种东西:
explicit rules, implicit rules, variable definitions, directives, and comments.
directive包括:
reading another makefile
deciding whether to use or ignore a part of the makefile
define a variable from a verbatim string containing multiple lines
#开头的行,是注释,recipe中的注释,将传递给shell。
makefile使用line-based语法,newline字符是特殊的,标志着statement的结束。GNU make不限制statement line的长度,直到用尽电脑的内存。
但是长行不利于阅读,可以使用 backslash (\) / newline 来打断长行。
makefile处理backslash/newline 对于recipe和non-recipe不同。
对于non-recipe,backslash /newline被转换成一个单个的space character。一旦这个完成,backslash/newline周围的所有空白都被浓缩成一个空白字符。
1 2 3 4 5 6 7 8 9 10 | $ cat Makefile word = hello \ world all: echo "$(word)" $ make echo "hello world" hello world |
默认按下列文件去搜索:GNUmakefile, makefile, Makefile
也可以使用-f name或--file=name,来指定makefile文件。
include filenames ...
filenames可以包含shell file name patterns,如果filenames为空,则什么也不包含,也不会报错。
filenames可以包含变量和函数引用,它们会被扩展。
如果被包含的文件不存在,那么make会给出一个警告信息,但是不会立即导致一个fatal error。然后make继续解析,执行remake(执行叫filename的target),remake之后,如果文件存在,则会include。如果make找到了remake,但是执行失败,那么make会产生fatal error。如果没有remake的rule,make会立即产生fatal error。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | $ cat Makefile include a all: echo "c=$(c)" a: echo c=a > a $ make Makefile:1: a: No such file or directory echo c=a > a echo "c=a" c=a $ make echo "c=a" c=a |
第一次执行,a文件不存在,所以给出警告,然后remake,执行a目标,生成a,然后在执行make的默认目标。
第二次执行,a已经存在了,不会触发remake,目标a就不会执行了。
如果include的makefile不存在,但是存在remake的rule,如果这个rule执行后,没有产生makefile,那么也不会报错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | $ cat Makefile include a all: echo all a: echo a herbert@herbert-pc:/work/learn/make$ make Makefile:1: a: No such file or directory echo a a echo all all |
这里只是给出了警告,没有报错。
-include或者sinclude,可以忽略文件不存在警告和错误,但是remake还是会执行,remake rule的失败也被忽略。
如果定义了环境变量MAKEFILES,那么make会在读取其它之前包含这些文件。但是default goal不会在这些文件中,如果这些文件不存在,也不会报错。
makefile可以包含其它makefile,如果其它makefile可能被更新,那么make可以得到最新版本的makefile来读取。makefile也可以写规则来更新自己,这也会导致remake。
makefile在读取所有makefiles后,make会尝试将每个makefile作为一个goal target,尝试更新它。如果makefile有一个rule来表明如何更新它,或者有implicit rule,那么执行这些rule。如果make发现某些makefile更新了,那么会再次包含它们。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | $ cat Makefile include a all: echo "c=$(c)" .PHONY: a a: echo c=a > a $ ls a Makefile $ make echo c=a > a echo "c=a" c=a |
如上,include a,尽管a已经存在了,但是还是会执行,因为a总是过时的,也就是说a总是依赖于规则被remake的。
如果使用double-colon,且有一个没有依赖的recipe,对于这种被包含的makefile,make有意地不执行remake。因为如果执行的话,那将是一个死循环。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | $ ls Makefile $ cat Makefile include a all: echo "c=$(c)" a:: echo c=a > a $ make Makefile:1: a: No such file or directory echo "c=" c= |
如果a::是双冒号的,所以a不会被remake,没有规则remake a,此时include失败,也不会导致fatal error。
有时候,你想一个Makefile几乎像另外一个Makefile,你可以使用include,添加更多的targets或者变量定义,但是两个makefile有同一个target,不同的recipe是非法的,但是有其它的方法:
1 2 3 4 5 6 7 | foo: frobnicate > foo %: force @$(MAKE) -f Makefile $@ force: ; |
force是为了保证target那个文件存在,也执行recipe。force添加一个空的recipe是为了防止隐含规则,否则它将匹配%,导致一个循环依赖。
上述还有一个问题,%会匹配这个makefile本身,同时这个%又总是过时的,这会导致remake。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | $ cat Makefile sd: echo sd %: force make -f inc.mk $@ force: ; $ make all make -f inc.mk Makefile make[1]: Entering directory '/work/learn/make' make[1]: Nothing to be done for 'Makefile'. make[1]: Leaving directory '/work/learn/make' make -f inc.mk all make[1]: Entering directory '/work/learn/make' echo inc.mk inc.mk |
可以看到,这里触发了Makefile的remake。这可能不是我们想要的。可以加一个空的 Makefile: ;
GNU make通过两个distinct phases来完成它的工作。
first phase读取所有的Makefiles,internalizes所有的变量和它们的值,implicit / explicit rules,构造targets的依赖路径和它们的依赖。
second phase根据内部结构决定那些targets需要rebuilt,执行它们的rule。
了解这两个阶段是很重要的,这影响变量和函数扩展是如何发生的。
如果扩展在第一阶段发生,则我们称它是immediate的。这种情况下,make会在parse的时候扩展变量或函数。
如果扩展没有立即执行,我们称它是deferred的。deferred construct的扩展不会立即执行,直到这个construct出现在immediate context中,或者直到second phase。
如下,左边的变量名都是立即扩展的,但是变量的值根据情况是immediate或者deferred的。
immediate = deferred
immediate ?= deferred
immediate := immediate
immediate ::= immediate
immediate += deferred or immediate
immediate != immediate
define immediate
deferred
endef
define immediate =
deferred
endef
define immediate ?=
deferred
endef
define immediate :=
immediate
endef
define immediate ::=
immediate
endef
define immediate +=
deferred or immediate
endef
define immediate !=
immediate
endef
+= 评估的类型,跟里面变量的类型一致。
注意一个延后的变量被立即评估,附给另外一个变量,但是这个被延后的变量,仍然是延后的。看下面的例子
1 2 3 4 5 6 | a := 2 b = $(a) c := $(b) a := 3 $(error $c)) $(error $b)) |
两个error处,c的值是2,但是b的是3。
条件语法是立即评估的。因此automatic variables不能用在条件语法中。自动变量直到rule的recipe被调用时才会设置。如果需要根据自动变量条件执行,可以通过shell的条件语法实现。
rule总是用相同的方式扩展,如下:
immediate : immediate ; deferred
deferred
target和prerequisite总是立即扩展,recipe总是延后扩展。
前面,我们学习到make工作在两个phase,read-in phase和target-update phase。make还有一个能力:只对一些targets的prerequisites执行second expansion。
二次扩展是在两个phase之间执行的。
二次扩展通过添加一个特殊的目标.SECONDEXPANSION,这个目标之后的所有目标的依赖执行二次扩展。
例子:
1 2 3 4 5 | .SECONDEXPANSION: ONEVAR := onefile TWOVAR := twofile all: $(ONEVAR) $$(TWOVAR) echo $^ |
第一次评估的时候:onefile $(TWOVAR)
第二次评估的时候:onefile twofile
由于一次扩展是immediate的,而二次扩展是在first phase之后,所以对于延后的变量一次扩展和二次扩展可能有不同的结果。
二次扩展真正展现力量的是用在自动变量上面,因为在依赖上面,自动变量,只有在二次扩展时,才可用:
1 2 3 4 | .SECONDEXPANSION: main_OBJS := main.o try.o test.o lib_OBJS := lib.o api.o main lib: $$($$@_OBJS) |
第一次扩展的目的是变成 $($@_OBJS),这样第二次扩展就能根据目标来选择依赖。