写Rule(三)
来源:互联网 发布:淘宝二手镜头可以用吗 编辑:程序博客网 时间:2024/05/20 00:38
4 写Rule
用来描述如何以及何时创建特定文件的规则,叫做目标规则(rule’s target)。它(rule)列出target的prerequisite(依赖项),以及创建或更新target的recipe(规则)。
在一个makefile中这种rule可以有很多,他们的顺序并不重要。在执行make命令时如果没有指定,则第一个makefile文件中出现的第一个rule所确定的target将成为default goal。如果这个rule有若干target,则只有第一个被当作default goal。但是有两个例外:target以.
开头时不被当作default goal,除非它包含若干/
;定义了模板规则的target不会被当作default goal。
因此,我们通常写一个makefile时,第一个rule为编译的程序的入口,或者所有的程序被这个makefile描述。
4.1 Rule 语法
通常,一个rule看起来像这样:
targets : prerequisites recipe ...
或者:
targets : prerequisites ; recipe recipe ...
targets是文件名,用空格隔开,可以使用通配符。通常target只有一个,但有时也会有多个。
recipe以tab
字符开始,并位于prerequisite的下一行,也可以放在prerequisite后面,用分号;
隔开。
$
符号在make
中表示变量引用的开始,如果在target或prerequisite中想要输入一个$
字符,可以用两个$$
来表示。如果你使用了二次展开(secondary expansion),那么在target或prerequisite中输入一个$
字符时,你必须输入四个$$$$
。
你可以用反斜杠\
分割一个长行,但这并不是必须的,因为make并不限制一行的长度。
rule告诉了make两件事:什么时候targets过期,以及必要时如何更新它们。
一个target过期的意思是,它不存在,或者它的prerequisite(依赖项)中,有一个或多个文件最后的修改时间比target更新。也就是说target的内容是否被计算,取决于它的prerequisite(依赖项)的信息,因此任何prerequisite发生了改变时,target的内容也就不再有效了。
如何更新target取决于recipe,而这些recipe被shell执行,并依赖于外部特性。
4.2 Prerequisites的种类
GNU make可以识别两种不同的prerequisites:上面所描述的普通prerequisite,以及order-only prerequisites。普通的prerequisite声明了两件事:首先,它指定了一个顺序——prerequisite的recipe会在target的recipe调用之前完成执行;其次,它确定了一个依赖关系——任何比target新的prerequisite存在时,target将过期,且必须被重建。
通常,当prerequisite被更新时,对应的target也应该被更新。
有时候,你会遇到这么一种情形:你想让rules按特定的顺序被调用,当规则中的一条被执行时,你不想让target更新。这种情况下,你可以使用order-only prerequisite。order-only prerequisite 是用管道标志符|
来指定的,在管道标志符左边的是普通prerequisite,在管道标志符右边的是order-only prerequisite:
targets : normal-prerequisites | order-only-prerequisites
其中,normal-prerequisites可以为空。你也可以在多行中指定prerequisite,当同时指定normal-prerequisite和order-only prerequisite时,normal-prerequisite拥有更高的优先级。
想象如下情况,你的targets被放置在不同的目录下,而这些目录在make运行之前可能还不存在。此时,你希望这些目录在target写入之前先创建,但是由于目录的时间戳在文件被添加、删除或重命名时会发生改变,而我们又不想在目录时间戳发生变化时重建targets。处理这种情况的一种方式就是将目录作为这些targets的order-only prerequisite:
OBJDIR := objdirOBJS := $(addprefix $(OBJDIR)/,foo.o bar.o baz.o)$(OBJDIR)/%.o : %.c $(COMPILE.c) $(OUTPUT_OPTION) $<all : $(OBJS)$(OBJS) : | $(OBJDIR)$(OBJDIR) : mkdir $(OBJDIR)
现在,在任何.o
文件被建立之前,创建objdir
目录的规则会运行。而当objdir
目录的时间戳改变时,.o
文件不会被重建。
4.3 文件名中通配符的使用
使用通配符可以用一个文件名指代很多文件,与Bourn shell一样,make中使用的通配符有*
, ?
和 [...]
。例如*.c
表示所有的后缀为.c
的文件。
~
在文件名开始有特殊的意义。如果单独使用或者后面跟/
,则表示你的家目录(home directory),例如,~/bin
会展开成/home/you/bin
。如果~
后跟一个单词,这个字符串代表这个单词所指的用户名的家目录,例如,~john/bin
会展开为/home/john/bin
。在Windows系统中没有家目录的概念,你可以使用环境变量HOME
模拟这个功能。
make会自动将targets和prerequisites中的通配符展开,但是recipe中的通配符却是由运行make的shell展开的。其它情况下,通配符只有在你显式调用通配符函数(wildcard function)时展开。
通配符的特殊含义可以通过转义符\
关闭,这样,foo\*bar
就表示一个文件名,由foo
, *
和bar
组成。
4.3.1 通配符示例
通配符可以用在rule的recipe中,其由shell展开。例如,下面是一个删除所有中间文件(objcet file)的rule:
clean: rm -f *.o
通配符也可以用于rule的prerequisite中,下面的rule中,make print
命令会打印所有自上次打印以来所有改变过的的.c
文件:
print : *.c lpr -p $? touch print
这条rule中,使用print作为一个空的target文件,自动变量$?
用来打印那些改变了的文件。
当你定义一个变量的时候,通配符不会被展开,因此当你定义
objects = *.o
时,objects
确实就是字符串*.o
本身。然而,当你在target或prerequisite中使用这个变量时,通配符将会在变量使用的地方展开。如果你在recipe中使用objects
,那么shell会在这个recipe运行的时候将通配符展开。如果要在定义时就展开,可以这样定义变量:
objects := $(wildcard *.o)
4.3.2 使用通配符的误区
下面有一个简单的使用通配符的示例,它不会按照你想的方式工作。假设你想要可执行文件foo
的创建依赖于目录中的所有object文件,你这样写道:
objects = *.ofoo : $(objects) cc -o foo $(CFLAGS) $(objects)
objects
的实际值是字符串*.o
。通配符在foo
的rule中展开,每一个已经存在的.o
文件都成为foo
的prerequisite,并且在必要时被重新编译。
但是倘若你删除了所有的.o
文件呢?此时通配符匹配不到任何文件,那字符串*.o
本身就被当作一个文件名,又因为这样的文件不存在,所以make将会给出不知道如何创建文件*.o
的错误信息,而这并不是你所希望的。
事实上,是可以实现上面例子中所想要达到的通配符展开的目的的,但这需要更复杂的技术,包括wildcard函数以及字符串替换,而这些会在后面讨论。
Windows系统中使用反斜杆\
分割路径名,如:
c:\foo\bar\baz.c
这等效于Unix风格c:/foo/bar/baz.c
(c:
是驱动器号)。make运行在Windows系统中时,它支持反斜杆\
,也支持Unix风格的正斜杆/
。但是这种支持不包括通配符的展开,此时反斜杆\
是一个引用字符。所以,此时你必须使用Unix风格的斜杆/
。
4.3.3 通配符函数
通配符展开在rules中自动发生,但是当设置一个变量,或者在一个函数变量的内部时,通配符并不会正常展开。此时如果想让通配符展开,需要用到通配符函数:
$(wildcard pattern ...)
这个字符串可以在makefile文件中的任何地方使用,它会展开成一个用空格分开的文件列表,这些文件匹配给定的模板pattern
。当没有文件匹配时,通配符函数输出中的pattern
会被省略,这不同于rule中的通配符(如果没有匹配项就使用字面值,而不是忽略)。
使用通配符函数获取一个目录下的所有.c
源文件:
$(wildcard *.c)
我们可以通过替换.c
为.o
,从而获得一个object文件列表:
$(patsubst %.c,%.o,$(wildcard *.c))
这儿我们使用的patsubst
函数会在之后讨论。
因此,一个makefile文件编译所有的.c
文件,并在之后链接他们可以写成这样:
objects := $(patsubst %.c,%.o,$(wildcard *.c))foo : $(objects) cc -o foo $(objects)
4.4 在目录中搜寻prerequisites
对于大型系统,通常把源文件和二进制文件放在不同的目录中。make的目录搜索特性(directory search)有助于在若干目录中找到一个prerequisite。当把文件分放于不同目录时,你不需要去改动单个rule,只需变动搜索路径。
4.4.1 VPATH:所有prerequisites搜索路径
make中的变量VPATH
指定了一个搜索目录列表。通常这些目录会包含不在当前目录的prerequisites文件,但其实make使用VPATH
作为targets和prerequisites的搜索路径。
当一个作为target或prerequisite的文件不在当前目录时,make会在VPATH
所列的目录中搜寻。如果在其中找到该文件,那么该文件就被作为prerequisite。
在VPATH
变量中,目录名以冒号或空白分割,它们的顺序就是make之后搜寻的顺序。(在Windows系统中使用分号;
分割,因为冒号用在了驱动器号中,如c:\
)
例如:
VPATH = src:../headers
其中包含两个目录src
和../headers
,make按这个顺序搜寻。
根据VPATH
的值,下面的rule:
foo.o : foo.c
被解释成如下写法:
foo.o : src/foo.c
(假设foo.c
没有在当前目录下找到,而是在src
中被发现。)
4.4.2 vpath命令
vpath
命令类似于变量VPATH
,但是具有更强的选择性。vpath
允许匹配特定模板的文件搜寻特定的目录,这样你就可以对不同类型的文件搜寻不同的目录了。
vpath
有三种命令形式:
vpath pattern directories
对符合模板pattern
的文件,指定搜寻目录directories
列表,目录之间用冒号:
(Windows系统使用分号)或空白隔开,与VPATH
类似。
vpath pattern
清除模板pattern
的搜索路径。
vpath
清除所有模板的搜索路径。
vpath
模板是一个包含%
的字符串,这个字符串必须匹配被搜寻的prerequisite文件名,%
表示任意多个任意字符。例如,%.h
可以匹配所有的.h
文件(不使用%
的模板只能匹配特定的同名文件,通常不怎么使用)。
在vpath
的pattern
中可以使用反斜杆\
引用%
,而反斜杆本身也可以用反斜杆引用。当pattern
与文件名比较时,引用符\
会被移除,保留%
和\
本身。
当prerequisite文件不在当前目录时,其匹配的模板pattern
所指定的目录directories
将会被搜索,与VPATH
变量一样。例如:
vpath %.h ../headers
告诉make任何在当前目录找不到的.h
头文件,都可以去../headers
中查找。
如果有多个vpath
模板匹配到prerequisite文件名,make会依次处理每一个匹配到的vpath
命令,查找每个命令中提到的directories
。make按vpath
命令出现的顺序处理相同的匹配模板,因此:
vpath %.c foovpath % blishvpath %.c bar
将会先在目录foo
中查找.c
文件,然后是目录blish
,然后是目录bar
。而:
vpath %.c foo:barvpath % blish
会先在目录foo
查找.c
文件,然后是目录bar
,然后是目录blish
。
4.4.3 如何执行目录查找
当通过目录查找找到一个prerequisite文件(无论是VPATH
,还是vpath
),这个文件所在的目录可能并不会被make作为prerequisite文件列表的目录,有时候这个目录会被抛弃。
下面的规则说明make何时保留或抛弃一个通过目录查找确定的目录:
如果target文件不在makefile指定的路径中,目录查找就会被执行。
如果目录查找成功,这个路径会被保留,并且找到的文件会作为临时的target。
这个target的所有prerequisites都会通过这个方法进行审核。
prerequisites被处理完之后,target可能会被重建,也可能不会:
如果target不需要重建,前面找到的目录会被用于包含这个target的所有rerequisite列表。简单来说,如果make不需要重建target,那这个找到的目录就会被使用。
如果target需要被重建(target过期),这个找到的目录就会被抛弃,并且target会在makefile指定的目录中重建。简单来说,如果make必须重建,那么target会在本地重建,而不是目录查找中找到的目录。
这个规则可能看起来很复杂,但在实际使用中其执行过程正是你想要的。
其他版本的make使用一个简单的规则:如果文件不存在,但在目录查找中找到,那么这个找到的目录总是会被使用,不管target是否需要重建。这样,如果需要时,target会在找到的目录中被重建。
如果这正是你需要的,你可以使用变量GPATH
。GPATH
语法与VPATH
相同(使用空格或冒号分割路径名)。如果一个过期的target在目录查找中被发现,并且这个找到的目录也在GPATH
中,那么这个目录就不会被抛弃,同时target会在其中重建。
4.4.4 使用目录查找编写recipe
当一个prerequisite文件在目录查找中被找到,这并不会改变rule中的recipe,它们会按照编写的格式执行。因此,你必须仔细的编写recipe语句,以使其会查看make在目录查找中找到的目录。
自动变量$^
可以达到上面的目的。$^
的值是rule的所有的prerequisite文件,包括找到他们的目录名。变量$@
的值是target。例如:
foo.o : foo.c cc -c $(CFLAGS) $^ -o $@
(变量CFLAGS
用于指定C
编译标志)
通常prerequisites也包含头文件,这样可以避免将头文件放在recipe语句中。自动变量$<
表示第一个prerequisite文件:
VPATH = src:../headersfoo.o : foo.c defs.h hack.h cc -c $(CFLAGS) $< -o $@
这儿$<
表示foo.c
。
4.4.5 目录查找和隐含规则
通过VPATH
或vpath
进行的查找也发生在隐含规则中。例如,当一个文件foo.o
没有显式规则时,make会使用内建的隐含规则对foo.c
(如果存在)进行编译。如果在当前目录没有找到foo.c
,就会在适当的目录中查找。如果找到foo.c
,.c
文件编译的隐含规则就会被调用。隐含规则的recipe通常使用自动变量,因此它们使用目录查找中找到的文件名。
4.4.6 链接库文件的目录查找
连接器(linker)对库文件进行目录查找时,会使用特殊的方式。你需要通过-lname
的形式写一个prerequisite文件(你可能感到奇怪,一个库文件的名字一般看起来像libname.a
,而不是-lname
。)
当一个prerequisite文件有-lname
形式的名字时,make将会寻找libname.so
的库文件,如果在当前目录没有找到,就在VPATH
或vpath
指定的目录中查找,如果还没有找到,就在目录/lib
,/usr/lib
,prefib/lib
(通常是/usr/local/lib
,但在Windows系统中,prefix
被定义为DJGPP
的安装目录)中寻找。如果还没找到,就重复上面的过程,寻找文件名为libname.a
的库文件。
例如,系统中存在/usr/lib/libcurses.a
库文件(并且不存在/sur/lib/libcurses.so
文件),那么
foo : foo.c -lcurses cc $^ -o $@
当foo
比foo.c
或/sur/lib/libcurses.a
更旧时,将会执行命令cc foo.c /usr/lib/libcurses.a -o foo
。
默认情况下搜寻libname.so
和libname.a
文件,但是用户可以通过变量.LIBPATTERNS
自定义搜寻的文件类型。这个变量的每一个单词就是一个模板字符串,当在prerequisite中出现-lname
文件时,make就会用name
替换每一个模板中的百分号%
,并使用得到的库文件名进行上面的目录搜索。
变量.LIBPATTERNS
的默认值是lib%.so lib%.a
,这也就是上面描述的默认搜索模式。
你可以给变量.LIBPATTERNS
设置一个空值,这样就会禁止展开并链接库文件。
4.5 伪目标(Phony Targets)
伪目标不是一个真实的文件名,而是当你显式指定时,recipe命令执行的名字。使用伪目标有两个原因:避免跟同名文件冲突,改善make执行方式。
假设你写了一个rule,它的recipe不创建目标文件,那么这个rule的target会在你指定时被执行,如下示例:
clean : rm *.o temp
因为rm
并不创建一个名为clean
的文件(可能并没有这么一个文件存在),当你执行make clean
时,rm
命令将会被执行。
在这个例子中,如果同目录下存在一个名为clean
的文件,那么clean
作为target将不会正常工作。因为它没有prerequisites,clean
始终被认为是最新的,即它的recipe将永远不会被执行。为了避免这种情况发生,你可以使用.PHONY
显式指定一个target为伪目标:
.PHONY : cleanclean : rm *.o temp
这时,不管目录下是否有clean
文件存在,make clean
命令都会正常执行。
伪目标在连接make的递归调用时也很有用。在这种情况下,makefile通常包含一个列出需要被建立的一系列子目录的变量。
一个简单的处理方式是定义一个rule,它的recipe循环遍历子目录,就像这样:
SUBDIRS = foo bar bazsubdirs : for dir in $(SUBDIRS); do \ $(MAKE) -C $$dir; \ done
然而这种方法存在一些问题。首先,在这个rule中,子make(sub-make)的任何错误都会被忽略,所以当一个失败时,它会继续创建剩下的目录。可以通过命令行参数显示错误信息并退出make执行过程,但不幸的是,当make使用-k
选项时还是会发生上面的问题。其次,你不能充分利用make并行创建targets的能力,因为只有一条rule。第二点可能更重要。
通过将子目录声明为伪目标可以去除这些问题(你必须这么做,因为子目录通常都存在,否则就不会被建立):
SUBDIRS = foo bar baz.PHONY : subdirs $(SUBDIRS)subdirs : $(SUBDIRS)$(SUBDIRS) : $(MAKE) -C $@foo : baz
这儿我们还声明了子目录foo
必须在baz
之后建立,这种声明关系在并行建立目录时相当重要。
隐式规则会跳过.PHONY
进行搜索,这就是为什么将一个target声明为.PHONY
时,即使同名文件存在也可以正常运行的原因。
伪目标不能成为一个真实目标文件的prerequisite,否则,每次make更新真实目标文件时,它(伪目标)的recipe都会被执行。当伪目标不是一个真实文件的prerequisite时,只有将伪目标指定为make的目标时,它(伪目标)的recipe才会被执行。
伪目标可以有prerequisites,当一个目录包含多个程序,使用一个./Makefile
文件描述所有这些程序会很方便。默认情况下,被建立的目标是makefile中的第一个,如果将所有的独立程序作为伪目标的prerequisites:
all : prog1 prog2 prog3.PHONY : allprog1 : prog1.o utils.o cc -o prog1 prog1.o utils.oprog2 : prog2.o cc -o prog2 prog2.oprog3 : prog3.o sort.o utils.o cc -o prog3 porg3.o sort.o utils.o
现在你可以只输入一个make
就能重建所有三个程序,或者将某一个程序作为make的参数进行指定重建(例如,make prog1 prog3
)。伪目标不能继承,一个伪目标的prerequisite不是伪目标(除非它们被声明为伪目标)。
如果一个伪目标是另一个伪目标的prerequisite,那么它会作为另一个伪目标的子程序。下面的例子中,执行make cleanall
将会删除.o
文件、.diff
文件和文件program
:
.PHONY : cleanall cleanobj cleandiffcleanall : cleanobj cleandiff rm programcleanobj : rm *.ocleandiff : rm *.diff
4.6 没有recipe和prerequisites的rule
如果一个rule没有prerequisite和recipe,并且target是一个并不存在的文件,那么当这个rule运行时,这个target总是会被更新。这意味着所有基于这个target的targets,他们的recipe总是会被执行。
一个示例可以说明这些:
clean : FORCE rm $(objects)FORCE :
这儿目标FORCE
满足上面的情况,因此基于它的目标clean
将会强制执行其recipe。名字FORCE
并没有什么特殊,但通常在这种情况下被使用。
你应该可以看出来,这样使用FORCE
的方式,与使用语法.PHONY : clean
作用相同。
使用.PHONY
更加清晰高效,但其他版本的make不支持.PHONY
,因此FORCE
出现在许多makefile中。
4.7 记录事件的空目标(Empty Target)文件
空目标是伪目标的一个变体,通常用于显式指定的recipe行为。不同于伪目标,空目标的target文件可以实际存在,但是文件内容无关紧要,通常是空文件。
空目标文件通常用于如下情况:当recipe被执行时,空目标文件记录最后的修改时间。之所以会这样,是因为recipe中的命令之一是一个用于更新target文件的修改命令(touch command)。
空目标文件应该有一些prerequisites(否则就没意义了),如果任何prerequisites较target新(换句话说就是在上次重建target以来有prerequisite文件被修改了),当你要求重建target时,recipe就会被执行。下面是一个例子:
print : foo.c bar.c lpr -p $? touch print
自上次执行make print
以来,如果任何源文件被修改,那么make print
将会执行lpr
命令。自动变量$?
表示被修改的文件。
4.8 特殊的内建目标名
以下名字出现在目标中有特殊含义:
.PHONY
.PHONY
的prerequisites将会被当作伪目标,当被make当作一个伪目标时,不管同名文件是否存在,也不管这个文件的最后修改时间,伪目标的recipe总会被执行。
.SUFFIXES
.SUFFIXES
的prerequisites是一个用于检查前缀规则(suffix rules)的前缀列表。
.DEFAULT
.DEFAULT
的recipe被用于那些没有规则的target。如果.DEFAULT
的recipe被指定,每个在rule中被看作prerequisite而不是target的文件都会对它们自己执行这个recipe。
.PRECIOUS
基于
.PRECIOUS
的targets拥有如下特性:当在执行他们的recipe时,如果make被中断或退出,它们的targets将不会被删除。当然,如果这个target是一个中间文件,那它并不会在不需要时被删除(通常中间文件需要被删除)。这和.SECONDARY
作用类似。你也可以将一个隐含规则的目标模板(例如,%.o
)作为特殊目标.PRECIOUS
的prerequisite,用于保留那些target匹配这个prerequisite的rule创建的中间文件。(类似于作为.PHONY
的所有prerequisites都是伪目标,作为.PRECIOUS
的所有prerequisites都会被保留)
.INTERMEDIATE
基于.INTERMEDIATE的targets都是中间文件,没有prerequisites的.INTERMEDIATE没有意义。
.SECONDARY
基于
.SECONDARY
的targets被当作中间文件,除非它们不会被自动删除。没有prerequisites的.SECONDARY
会导致所有的targets都被当作中间文件(也就是说,没有target被删除,因为它被当作中间文件)。
.SECONDEXPANSION
如果
.SECONDEXPANSION
在makefile文件的任何位置被当作target,所有定义了after的prerequisites列表将会在所有makefile文件被读入以后再次展开。
.DELETE_ON_ERROR
如果
.DELETE_ON_ERROR
在makefile的任何位置被当作target,那么当某些rule的recipe以非零状态退出,并且其target已经被改变时,这些target会被删除。
.IGNORE
如果为
.IGNORE
指定prerequisites,那么make会忽略这些文件的recipe产生的错误。.IGNORE
的recipe(如果有的话)将会被忽略。.IGNORE
的使用只是为了向后兼容,因为其影响makefile中的所有recipe,所以并不是很有用。我们建议你使用更有选择性的方式来忽略错误信息,这将在后面讨论。
.LOW_RESOLUTION_TIME
如果你为
.LOW_RESOLUTION_TIME
指定prerequisites,那么make会认为这些文件是被产生低分辨率时间戳的命令所创建。.LOW_RESOLUTION_TIME
的target的recipe会被忽略。现代系统的高分辨率时间戳减少了make误判文件被更新的机会。不幸的是,一些主机并没有提供设置高分辨率时间戳的方法,因此,诸如cp -p
显式设置文件时间戳的命令必须去掉时间戳中次秒级(sub-second,小于秒的部分)的部分。如果一个文件被这种命令所创建,你应该将它列入.LOW_RESOLUTION_TIME
的prerequisite中,这样make就不会误以为这个文件过时了。例如:
.LOW_RESOLUTION_TIME : dst dst : src cp -p src dst
由于cp -p去掉了src文件的时间戳的次秒级部分,当dst被更新时,它也只是比src稍微旧一点。当dst与src有相同的秒级时间戳,.LOW_RESOLUTION_TIME行会让make认为dst需要被更新。
由于存档格式的限制,文件总是具有低分辨率的时间戳。你不需要将存档成员列入prerequisites,因为make会自动这么做。
.SILENT
当你为
.SILENT
指定prerequisites,在make执行它们之前,将不会打印用于重建这些文件的recipe。.SILENT
的recipe被忽略。
如果上面提到的target文件没有prerequisites,.SILENT
表示在执行它们之前不要打印任何recipe。.SILENT
只是为了向后兼容,我们建议你使用更具选择性的方式来不打印特定的recipe。如果你在执行make时不想打印所有的recipe,可以使用-s
或--silent
选项。
.EXPORT_ALL_VARIABLES
作为一个target使用,告诉make将本makefile的所有变量在其子程序中可见。
.NOTPARALLEL
如果
.NOTPARALLEL
作为target,make执行这个makefile将使用串行方式,即使有-j
选项。任何这个makefile递归调用的make命令将并行执行recipe(除非该makefile中包含这个target)。这个target的prerequisites将被忽略。
.ONESHELL
如果
.ONESHELL
作为target,那么当一个target被创建时,它的所有recipe行会当作一个命令被shell执行,而不是一个一个的分开执行。
.POSIX
如果
.POSIX
作为target,makefile会在POSIX模式下被分析并运行。这并不是说只有POSIX一致的makefile才会被接受:GNU make的所有高级特性仍让可用。而是这个target导致make的行为遵循POSIX。特别是,当这个target被指定时,之后的recipe调用将会等同于shell被设置了
-e
标志:recipe中第一个失败的命令会立即终止recipe。
任何定义了隐式规则的后缀也被看作是特殊的target(如果它被作为target),即使是相连的两个后缀(如,.c.o)。这些target叫做后缀规则(suffix rules),是一种过时的定义隐式规则的方式(但仍然被广泛使用)。原则上讲,任何target的名字都可以被当作这种特殊特target(当你将其分为两半并加入到后缀列表中)。实际应用中,后缀通常以.开始,因此这些特殊的target也用.开始。
4.9 多目标规则
一个多目标规则等同于多个规则,每个规则有一个目标,其余部分完全一样。相同的recipe作用于所有的target上,但是效果并不一定相同,因为可以使用$@
代替recipe中的实际target名。相同的prerequisites对于target也是一样。
在两种情况下这比较有用:
如果你只想要prerequisites,而没有recipe,例如:
kbd.o command.o files.o : command.h
给每个object文件增减了一个command.h
依赖文件。
相似的recipes作用于所有的targets。recipes不需要完全相同,因为自动变量$@
可以替换命令中需要重建的特定targets,例如:
bigoutput littleoutput : text.g generate text.g -$(subst output,,$@) > $@
等效于
bigoutput : text.g generate text.g -big > bigoutputlittleoutput : text.g generate text.g -little > littleouput
(这儿虚构的程序generate
创建了两个不同类型的文件,-big
和-little
)
变量$@
允许你根据target调整prerequisites和recipe。在普通规则中你不能使用这种多目标规则,但是在静态模板规则中可以。
4.10 同一target拥有多个规则
一个文件可以成为多个规则的target,所有规则中的prerequisites会合并成target的一个prerequisites列表。如果这个target比其中任何一个prerequisite旧,则recipe将会被执行。
对于一个文件只有一个recipe可以被执行,如果对于同一个文件存在多个recipe,那么make会使用最后那个,并打印一份错误信息(有一个例外,如果文件名以.
开始,将不会有错误信息。这个奇怪的行为只是为了兼容其它版本的make实现,应尽量避免使用)。有时候在不同的makefile文件中,为相同名字的target调用不同的recipe是很有用的,这在双冒号规则中讨论。
一个只包含prerequisites的规则,可以用于给一些文件一次性增加若干prerequisites。例如,makefile通常有一个变量objects
,其中包含所有的编译输出文件。如果文件config.h
发生改变时,为了让所有objects
全部重建,一个简单的方法是:
objects = foo.o bar.ofoo.o : defs.hbar.o : defs.h test.h$(objects) : config.h
这样,在不改变objects
的实际创建规则前提下,可以很方便的把依赖文件config.h
加进去。
另一个技巧是,可以通过变量来指定要增加的依赖项,你只需要在执行make
时提供对应的参数即可,例如:
extradeps = $(objects) : $(extradeps)
这样,执行make extradeps=foo.h
命令将会把foo.h
加入到每一个objects
的依赖项列表中,如果只是执行make
则不会。
如果所有这些target的显式规则中都没有recipe的话,make会查找可使用的隐式规则。
4.11 静态模板规则
静态模板规则是针对多目标的规则,它可以根据每个的target名字构建prerequisites。对于多目标规则来说,静态模板规则比普通规则更加通用,因为一般情况下targets不会有完全相同的prerequisites列表。它们的prerequisites列表必须相似,但不必完全相同。
4.11.1 静态模板规则的语法
静态模板规则的语法如下:
targets ...: target-pattern: prereq-patterns ... recipe ...
targets列表指定这条规则的应用对象。其中使用包含通配符,与普通规则的targets一样。
target-pattern
和prereq-patterns
描述如何构建target的prerequisite列表。每个target匹配于target-pattern
的部分叫做词干(stem)。词干(stem)会替换到prereq-patterns
模板中,用于构建prerequisites列表。
每个模板通常只含有一个%
字符。当target-pattern
匹配一个target时,%
可以匹配target名字的任意部分,这部分叫做词干。模板的剩余部分必须完全匹配,例如,foo.o
匹配%.o
,其中foo
是词干。foo.c
和foo.out
不会匹配这个模板。
每个target的prerequisite名字是通过将prereq-patterns
中的%
替换为对应词干得到的。例如,如果一个prereq-patterns
是%.c
,那么prerequisite就是foo.c
。prereq-patterns
中不包含%
是合法的,此时对所有targets来说,prerequisites将完全相同。
在模板规则中,%
字符可以被反斜杆\
引用(\%
),反斜杆可以引用自己(\\
)。在模板进行比较或者替换时,引用符\
会被去除。使用反斜杆引用%
将会造成语义上的复杂化。例如,模板the\%weird\\%pattern\\
被真正的通配符%
分成了两部分:the%weird\
和pattern\\
。最后的两个反斜杆被保留,因为它们不会影响任何的%
。
下面是一个例子,foo.c
和bar.o
根据其对应的.c
文件编译获得:
objects = foo.o bar.oall: $(objects)$(objects): %.o: %.c $(CC) -c $(CFLAGS) $< -o $@
这里自动变量$<
代表prerequisite名,$@
代表target名。
每个target必须匹配target-pattern
,对于不匹配的target,将会给出警告。如果文件列表中只有一部分文件匹配模板,可以使用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 %<
其中$(filter %.o,$(files))
的结果是bar.o lose.o
,第一个静态模板规则中通过编译对应的C
源文件更新object文件。$(filter %.elc,$(files))
的结果是foo.elc
,因此由foo.el
创建。
另一个例子展示如何在静态模板规则中使用$*
:
bigoutput littleoutput : %output : text.g generate text.g -$* > $@
当generate
命令运行时,$*
会展开成词干big
或者little
。
4.11.2 静态模板规则与隐式规则
静态模板规则与定义了模板的隐式规则非常类似。两者都有针对target的模板和构建prerequisites名的模板。不同之处在于make如何决定这些规则何时被使用。
隐式规则可以应用于匹配其模板的所有targets,但是只有没有其他recipe被指定,或者prerequisites可以被找到时,隐式规则才会被使用。如果有多余一个的隐式规则可以被应用,则只有一个会被使用,其选择取决于规则的顺序。
相反,静态模板规则可以精确应用于你在规则中指定的特定的targets。它不会应用于其它target,并且始终应用于每个特定的target。如果有两条规则发生冲突,并且都有recipe,就会报错。
基于如下两个原因,静态模板规则优于隐式规则:
有些文件的文件名无法在语法上进行分类时,你可以使用显式列表覆盖掉普通的隐式规则。
如果你无法确认正在使用的目录下有哪些文件,并且无法确认那些无关文件会使make调用错误的隐式规则,这些隐式规则的选取依赖于搜寻顺序。使用静态模板规则可以避免这些不确定性:每条规则都精确对应于特定的targets。
4.12 双冒号规则
双冒号规则是指在target名字后面用::
代替:
。当一个target出现在多个规则中时,双冒号规则和普通规则有不同的处理方式。双冒号模板规则有用完全不同的意义。
当target出现在不同的规则中时,这些规则必须拥有相同的类型:或者普通规则,或者双冒号规则。如果是双冒号,那这些target都是相互独立的,每个target会在其对应的prerequisites更新时更新。如果没有对应的prerequisites,则该规则总是被执行(即使对应的target已经存在)。双冒号规则对应的所有target中,可能有任意多个会被执行。
拥有相同target的双冒号规则实际上是完全互相独立的,每个规则都独自处理,就像对于不同的target一样。
一个target的双冒号规则执行顺序取决于规则在makefile中出现的顺序。但是实际的执行顺序又无所谓。
双冒号规则隐晦而又不常使用,它提供了一种机制:一个target基于不同的prerequisites进行更新,但是这种情况比较罕见。
每个双冒号规则应该指定一个recipe,如果没有指定,隐式规则将会被使用。
4.13 自动生成prerequisites
在一个程序的makefile中,你需要写很多规则来描述哪些对象依赖于哪些头文件。例如,如果main.c
通过#include
包含defs.h
,你可以这样写:
main.o: defs.h
你需要通过这条规则让make知道,当defs.h
发生改变时,要重建main.o
。在一个大型程序中,你要写几十个这样的规则,而且在源文件中增加或减少#include
语句时,你还要小心的增加或删除makefile中对应的规则。
为了避免这种繁琐的工作,大多数现代C
编译器会通过查看源文件的#include
,为你写出这些规则。通常编译器使用-M
选项来实现这个功能。例如:
cc -M main.c
产生如下输出:
main.o : main.c defs.h
这样,你不在需要手动输入这些规则了,编译器会帮你做。
注意,这条规则在makefile文件中构建main.o
,因此隐式规则不会把它当作中间文件。也就是说make不会在使用过它之后将其删除。
在老式make程序中,要实现这个功能需要使用命令make depend
。这条命令会创建一个depend
文件,其中包含自动生成的prerequisites,之后你可以在makefile中使用include
语句将其读进来。
在GNU make中,重建makefile文件的特性使这个功能不再需要,你不需要显式的告诉make去重新生成prerequisites,因为当makefile过期时会自动重新生成。
推荐的自动生成prerequisite的方法是让每一个源文件对应一个makefile。对每一个源文件name.c
,对应一个名为name.d
的makefile,其中列出了name.o
的所有依赖文件。这样,只有源文件发生改变时,才会被重新检查,生成新的prerequisites。
下面是一个模板规则,用于从C源文件name.c
生成名为name.d
的依赖项文件:
%.d: %.c @set -e; rm -f $@; \ $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \ sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \ rm -f $@.$$$$
-e
标志指定当$(CC)
命令失败时(或者其它任何命令失败时),shell会立即退出。
对于GNU C 编译器,可以使用-MM
代替-M
,这可以省略对系统头文件的依赖规则。
sed
命令的目的是将:
main.o : main.c defs.h
翻译成:
main.o main.d : main.c defs.h
这会使每个.d
文件与对应的.o
文件依赖于相同的源文件和头文件。当任何源文件或头文件改变时,make就会重新生成依赖项。
一旦你定义了重建.d
文件的规则,就可以使用include
命令将它们读进来。例如:
sources = foo.c bar.cinclude $(source:.c=.d)
其中使用了变量引用替换,将根据源文件列表foo.c bar.c
生成依赖makefile文件列表foo.d bar.d
。.d
文件类似于其它makefile文件,make会在必要时重建它们。
注意,.d
文件中包含target定义,要保证include
命令放在第一个(默认)目标之后,否则有可能使用一个随机的object
文件作为默认目标。
- 写Rule(三)
- MySQL分布式集群之MyCAT(三)rule的分析
- rule
- rule
- tomcat源码解析(三)——Digester类源码解析及Rule分析
- 写Makefile(三)
- <hr/> horizontal rule(水平线)
- 自己动手写操作系统(三)
- 自己动手写操作系统(三)
- 自己动手写操作系统(三)
- 自己动手写俄罗斯方块(三)
- 从零开始写PHP (三)
- 自己动手写操作系统(三)
- JBoss Rules 学习(一): 什么是Rule
- JBoss Rules 学习(一): 什么是Rule
- JBoss Rules 学习(一): 什么是Rule
- JBoss Rules 学习(一): 什么是Rule
- Horner Rule(霍纳法则)
- OSI模型与TCP/IP五层网络架构的关系
- java 大顶堆
- mysql设计规范
- redhat7安装oracle11gR2之动手安装
- HashMap,LinkedHashMap,TreeMap的有序性
- 写Rule(三)
- 万圣节惊天大劫案
- Android MVP模式之模拟登陆功能
- 开发后台界面用 div好,还是frame 框架 好?
- Python中数字和字符串比较大小问题
- Address already in use: JVM_Bind<null>:80 解决方案
- 06-python中高级数据类型
- Fly拦截全局Ajax请求
- Android常用设计模式——观察者模式