Makefile语法[3/10]
4.6 不带recipe、prerequisite的rule
如果一个rule没有recipe、也没有prerequisite,而且这个target也不是一个存在的文件,则make认为只要该rule运行,该target就已被更新。这意味着,所有以这种rule的target为prerequisite的target,它们的recipe将总被执行(发现没,4.5中介绍的.PHONY
也有这样的用法)。
clean: FORCE
rm $(objects)
FORCE:
被文字绕晕了吗,那可能是你对一条rule的组成成分还不够熟练。举个例子很容易理解,上面一大段就是在说,clean每次都会执行。
4.7 用空target文件记录事件
空target是phony target的一个变体。
所谓“empty target"并不是说rule中的target那一项什么也不写,而是说target那一项是个确确实实的文件,但是这个文件什么内容也没有。
这有什么用呢?下例中,每次执行这条rule都会更新print,在此之后如果修改了依赖的文件,则用lpr
打印修改后的文件。如此,每次执行make print
,都会把有过修改的文件打印出来,形成记录。
print: foo.c bar.c
lpr -p $?
touch print
4.8 特殊的内置target名
一些名字作为target使用则含有特殊的意义,它们以点号.
开头,共有15个:
.PHONY
.SUFFIXES
.DEFAULT
.PRECIOUS
.INTERMEIDATE
.SECONDARY
.SECONDEXPANSION
.DELETE_ON_ERROR
.IGNORE
.LOW_RESOLUTION_TIME
.SILENT
.EXPORT_ALL_VARIABLES
.NOTPARALLEL
.ONESHELL
.POSIX
有些不常用,作为新手也不怎么见得到,不再一一展开,碰到的时候再翻手册查。
4.9 一个rule有多个targets
一条rule带多个target的情况有两种:target之间独立(target和prerequisite之间是冒号:
)、target捆绑组队(target和prerequisite之间是与号加冒号&:
)。
target之间独立
作用有两个:
- 规定依赖关系,而不带recipe
- 对所有target做相同recipe,简写
这种场景会用到自动变量$@
,表示某个target,例如:
bigoutput littleoutput : text.g
generate text.g -$(subst output,,$@) > $@
等效于
bigoutput : text.g
generate text.g -big > bigoutput
littleoutput : text.g
generate text.g -little > littleoutput
Grouped Targets
这个Grouped的意思是,当make决定更新其中一个target时,会把这组targets作为一个整体,全部更新,符号&
就暗示着all。
跟target之间独立的用法不同,grouped targets就必须有recipe。在grouped targets中的某一个target,也可以出现在其他grouped targets或者independent targets之中,但是只有最后一个有效。这种奇怪的用法建议不要用,高阶选手也可以用&::
要求make都有效。
4.10 一个target有多个rules
在多个rules中的同一个target,如果有多个prerequisites,那么make会把这些依赖都考虑在内,无论哪个依赖更新,都会触发target的编译。
这种用法很常见,如下:
objects = foo.o bar.o
bar.o : test.h
$(objects) : config.h defs.h
但是,在多个rules中的同一个target,如果有多个recipe,那么make只承认最后一个。高阶玩家可以用4.12小节的双冒号规则例外。
如果这个target的所有rules都没有显式recipe,那么make就会用隐式规则进行推导(见第10章)。
4.11 静态模式rule
所谓static pattern rule,就是指定多个target,并根据target名推到出prerequisites。
静态模式rule比4.9小节的multiple targets更常用,因为现在prerequisites不要求完全共用了,每个target都有自己对应的依赖。
4.11.1 句法形式
静态模式规则的句法如下:
targets ...: target-pattern: prereq-patterns ...
recipe
...
targets项跟普通rule一样,也都可以带通配符。
target-pattern和prereq-patterns共同指定每个target的依赖是什么。target-pattern跟每个target匹配,提取出target名字的剩余部分,作为茎stem
。stem
替换到target-pattern中,形成最终的依赖。
target-pattern和prereq-patterns都有且仅有一个百分号%
。target-pattern中的%
用来匹配target名称中的任意部分,例如foo.o
匹配模式%.o
,得到的stem
就是foo。提取出来的stem
再替换到target-pattern中,例如%.c
,则形成最终的依赖foo.c。
所有targets必须都能匹配同样的pattern,否则就要用filter来过滤,例如:
files = foo.elc bar.o lose.o
$(filter %.o,$(files)): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
emacs -f batch-byte-compile $<
其中,自动变量$<
代表prerequisite,就像$@
代表target,类似的还有$*
代表stem
。
4.11.2 静态模式规则与隐式规则
静态模式规则和定义为pattern rule的隐式规则有很多相同的地方(详见10.5):两者都有匹配target的模式和构建prerequisite名称的模式。两者的区别在于,make
使用它们的时机不同。
隐式规则可以应用于任何与它匹配的target,但它仅仅是在目标没有具体recipe同时可以被搜寻到prerequisite的情况下应用。如果有多条隐含规则适合,仅执行其中一条规则,具体执行哪一条取决于隐式规则的定义次序。
相反地,静态模式规则用于在当前rule中指明的target,不能应用于其它任何目标,并且它的使用方式对于各个target是固定不变的。如果两个带有recipe的静态模式规则冲突,则报错。
与隐式规则相比,静态模式规则的优势在于:
1.可以覆盖隐式规则,做用户自定义的指定
2.可以减少不确定性,毕竟有些情况我们也拿不准
4.12 双冒号规则
显式规则中用双冒号::
隔开target和prerequisite的规则,称为“Double-Colon Rule”。当同一个target在一条以上的rules中出现时,双冒号规则和普通规则的处理有所差异。
当同一个target在多条规则中出现时,所有的规则必须是同一类型:要么都是双冒号规则,要么都是普通规则。如果他们都是双冒号规则,则它们之间都是相互独立的。如果target比一个双冒号规则的依赖‘旧’,则该双冒号规则的recipe就会执行,就像这些规则的target不同一样。如果双冒号规则没有prerequisite,则recipe每次都会执行。
同一个target的不同双冒号规则按照它们在makefile文件中出现的顺序执行。然而双冒号规则真正有意义的场合是双冒号规则和执行顺序无关的场合。
双冒号规则有点模糊难以理解,实际应用双冒号规则的情况非常罕见。
每一个双冒号规则都应该指定recipe,如果没有则会使用隐式规则(详见第10章)。
4.13 自动生成依赖
如果没有自动推导的能力,比如main程序依赖defs.h头文件,那你就要写:
main.o: defs.h
这种规则要写一大堆不说,#include的内容改变后,你还要小心翼翼地修改makefile文件,即麻烦又容易出错。为了避免这种麻烦,现代C语言编译器都能自动扫描源文件推理出依赖的头文件,例如:
cc -M main.c
将生成:
main.o : main.c defs.h
注意,由于在makefile文件中提及构造‘main.o’,因此‘main.o’将永远不会被隐式规则认为是中间文件而进行搜寻,这同时意味着make不会在使用它之后自动删除它(详见10.4)。
对于旧版的make程序,通过‘make depend’产生一个‘depend’文件,该文件包含所有自动生成的依赖;然后makefile文件可以使用include命令将它们读入(详见3.3)。
在GNU make中,这种做法已经过时了。推荐的做法是把makefile文件和源程序文件一一对应。如,对每一个源程序文件‘name.c’有一名为‘name.d’的makefile文件和它对应,‘name.d’文件中列出了‘name.o’所依赖的文件。这种方式的优点是仅在源文件改变的情况下才有必要重新扫描生成新的依赖。
为每个源文件生成一个makefile文件的方式类似:
%.d: %.c
@set -e; rm -f $@; \
$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
sed ’s,\($*\)\.o[ :]*,\1.o $@ : ,g’ < $@.$$$$ > $@; \
rm -f $@.$$$$
模式规则的定义详见10.3。
@
符号在makefile中表示这条命令只执行不显示,-e表示一旦命令执行失败立即退出shell。
生成了一大堆.d
文件后,就在主makefile中include它:
sources = foo.c bar.c
include $(sources:.c=.d)
【暂时写到这,每周的周末更新这个系列,注意,工作日看makefile只要针对每个点百度就行,别把时间耗费在系统梳理makefile上,效率低影响进度。但是,系统梳理make才能提升长期工作效率和工作能力。】