分类: LINUX
2015-05-12 20:03:21
原文地址:第四章:Makefile的规则(四) 作者:ivykiki
静态模式规则是这样一个规则:规则存在多个目标,并且不同的目标可以根据目标文件的名字来自动构造出依赖文件。静态模式规则比多目标规则更通用,它不需要多个目标具有相同的依赖。但是静态模式规则中的依赖文件必须是相类似的而不是完全相同的。
首先,我们来看一下静态模式规则的基本语法:
TARGETS ...: TARGET-PATTERN: PREREQ-PATTERNS ...
COMMANDS
...
“TAGETS”列出了此规则的一系列目标文件。像普通规则的目标一样可以包含通配符。
“TAGET-PATTERN”和“PREREQ-PATTERNS”说明了如何为每一个目标文件生成依赖文件。从目标模式(TAGET-PATTERN)的目标名字中抽取一部分字符串(称为“茎”)。使用“茎”替代依赖模式(PREREQ-PATTERNS)中的相应部分来产生对应目标的依赖文件。下边详细介绍这一替代的过程。
首先在目标模式和依赖模式中,一般需要包含模式字符“%”。在目标模式中“%”可以匹配目标文件的任何部分,模式字符“%”匹配的部分就是“茎”。目标文件和目标模式的其余部分必须精确的匹配。看一个例子:目标“foo.o”符合模式“%.o”,其“茎”为“foo”。而目标“foo.c”和“foo.out”就不符合此目标模式。
每一个目标的依赖文件是使用此目标模式的“茎”代替依赖模式中的模式字符“%”而得到。例如:上边的例子中依赖模式为“%.c”,那么使用“茎”“foo”替代依赖模式中的“%”得到的依赖文件就是“foo.c”。需要明确的一点是:在模式规则的依赖列表中使用不包含模式字符“%”也是合法的。代表这个文件是所有目标的依赖文件。
在模式规则中字符‘%’可以用前面加反斜杠“\”方法引用。引用“%”的反斜杠也可以由更多的反斜杠引用。引用“%”、“\”的反斜杠在和文件名比较或由“茎”代替它之前会从模式中被删除。反斜杠不会因为引用“%”而混乱。如,模式“the\%weird\\%pattern\\”是“the%weird\”+“%”+“pattern\\”构成。最后的两个反斜杠由于没有任何转义引用“%”所以保持不变。
我们来看一个例子,它根据相应的.c文件来编译生成“foo.o”和“bar.o”文件:
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
例子中,规则描述了所有的.o文件的依赖文件为对应的.c文件,对于目标“foo.o”,取其茎“foo”替代对应的依赖模式“%.c”中的模式字符“%”之后可得到目标的依赖文件“foo.c”。这就是目标“foo.o”的依赖关系“foo.o: foo.c”,规则的命令行描述了如何完成由“foo.c”编译生成目标“foo.o”。命令行中“$<”和“$@”是自动化变量,“$<”表示规则中的第一个依赖文件,“$@”表示规则中的目标文件。上边的这个规则描述了以下两个具体的规则:
foo.o : foo.c
$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
$(CC) -c $(CFLAGS) bar.c -o bar.o
在使用静态模式规则时,指定的目标必须和目标模式相匹配,否则执行make时将会得到一个错误提示。如果存在一个文件列表,其中一部分符合某一种模式而另外一部分符合另外一种模式,这种情况下我们可以使用“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”。“filter”函数过滤不符合“%.o”模式的文件名而返回所有符合此模式的文件列表。第一条静态模式规则描述了这些目标文件是通过编译对应的.c源文件来重建的。同样第二条规则也是使用这种方式。
我们通过另外一个例子来看一下自动环变量“$*”在静态模式规则中的使用方法:
bigoutput littleoutput : %output : text.g
generate text.g -$* > $@
当执行此规则的命令时,自动环变量“$*”被展开为“茎”。在这里就是“big”和“little”。
静态模式规则对一个较大工程的管理非常有用,它可以对整个工程的同一类文件的重建规则进行一次定义,而实现对整个工程中此类文件指定相同的重建规则。比如,可以用来描述整个工程中所有的.o文件的依赖规则和编译命令,通常的做法是将生成同一类目标的模式定义在一个rules.mak的文件中,在工程各个模块的Makefile中包含此文件。
Makefile中,静态模式规则和被定义为隐含规则的模式规则都是我们经常使用的两种方式。两者相同的地方都是用目标模式和依赖模式来构建目标的规则中的文件依赖关系,两者不同的地方是make在执行时使用它们的时机。
隐含规则可被用在任何和它相匹配的目标上,在Makefile中没有为这个目标指定具体的规则、存在规则但规则没有命令行或者这个目标的依赖文件可被搜寻到。当存在多个隐含规则和目标模式相匹配时,只执行其中的一个规则,具体执行哪一个规则取决于定义规则的顺序。
相反的,静态模式规则只能用在规则中明确指出的那些文件的重建过程中。不能用在除此之外的任何文件的重建过程中,并且它对指定的每一个目标来说是唯一的。如果一个目标存在于两个规则,并且这两个规则都定以了命令,make执行时就会提示错误。
静态模式规则相比隐含模式规则有以下两个优点:
1. 不能根据文件名通过词法分析进行分类的文件,我们可以明确列出这些文件,并使用静态模式规则来重建其隐含规则。
2. 对于无法确定工作目录内容,并且不能确定是否此目录下的无关文件会使用错误的隐含规则而导致make失败的情况。当存在多个适合此文件的隐含规则时,使用哪一个隐含规则取决于其规则的定义顺序。这种情况下我们使用静态模式规则就可以避免这些不确定因素,因为静态模式中,指定的目标文件有明确的规则来描述其依赖关系和重建命令。
双冒号规则就是使用“::”代替普通规则的“:”得到的规则。当同一个文件作为多个规则的目标时,双冒号规则的处理和普通规则的处理过程完全不同(双冒号规则允许在多个规则中为同一个目标指定不同的重建目标的命令)。
首先需要明确的是:Makefile中,一个目标可以出现在多个规则中。但是这些规则必须是同一类型的规则,要么都是普通规则,要么都是双冒号规则。而不允许一个目标同时出现在两种不同类型的规则中。双冒号规则和普通规则的处理的不同点表现在以下几个方面:
1. 双冒号规则中,当依赖文件比目标更新时。规则将会被执行。对于一个没有依赖而只有命令行的双冒号规则,当引用此目标时,规则的命令将会被无条件执行。而普通规则,当规则的目标文件存在时,此规则的命令永远不会被执行(目标文件永远是最新的)。
2. 当同一个文件作为多个双冒号规则的目标时。这些不同的规则会被独立的处理,而不是像普通规则那样合并所有的依赖到一个目标文件。这就意味着对这些规则的处理就像多个不同的普通规则一样。就是说多个双冒号规则中的每一个的依赖文件被改变之后,make只执行此规则定义的命令,而其它的以这个文件作为目标的双冒号规则将不会被执行。
我们来看一个例子,在我们的Makefile中包含以下两个规则:
Newprog :: foo.c
$(CC) $(CFLAGS) $< -o $@
Newprog :: bar.c
$(CC) $(CFLAGS) $< -o $@
如果“foo.c”文件被修改,执行make以后将根据“foo.c”文件重建目标“Newprog”。而如果“bar.c”被修改那么“Newprog”将根据“bar.c”被重建。回想一下,如果以上两个规则为普通规时出现的情况是什么?(make将会出错并提示错误信息)
当同一个目标出现在多个双冒号规则中时,规则的执行顺序和普通规则的执行顺序一样,按照其在Makefile中的书写顺序执行。
GNU make的双冒号规则给我们提供一种根据依赖的更新情况而执行不同的命令来重建同一目标的机制。一般这种需要的情况很少,所以双冒号规则的使用比较罕见。一般双冒号规则都需要定义命令,如果一个双冒号规则没有定义命令,在执行规则时将为其目标自动查找隐含规则。
Makefile中,有时需要书写一些规则来描述一个.o文件和头文件的依赖关系。例如,如果在main.c中使用“#include defs.h”,那么我们可能就需要一个像下边那样的规则来描述当头文件“defs.h”被修改以后再次执行make,目标“main.o”应该被重建。
main.o: defs.h
这样,对于一个大型工程。就需要在Makefile中书写很多条类似于这样的规则。并且,当在源文件中加入或删除头文件后,也需要小心地去修改Makefile。这是一件非常费力、费时并且危险(容易出错误)的工作。为了避免这个讨厌的问题,现代的c编译器提供了通过查找源文件中的“#include”来自动产生这种依赖关系的功能。Gcc通过“-M”选项来实现此功能,使用“-M”选项gcc将自动找寻源文件中包含的头文件,并生成文件的依赖关系。例如,如果“main.c”只包含了头文件“defs.h”,那么在Linxu下执行下面的命令:
gcc -M main.c
其输出是:
main.o : main.c defs.h
既然编译器已经提供了自动产生依赖关系的功能,那么我们就不需要去动手写这些规则的依赖关系了。但是需要明确的是:如果在“main.c”中包含了标准库的头文件,使用gcc的“-M”选项时,其输出结果中也包含对标准库的头文件的依赖关系描述。当不需要在依赖关系中考虑标准库头文件时,对于gcc需要使用“-MM”参数。
在使用gcc自动产生依赖关系时,所产生的规则中明确的指明了目标是“main.o”。一次在通过.c文件直接产生可执行文件时,作为中间过程文件的“main.o”在使用完之后将不会被删除。
在旧版本的make中,使用编译器此项功能通常的做法是:在Makefile中书写一个伪目标“depend”的规则来定义自动产生依赖关系文件的命令。输入“make depend”将生成一个称为“depend”的文件,其中包含了所有源文件的依赖规则描述。Makefile中使用“include”指示符包含这个文件。
在新版本的make中,推荐的方式是为每一个源文件产生一个描述其依赖关系的makefile文件。对于一个源文件“NAME.c”,对应的这个makefile文件为“NAME.d”。“NAME.d”中描述了文件“NAME.o”所要依赖的所有头文件。采用这种方式,只有源文件在修改之后才会重新使用命令生成新的依赖关系描述文件“NAME.d”。
我们可以使用如下的模式规则来自动生成每一个.c文件对应的.d文件:
%.d: %.c
$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f
此规则的含义是:所有的.d文件依赖于同名的.c文件。
第一行:使用c编译器自动生成依赖文件($<)的头文件的依赖关系,并输出成为一个临时文件,“$$$$”表示当前进程号。如果$(CC)为GNU的c编译工具,产生的依赖关系的规则中,依赖头文件包括了所有的使用的系统头文件和用户定义的头文件。如果需要生成的依赖描述文件不包含系统头文件,可使用“-MM”代替“-M”。
第二行:使用sed处理第二行已产生的那个临时文件并生成此规则的目标文件。这里sed完成了如下的转换过程。例如对已一个.c源文件。将编译器产生的依赖关系:
main.o : main.c defs.h
转成:
main.o main.d : main.c defs.h
这样就将.d加入到了规则的目标中,其和对应的.o文件文件一样依赖于对应的.c源文件和源文件所包含的头文件。当.c源文件或者头文件被改变之后规则将会被执行,相应的.d文件同样会被更新。
第三行;删除临时文件。
使用上例的规则就可以建立一个描述目标文件依赖关系的.d文件。我们可以在Makefile中使用include指示符将这个描述文件包含进来。在执行make时,Makefile所包含的所有.d文件就会被自动创建或者更新。Makefile中对当前目录下.d文件处理可以参考如下:
sources = foo.c bar.c
sinclude $(sources:.c=.d)
例子中,变量“sources”定义了当前目录下的需要编译的源文件。变量引用置换“$(sources : .c=.d)”的功能是根据变量“source”指定的.c文件自动产生对应的.d文件,并在当前Makefile文件中包含这些.d文件。.d文件和其它的makefile文件一样,make在执行时读取并试图重建它们。其实这些.d文件也是一些可被make解析的makefile文件。
需要注意的是include指示符的书写顺序,因为在这些.d文件中已经存在规则,当一个Makefile使用指示符include这些.d文件时,应该注意它应该出现在终极目标之后,以免.d文件中的规则被是Makefile的终极规则。关于这个前面我们已经有了比较详细的讨论。