make的隐含规则


在Makefile中重建一类目标的标准规则在很多场合需要用到。例如:根据.c源文件创建对应的.o文件,传统方式是使用GNU 的C编译器。

「隐含规则」为make提供了重建一类目标文件通用方法,不需要在Makefile中明确地给出重建特定目标文件所需要的细节描述。例如:典型地;make对C文件的编译过程是由.c源文件编译生成.o目标文件。当Makefile中出现一个.o文件目标时,make会使用这个通用的方式将后缀为.c的文件编译称为目标的.o文件。

另外,在make执行时根据需要也可能是用多个隐含规则。比如:make将从一个.y文件生成对应的.c文件,最后再生成最终的.o文件。就是说,只要目标文件名中除后缀以外其它部分相同,make都能够使用若干个隐含规则来最终产生这个目标文件(当然最原始的那个文件必须存在)。例如;可以在Makefile中这样来实现一个规则:「foo : foo.h」,只要在当前目录下存在「foo.c」这个文件,就可以生成「foo」可执行文件。本文前边的很多例子中已经使用到了隐含规则。

内嵌的「隐含规则」在其所定义的命令行中,会使用到一些变数(通常也是内嵌变数)。我们可以通过改变这些变数的值来控制隐含规则命令的执行情况。例如:内嵌变数「CFLAGS」代表了gcc编译器编译源文件的编译选项,我们就可以在Makefile中重新定义它,来改变编译源文件所要使用的参数。

尽管我们不能改变make内嵌的隐含规则,但是我们可以使用模式规则重新定义自己的隐含规则,也可以使用后追规则来重新定义隐含规则。后缀规则存在某些限制(目前版本make保存它的原因是为了兼容以前版本)。使用模式规则更加清晰明了。

10.1 隐含规则的使用

使用make内嵌的隐含规则,在Makefile中就不需要明确给出重建某一个目标的命令,甚至可以不需要规则。make会自动根据已存在(或者可以被创建)的源文件类型来启动相应的隐含规则。例如:

foo : foo.o bar.o

cc -o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)

这里并没有给出重建文件「foo.o」的规则,make执行这条规则时,无论文件「foo.o」存在与否,都会试图根据隐含规则来重建这个文件(就是试图重新编译文件「foo.c」或者其它类型的源文件)。

make执行过程中找到的隐含规则,提供了此目标的基本依赖关系,确定了目标的依赖文件(通常是源文件,不包含对应的头文件依赖)和重建目标需要使用的命令行。隐含规则所提供的依赖文件只是一个最基本的(通常它们之间的对应关系为:「EXENAME.o」对应「EXENAME.c」、「EXENAME」对应于「EXENAME.o」)。当需要增加这个目标的依赖文件时,要在Makefile中使用没有命令行的规则给出。

每一个内嵌的隐含规则中都存在一个目标模式和依赖模式,而且同一个目标模式可以对应多个依赖模式。例如:一个.o文件的目标可以由c编译器编译对应的.c源文件得到、Pascal编译器编译.p的源文件得到,等等。make会根据不同的源文件来使用不同的编译器。对于「foo.c」就是用c编译,对于「foo.p」就使用Pascal编译器编译。

上边提到,make会自动根据已存在(或者可以被创建)的源文件类型来启动相应的隐含规则。这里的「可被创建」文件是指:这个文件在Makefile中被作为目标或者依赖明确的提及,或者可以根据已存在的文件使用其它的隐含规则来创建它。当一个隐含规则的目标是另外一个隐含规则的依赖时,我们称它们是一个隐含规则链。

通常,make会对那些没有命令行的规则、双冒号规则寻找一个隐含规则来执行。作为一个规则的依赖文件,在没有一个规则明确描述它的依赖关系的情况下;make会将其作为一个目标并为它搜索一个隐含规则,试图重建它。

注意:给目标文件指定明确的依赖文件并不会影响隐含规则的搜索。我们来看一个例子:

foo.o: foo.p

这个规则指定了「foo」的依赖文件是「foo.p」。但是如果在工作目录下存在同名.c源文件「foo.c」。执行make的结果就不是用「pc」编译「foo.p」来生成「foo」,而是用「cc」编译「foo.c」来生成目标文件。这是因为在隐含规则列表中对.c文件的隐含规则处于.p文件隐含规则之前。

当需要给目标指定明确的重建规则时,规则描述中就不能省略命令行,这个规则必须提供明确的重建命令来说明目标需要重建所需要的动作。为了能够在存在「foo.c」的情况下编译「foo.p」。规则可以这样写:

foo.o: foo.p

pc $< -o $@

这一点在多语言实现的工程编译中,需要特别注意!否则编译出来的可能就不是你想要得程序。

另外:当我们不想让make为一个没有命令行的规则中的目标搜索隐含规则时,我们需要使用空命令来实现。

最后让我们来看一个简单的例子,之前在目标指定变数 一节的例子我们就可以简化为:

# sample Makefile

CUR_DIR = $(shell pwd)

INCS := $(CUR_DIR)/include

CFLAGS := -Wall –I$(INCS)

EXEF := foo bar

.PHONY : all clean

all : $(EXEF)

foo : CFLAGS+=-O2

bar : CFLAGS+=-g

clean :

$(RM) *.o *.d $(EXES)

例子中没有出现任何关于源文件的描述。所有剩余工作全部交给了make去处理,它会自动寻找到相应规则并执行、最终完成目标文件的重建。

隐含规则为我们提供了一个编译整个工程非常高效的手段,一个大的工程中毫无例外的会用到隐含规则。实际工作中,灵活运用GNU make所提供的隐含规则功能,可以大大提供效率。

10.2 make的隐含规则一览

本节罗列出了GUN make常见的一些内嵌隐含规则,除非在Makefile有名确定义、或者使用命令行「-r」或者「-R」参数取消隐含规则,否则这些隐含规则将有效。

需要说明的是:即使我们没有使用命令行参数「-r」,在make中也并不是所有的这些隐含规则都被定义了。其实,很多的这些看似预定义的隐含规则在make执行时,实际是用后缀规则来实现的;因此,它们依赖于make中的「后缀列表」(也就是目标.SUFFIXES的后缀列表)。make的默认后缀列表为:「.out」、「.a」、「.ln」、「.o」、「.c」、「.cc」、「.C」、「.p」、「.f」、「.F」、「.r」、「.y」、「.l」、「.s」、「.S」、「.mod」、「.sym」、「.def」、「.h」、「.info」、「.dvi」、「.tex」、「.texinfo」、「.texi」、「txinfo」、「.w」、「.ch」、「.web」、「.sh」、「.elc」、「el」。所有我们下边将提到的隐含规则,如果其依赖文件中某一个满足列表中列出的后缀,则是后缀规则。如果修改了可识别后缀列表,那么可能会是许多默认预定义的规则无效(因为一些后缀可能不会别识别)。以下是常用的一些隐含规则(对于不常见的隐含规则这里没有描述):

1. 编译C程序

「N.o」自动由「N.c」 生成,执行命令为「$(CC) -c $(CPPFLAGS) $(CFLAGS)」。

2. 编译C++程序

「N.o」自动由「N.cc」或者「N.C」 生成,执行命令为「$(CXX) -c $(CPPFLAGS) $(CFLAGS)」。建议使用「.cc」作为C++源文件的后缀,而不是「.C」

3. 编译Pascal程序

「N.o」自动由「N.p」创建,执行命令时「$(PC) -c $(PFLAGS)」。

4. 编译Fortran/Ratfor程序

「N.o」自动由「N.r」、「N.F」或者「N.f」 生成,根据源文件后缀执行对应的命令:

.f — 「$(FC) –c $(FFLAGS)」

.F — 「$(FC) –c $(FFLAGS) $(CPPFLAGS)」

.r — 「$(FC) –c $(FFLAGS) $(RFLAGS)」

5. 预处理Fortran/Ratfor程序

「N.f」自动由「N.r」或者「N.F」 生成。此规则只是转换Ratfor或有预处理的Fortran程序到一个标准的Fortran程序。根据源文件后缀执行对应的命令:

.F — 「$(FC) –F $(CPPFLAGS) $(FFLAGS)」

.r — 「$(FC) –F $(FFLAGS) $(RFLAGS)」

6. 编译Modula-2程序

「N.sym」自动由「N.def」 生成,执行的命令是:「$(M2C) $(M2FLAGS) $(DEFFLAGS)」。「N.o」自动由「N.mod」生成,执行的命令是:「$(M2C) $(M2FLAGS) $(MODFLAGS)」。

7. 汇编和需要预处理的汇编程序

「N.s」是不需要预处理的汇编源文件,「N.S」是需要预处理的汇编源文件。汇编器为「as」。

「N.o」 可自动由「N.s」生成,执行命令是:「$(AS) $(ASFLAGS)」。

「N.s」 可由「N.S」生成,C预编译器「cpp」,执行命令是:「$(CPP) $(CPPFLAGS)」。

8. 链接单一的object文件

「N」自动由「N.o」生成,通过C编译器使用链接器(GUN ld),执行命令是:「$(CC) $(LDFLAGS) N.o $(LOADLIBES) $(LDLIBS)」。

此规则仅适用:由一个源文件直接产生可执行文件的情况。当需要有多个源文件共同来创建一个可执行文件时,需要在Makefile中增加隐含规则的依赖文件。例如:

x : y.o z.o

当「x.c」、「y.c」和「z.c」都存在时,规则执行如下命令:

cc -c x.c -o x.o

cc -c y.c -o y.o

cc -c z.c -o z.o

cc x.o y.o z.o -o x

rm -f x.o

rm -f y.o

rm -f z.o

在复杂的场合,目标文件和源文件之间不存在向上边那样的名字对应关系时(「x」和「x.c」对应,因此隐含规则在进行链接时,自动将「x.c」作为其依赖文件)。这时,需要在Makefile中明确给出描述目标依赖关系的规则。

通常,gcc在编译源文件时(根据源文件的后缀名来启动相应的编译器),如果没有指定「-c」选项,gcc会在编译完成之后调用「ld」连接成为可执行文件。

9. Yacc C程序

「N.c」自动由「N.y」,执行的命令:「$(YACC) $(YFALGS)」。(「Yacc」是一个语法分析工具)

10. Lex C程序时的隐含规则。

「N.c」自动由「N.l」,执行的命令是:「$(LEX) $(LFALGS)」。(关于「Lex」的细节请查看相关资料)

这里没有列出所有的隐含规则,仅列出我个人在实际工作中涉及到的。没有涉及的很难对英文文档进行深入地说明和理解。如果那些没有提到的各位有所使用,或者能够详细的描述可以添加到这个文档中!

在隐含规则中,命令行中的实际命令是使用一个变数计算得到,诸如:「COMPILE.c」、「LINK.o」(这个在前面也看到过)和「PREPROCESS.S」等。这些变数被展开之后就是对应的命令(包括了命令行选项),例如:变数「COMPILE.c」的定义为 「cc -c」(如果Makefile中存在「CFLAGS」的定义,它的值会存在于这个变数中)。

make会根据默认的约定,使用「COMPILE.x」来编译一个「.x」的文件。类似地使用「LINK.x」来连接「.x」文件;使用「PREPROCESS.x」对「.x」文件进行预处理。

每一个隐含规则在创建一个文件时都使用了变数「OUTPUT_OPTION」。make执行命令时根据命令行参数来决定它的值,当命令行中没有包含「-o」选项时,它的值为:「-o $@」,否则为空。建议在规则的命令行中明确使用「-o」选项执行输出文件路径。这是因为在编译一个多目录的工程时,如果我们的Makefile中使用了「VPATH」指定搜索目录 时,编译后的.o文件或者其它文件会出现在和源文件不同的目录中。在有些系统的编译器不接受命令行的「-o」参数,而Makefile中包含「VPAT」的情况时,输出文件可能会出现在错误的目录下。解决这个问题的方式就是将「OUTPUT_OPTION」的值赋为「;mv $*.o $@」,其功能是将编译完成的.o文件改变为规则中的目标文件。

10.3 隐含变数

内嵌隐含规则的命令中,所使用的变数都是预定义的变数。我们将这些变数称为「隐含变数」。这些变数允许对它进行修改:在Makefile中、通过命令行参数或者设置系统环境变数的方式来对它进行重定义。无论是用那种方式,只要make在运行时它的定义有效,make的隐含规则都会使用这些变数。当然,也可以使用「-R」或「--no–builtin-variables」选项来取消所有的隐含变数(同时将取消了所有的隐含规则)。

例如,编译.c源文件的隐含规则为:「$(CC) -c $(CFLAGS) $(CPPFLAGS)」。默认的编译命令是「cc」,执行的命令是:「cc –c」。我们可以同上述的任何一种方式将变数「CC」定义为「ncc」,那么编译.c源文件所执行的命令将是「ncc -c」。同样我们可以对变数「CFLAGS」进行重定义。对这些变数重定义后如果需要整个工程的各个子目录有效,同样需要使用关键字「export」将他们导出;否则目录间编译命令可能出现不一致。编译.c源文件时,隐含规则使用「$(CC)」来引用编译器;「$(CFLAGS)」引用编译选项。

隐含规则中所使用的变数(隐含变数)分为两类:1. 代表一个程序的名字(例如:「CC」代表了编译器这个可执行程序)。2. 代表执行这个程序使用的参数(例如:变数「CFLAGS」),多个参数使用空格分开。当然也允许在程序的名字中包含参数。但是这种方式建议不要使用。

以下是一些作为程序名的隐含变数定义:

10.3.1 代表命令的变数

AR

函数库打包程序,可创建静态库.a文档。默认是「ar」。

AS

汇编程序。默认是「as」。

CC

C编译程序。默认是「cc」。

CXX

C++编译程序。默认是「g++」。

CO

从 RCS中提取文件的程序。默认是「co」。

CPP

C程序的预处理器(输出是标准输出设备)。默认是「$(CC) -E」。

FC

编译器和预处理Fortran 和 Ratfor 源文件的编译器。默认是「f77」。

GET

从SCCS中提取文件程序。默认是「get」。

LEX

将 Lex 语言转变为 C 或 Ratfo 的程序。默认是「lex」。

PC

Pascal语言编译器。默认是「pc」。

YACC

Yacc文法分析器(针对于C程序)。默认命令是「yacc」。

YACCR

Yacc文法分析器(针对于Ratfor程序)。默认是「yacc -r」。

MAKEINFO

转换Texinfo源文件(.texi)到Info文件程序。默认是「makeinfo」。

TEX

从TeX源文件创建TeX DVI文件的程序。默认是「tex」。

TEXI2DVI

从Texinfo源文件创建TeX DVI 文件的程序。默认是「texi2dvi」。

WEAVE

转换Web到TeX的程序。默认是「weave」。

CWEAVE

转换C Web 到 TeX的程序。默认是「cweave」。

TANGLE

转换Web到Pascal语言的程序。默认是「tangle」。

CTANGLE

转换C Web 到 C。默认是「ctangle」。

RM

删除命令。默认是「rm -f」。

10.3.2 命令参数的变数

下边的是代表命令执行参数的变数。如果没有给出默认值则默认值为空。

ARFLAGS

执行「AR」命令的命令行参数。默认值是「rv」。

ASFLAGS

执行汇编语器「AS」的命令行参数(明确指定「.s」或「.S」文件时)。

CFLAGS

执行「CC」编译器的命令行参数(编译.c源文件的选项)。

CXXFLAGS

执行「g++」编译器的命令行参数(编译.cc源文件的选项)。

COFLAGS

执行「co」的命令行参数(在RCS中提取文件的选项)。

CPPFLAGS

执行C预处理器「cc -E」的命令行参数(C 和 Fortran 编译器会用到)。

FFLAGS

Fortran语言编译器「f77」执行的命令行参数(编译Fortran源文件的选项)。

GFLAGS

SCCS 「get」程序参数。

LDFLAGS

链接器(如:「ld」)参数。

LFLAGS

Lex文法分析器参数。

PFLAGS

Pascal语言编译器参数。

RFLAGS

Ratfor 程序的Fortran 编译器参数。

YFLAGS

Yacc文法分析器参数。

10.4 make隐含规则链

有时,一个目标文件需要多个(一系列)隐含规则来创建。例如:创建文件「N.o」的过程可能是:首先执行「yacc」由「N.y」生成文件「N.c」,之后由编译器将「N.c」编译成为「N.o」。如果一个目标文件需要一系列隐含规则才能完成它的创建,我们就把这个系列称为一个「链」。

我们来看上边例子的执行过程。有两种情况:

1. 如果文件「N.c」存在或者它在Makefile中被提及,那就不需要进行其它搜索,make处理的过程是:首先,make可以确定出「N.o」可由「N.c」创建;之后,make试图使用隐含规则来重建「N.c」。它会寻找「N.y」这个文件,如果「N.y」存在,则执行隐含规则来重建「N.c」这个文件。之后再由「N.c」重建「N.o」;当不存在「N.y」文件时,直接编译「N.c」生成「N.o」。

2. 文件「N.c」不存在也没有在Makefile中提及的情况,只要存在「N.y」这个文件,那么make也会经过这两个步骤来重建「N.o」(N.y → N.c → N.o)。这种情况下,文件「N.c」作为一个中间过程文件。Make在执行规则时,如果需要一个中间文件才能完成目标的重建,那么这个文件将会自动地加入到依赖关系链中(和Makefile中明确提及的目标作相同处理),并使用合适的隐含规则对它进行重建。

make的中间过程文件和那些明确指定的文件在规则中的地位完全相同。但make在处理时两者之间存在一些差异:

第一:中间文件不存在时,make处理两者的方式不同。对于一个普通文件来说,因为Makefile中有明确的提及,此文件可能是作为一个目标的依赖,make在执行它所在的规则前会试图重建它。但是对于中间文件,因为没有明确提及,make不会去试图重建它。除非这个中间文件所依赖的文件(上例第二种情况中的文件「N.y」;N.c是中间过程文件)被更新。

第二:如果make在执行时需要用到一个中间过程文件,那么默认的动作将是:这个过程文件在make执行结束后会被删除(make会在删除中间过程文件时列印出执行的命令以显示那些文件被删除了)。因此一个中间过程文件在make执行结束后就不再存在了。

在Makefile中明确提及的所有文件都不被作为中间过程文件来处理,这是预设地。不过我们可以在Makefile中使用特殊目标「.INTERMEDIATE」来指除将那些文件作为中间过程文件来处理(这些文件作为目标「.INTERMEDIATE」的依赖文件罗列),即使它们在Makefile中被明确提及,这些作为特殊目标「.INTERMEDIATE」依赖的文件在make执行结束之后会被自动删除。

另一方面,如果我们希望保留某些中间过程文件(它没有在Makefile中被提及),不希望make结束时自动删除它们。可以在Makefile中使用特使目标「.SECONDARY」来指出这些文件(这些文件将被作为「secondary」文件;需要保留的文件作为特殊目标「.SECONDARY」的依赖文件罗列)。注意:「secondary」文件也同时被作为中间过程文件来对待。

需要保留中间过程文件还存在另外一种实现方式。例如需要保留所有.o的中间过程文件,我们可以将.o文件的模式(%.o)作为特殊目标「.PRECIOUS」的依赖。

一个「链」可以包含两个以上隐含规则的调用过程。同一个隐含规则在一个「链」中只能出现一次。否则就会出现像「foo」依赖「foo.o.o」甚至「foo.o.o.o.o…」这样不合逻辑的情况发生。因为,如果允许在一个「链」中多次调用同一隐含规则(N : N.o; $(LINK.o) $(LDFLAGS) N.o $(LOADLIBES) $(LDLIBS) ),将会导致make进入到无限的循环中去。

隐含规则链中的某些隐含规则,在一些情况会被优化处理。例如:从文件「foo.c」创建可执行文件「foo」,这一过程可以是:使用隐含规则将「foo.c」编译生成「foo.o」文件,之后再使用另一个隐含规则来完成对「foo.o」的链接,最后生成执行文件「foo」。这个过程中对源文件的编译和对.o文件的链接分别使用了两个独立的规则(它们组成一个隐含规则链)。但是实际情况是,对源文件的编译和对.o文件的链接是在一个规则中完成的,规则使用命令「cc foo.c foo」。make的隐含规则表中,所有可用的优化规则处于首选地位。

10.5 模式规则

模式规则类似于普通规则。只是在模式规则中,目标名中需要包含有模式字元「%」(一个),包含有模式字元「%」的目标被用来匹配一个文件名,「%」可以匹配任何非空字元串。规则的依赖文件中同样可以使用「%」,依赖文件中模式字元「%」的取值情况由目标中的「%」来决定。例如:对于模式规则「%.o : %.c」,它表示的含义是:所有的.o文件依赖于对应的.c文件。我们可以使用模式规则来定义隐含规则。

要注意的是:模式字元「%」的匹配和替换发生在规则中所有变数和函数引用展开之后,变数和函数的展开一般发生在make读取Makefile时(变数和函数的展开可参考 第五 章 使用变数 和 第七章 make的函数 ),而模式规则中的「%」的匹配和替换则发生在make执行时。

10.5.1 模式规则介绍

在模式规则中,目标文件是一个带有模式字元「%」的文件,使用模式来匹配目标文件。文件名中的模式字元「%」可以匹配任何非空字元串,除模式字元以外的部分要求一致。例如:「%.c」匹配所有以「.c」结尾的文件(匹配的文件名长度最少为3个字母),「s%.c」匹配所有第一个字母为「s」,而且必须以「.c」结尾的文件,文件名长度最小为5个字元(模式字元「%」至少匹配一个字元)。在目标文件名中「%」匹配的部分称为「茎」(前面已经提到过,参考 静态模式 一节)。使用模式规则时,目标文件匹配之后得到「茎」,依赖根据「茎」产生对应的依赖文件,这个依赖文件必须是存在的或者可被创建的。

因此,一个模式规则的格式为:

%.o : %.c ; COMMAND...

这个模式规则指定了如何由文件「N.c」来创建文件「N.o」,文件「N.c」应该是已存在的或者可被创建的。

模式规则中依赖文件也可以不包含模式字元「%」。当依赖文件名中不包含模式字元「%」时,其含义是所有符合目标模式的目标文件都依赖于一个指定的文件(例如:%.o : debug.h,表示所有的.o文件都依赖于头文件「debug.h」)。这样的模式规则在很多场合是非常有用的。

同样一个模式规则可以存在多个目标。多目标的模式规则和普通多目标规则有些不同,普通多目标规则的处理是将每一个目标作为一个独立的规则来处理,所以多个目标就就对应多个独立的规则(这些规则各自有自己的命令行,各个规则的命令行可能相同)。但对于多目标模式规则来说,所有规则的目标共同拥有依赖文件和规则的命令行,当文件符合多个目标模式中的任何一个时,规则定义的命令就有可能将会执行;因为多个目标共同拥有规则的命令行,因此一次命令执行之后,规则不会再去检查是否需要重建符合其它模式的目标。看一个例子:

#sample Makefile

Objects = foo.o bar.o

CFLAGS := -Wall

%x : CFLAGS += -g

%.o : CFLAGS += -O2

%.o %.x : %.c

$(CC) $(CFLAGS) $< -o $@

当在命令行中执行「make foo.o foo.x」时,会看到只有一个文件「foo.o」被创建了,同时make会提示「foo.x」文件是最新的(其实「foo.x」并没有被创建)。此过程表明了多目标的模式规则在make处理时是被作为一个整体来处理的。这是多目标模式规则和多目标的普通规则的区别之处。大家不妨将上边的例子改为普通多目标规则试试看将会得到什么样的结果。

最后需要说明的是:

1. 模式规则在Makefile中的顺序需要注意,当一个目标文件同时符合多个目标模式时,make将会把第一个目标匹配的模式规则作为重建它的规则。

2. Makefile中明确指定的模式规则会覆盖隐含模式规则。就是说如果在Makefile中出现了一个对目标文件合适可用的模式规则,那么make就不会再为这个目标文件寻找其它隐含规则,而直接使用在Makefile中出现的这个规则。在使用时,明确规则永远优先于隐含规则。

3. 另外,依赖文件存在或者被提及的规则,优先于那些需要使用隐含规则来创建其依赖文件的规则。

10.5.2 模式规则示例

本小节来看一些使用模式规则的例子,这些模式规则在GNU make中已经被预定义。首先看编译.c文件到.o文件的隐含模式规则:

%.o : %.c

$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

此规则描述了一个.o文件如何由对应的.c文件创建。规则的命令行中使用了自动化变数「$<」和「$@」,其中自动化变数「$<」代表规则的依赖,「$@」代表规则的目标。此规则在执行时,命令行中的自动化变数将根据实际的目标和依赖文件取对应值。

make中第二个内嵌模式规则是:

% :: RCS/%,v

$(CO) $(COFLAGS) $<

这个规则的含义是:任何一个文件「X」都可以由目录「RCS」下的相应文件「x.v」来生成。规则的目标为「%」,它匹配任何文件名,因此只要存在相对应的依赖文件(N.v),目标(N)都可被创建。双冒号表示该规则是最终规则,意味著规则的依赖文件不是中间过程文件。

另外,一个具有多目标的隐含规则是:

%.tab.c %.tab.h: %.y

bison -d $<

它是一个多目标模式规则,关于多目标的特征可参考 模式规则介绍 一小节最后一个例子。

10.5.3 自动化变数

模式规则中,规则的目标和依赖文件名代表了一类文件名;规则的命令是对所有这一类文件重建过程的描述,显然,在命令中不能出现具体的文件名,否则模式规则失去意义。那么在模式规则的命令行中该如何表示文件,将是本小节的讨论的重点。

假如你需要书写一个将.c文件编译到.o文件的模式规则,那么你该如何为gcc书写正确的源文件名?当然了,不能使用任何具体的文件名,因为在每一次执行模式规则时源文件名都是不一样的。为了解决这个问题,就需要使用「自动环变数」,自动化变数的取值是根据具体所执行的规则来决定的,取决于所执行规则的目标和依赖文件名。

下面对所有的自动化变数进行说明:

$@

表示规则的目标文件名。如果目标是一个文档文件(Linux中,一般称.a文件为文档文件,也称为静态库文件),那么它代表这个文档的文件名。在多目标模式规则中,它代表的是哪个触发规则被执行的目标文件名。

$%

当规则的目标文件是一个静态库文件时,代表静态库的一个成员名。例如,规则的目标是「foo.a(bar.o)」,那么,「$%」的值就为「bar.o」,「$@」的值为「foo.a」。如果目标不是静态库文件,其值为空。

$<

规则的第一个依赖文件名。如果是一个目标文件使用隐含规则来重建,则它代表由隐含规则加入的第一个依赖文件。

$?

所有比目标文件更新的依赖文件列表,空格分割。如果目标是静态库文件名,代表的是库成员(.o文件)。

$^

规则的所有依赖文件列表,使用空格分隔。如果目标是静态库文件,它所代表的只能是所有库成员(.o文件)名。一个文件可重复的出现在目标的依赖中,变数「$^」只记录它的一次引用情况。就是说变数「$^」会去掉重复的依赖文件。

$+

类似「$^」,但是它保留了依赖文件中重复出现的文件。主要用在程序链接时库的交叉引用场合。

$*

在模式规则和静态模式规则中,代表「茎」。「茎」是目标模式中「%」所代表的部分(当文件名中存在目录时,「茎」也包含目录(斜杠之前)部分)。例如:文件「dir/a.foo.b」,当目标的模式为「a.%.b」时,「$*」的值为「dir/a.foo」。「茎」对于构造相关文件名非常有用。

自动化变数「$*」需要两点说明:

? 对于一个明确指定的规则来说不存在「茎」,这种情况下「$*」的含义发生改变。此时,如果目标文件名带有一个可识别的后缀,那么「$*」表示文件中除后缀以外的部分。例如:「foo.c」则「$*」的值为:「foo」,因为.c是一个可识别的文件后缀名。GUN make对明确规则的这种奇怪的处理行为是为了和其它版本的make兼容。通常,在除静态规则和模式规则以外,明确指定目标文件的规则中应该避免使用这个变数。

? 当明确指定文件名的规则中目标文件名包含不可识别的后缀时,此变数为空。

自动化变数「$?」在显式规则中也是非常有用的,使用它规则可以指定只对更新以后的依赖文件进行操作。例如,静态库文件「libN.a」,它由一些.o文件组成。这个规则实现了只将更新后的.o文件加入到库中:

lib: foo.o bar.o lose.o win.o

ar r lib $?

以上罗列的自动量变数中。其中有四个在规则中代表文件名($@、$<、$%、$*)。而其它三个的在规则中代表一个文件名列表。GUN make中,还可以通过这七个自动化变数来获取一个完整文件名中的目录部分和具体文件名部分。在这些变数中加入「D」或者「F」字元就形成了一系列变种的自动环变数。这些变数会出现在以前版本的make中,在当前版本的make中,可以使用「dir」或者「notdir」函数来实现同样的功能。

$(@D)

表示目标文件的目录部分(不包括斜杠)。如果「$@」是「dir/foo.o」,那么「$(@D)」的值为「dir」。如果「$@」不存在斜杠,其值就是「.」(当前目录)。注意它和函数「dir」的区别!

$(@F)

目标文件的完整文件名中除目录以外的部分(实际文件名)。如果「$@」为「dir/foo.o」,那么「$(@F)」只就是「foo.o」。「$(@F)」等价于函数「$(notdir $@)」。

$(*D)

$(*F)

分别代表目标「茎」中的目录部分和文件名部分。

$(%D)

$(%F)

当以如「archive(member)」形式静态库为目标时,分别表示库文件成员「member」名中的目录部分和文件名部分。它仅对这种形式的规则目标有效。

$(<D)

$(<F)

分别表示规则中第一个依赖文件的目录部分和文件名部分。

$(^D)

$(^F)

分别表示所有依赖文件的目录部分和文件部分(不存在同一文件)。

$(+D)

$(+F)

分别表示所有依赖文件的目录部分和文件部分(可存在重复文件)。

$(?D)

$(?F)

分别表示被更新的依赖文件的目录部分和文件名部分。

在讨论自动化变数时,为了和普通变数(如:「CFLAGS」)区别,我们直接使用了「$<」的形式。这种形式仅仅是为了和普通变数进行区别,没有别的目的。其实对于自动环变数和普通变数一样,代表规则第一个依赖文件名的变数名实际上是「<」,我们完全可以使用「$(<)」来替代「$<」。但是在引用自动化变数时通常的做法是「$<」,因为自动化变数本身是一个特殊字元。

GUN make同时支持「Sysv」特性,允许在规则的依赖列表中使用特殊的变数引用(一般的自动化变数只能在规则的命令行中被引用)「$$@」、「$$(@D)」和「$$(@F)」(注意:要使用「$$」),它们分别代表了「目标的完整文件名」、「目标文件名中的目录部分」和「目标的实际文件名部分」。这三个特殊的变数只能用在明确指定目标文件名的规则中或者是静态模式规则中,不用于隐含规则中。另外Sysv make和GNU make对规则依赖的处理也不尽相同。Sysv make对规则的依赖进行两次替换展开,而GUN make对依赖列表的处理只有一次,对其中的变数和函数引用直接进行展开。

自动化变数的这个古怪的特性完全是为了兼容Sysv 版本的makefile文件。在使用GNU make时可以不考虑这个,也可以在Makefile中使用伪目标「.POSIX」来禁止这一特性。

10.5.4 模式的匹配

通常,模式规则中目标模式由前缀、后缀、模式字元「%」组成,这三个部分允许两个同时为空。实际文件名应该是以模式指定的前缀开始、后缀结束的任何文件名。文件名中除前缀和后缀以外的所有部分称之为「茎」(模式字元「%」可以代表若干字元。因此:模式「%.o」所匹配的文件「test.c」中「test」就是「茎」)。模式规则中依赖文件名的确定过程是:首先根据规则定义的目标模式匹配实际的目标文件,确定「茎」,之后使用 「茎」替代规则依赖文件名中的模式字元「%」,生成依赖文件名。这样就产生了一个明确指定了目标和依赖文件的规则。例如模式规则:「%.o : %.c」,当「test.o」需要重建时将形成规则「test.o : test.c」。

当目标模式中包含斜杠(目录部分)。在进行目标文件匹配时,文件名中包含的目录字元串在匹配之前被移除,只进行基本文件名的匹配;匹配成功后,再将目录部分加入到匹配之后的字元串之前形成「茎」。来看一个例子:例如目标模式为「e%t」,文件「src/eat」匹配这个模式,那么「茎」就是「src/a」;模式规则中依赖文件的产生:首先使用「茎」中的非目录部分(「a」)替代依赖文件中的模式字元「%」,之后再将目录部分(「src/」)加入到形成的依赖文件名之前构成依赖文件的全路径名。这里如果模式规则的依赖模式为「c%r」,则那么目标「src/eat」对应的依赖文件就为「src/car」。

10.5.5 万用规则

当模式规则的目标只是一个模式字元「%」(它可以匹配任何文件名)时,我们称这个规则为万用规则。万用规则在书写Makefile时非常有用,但它会影响make的执行效率,因为make在执行时将会使用万用规则来试图重建其它规则的目标和依赖文件。

假如在一个存在万用规则的Makefile中提及了文件「foo.c」。为了创建这个目标,make会试图使用以下规则来创建这个目标:1.对一个.o文件「foo.c.o」进行链接并产生文件「foo.c」;2.使用c编译和连接程器由文件「foo.c.c」来创建这个文件;3. 编译并链接Pascal程序「foo.c.p」来创建;等等。总之make会试图使用所有可能的隐含规则来完成对这个文件的创建。

当然,我们很清楚以上这样的过程是没有必要的,我们知道「foo.c」是一个.c原文件,而不是一个可执行程序。make在执行时都会试图根据可能的隐含规则来创建这个文件,但由于其依赖的文件(「foo.c.o」、「foo.c.c」等)不存在,最终这些可能的隐含规则都会被否定。但是如果在Makefile中存在一个万用规则,那么make执行时所要考虑的情况就比较复杂,也很多(它会试图功过隐含规则来创建那些依赖文件,虽然最终这些文件不可能被创建,也无从创建),从而导致make的执行效率会很低。

为了避免万用规则带来的效率问题,我们可以对万用规则的使用加以限制。通常有两种方式,需要在定义一个万用规则时对其进行限制。

1. 将万用规则设置为最终规则,定义时使用双冒号规则。作为最终规则,此规则只有在它的依赖文件存在时才能被应用,即使它的依赖可以由隐含规则创建也不行。就是说,最终规则中没有进一步的「链」。

例如,从RCS和SCCS文件中提取源文件的内嵌隐含规则就是一个最终规则。因此如果文件「foo.c,v」不存在,make就不会试图从一个中间文件「foo.c,v.o」或「RCS/SCCS/s.foo.c,v」来创建它。 RCS 和 SCCS 文件一般都是最终的源文件,它不能由其它任何文件重建;make可以记录它的时间戳,但不会寻找重建它们的方式。

如果万用规则没有定义为最终规则,那么它就是一个非最终规则。非最终的万用规则不会被用来创建那些符合某一个明确模式规则的目标和依赖文件。就是说如果在Makefile中存在匹配此文件的模式规则(非万用规则),那么对于这个文件来说其重建规则只会是它所匹配的这个模式,而不是这个非最终的万用规则。例如,文件「foo.c」,如果在Makefile中同时存在一个万用规则和模式规则 「%.c : %.y」(该规则运行Yacc)。无论该规则是否会被使用(如果存在文件「foo.y」,那么规则将被执行)。那么make试图重建「foo.c」的规则都是「%.c : %.y」,而不是万用规则。这样做是为了避免make执行时试图使用非最终的万用规则来重建文件「foo.c」的情况发生。

2. 定义一个特殊的内嵌哑模式规则给出如何重建某一类文件,避免使用非最终万用规则。哑模式规则没有依赖,也没有命令行,在make的其它场合被忽略。例如,内嵌的哑模式规则:「%.p :」为Pascal源程序如「foo.p」指定了重建规则(规则不存在依赖文件、也不存在任何命令),这样就避免了make试图「foo.p」而去寻找「foo.p.o」或「foo.p.c」的过程。

我们可以为所有的make可识别的后缀创建一个形如「%.p :」的哑模式规则。

10.5.6 重建内嵌隐含规则

一个隐含规则,我们可以对它进行重建。重建一个隐含规则时,需要使用相同的目标和依赖模式,命令可以不同(重新指定规则的命令)。这样就可以替代有相同目标和依赖的那个make内嵌规则,替代之后隐含规则可能被使用的顺序由它在Makefile中的位置决定。例如通常Makefile中可能会包含这样一个规则:

%.o : %.c

$(CC) $(CFLAGS) –D__DEBUG__ $< -o $@

它替代了编译.c文件的内嵌隐含规则。

也可以取消一个内嵌的隐含规则。同样需要定义一个和隐含规则具有相同目标和依赖的规则,但这个规则没有命令行。例如下边的这个规则取消了编译.s文件的内嵌规则。

%.o : %.s

10.6 预设规则

有时make会需要一个预设的规则,在执行过程中无法为一个文件找到合适的重建规则(在Makefile中没有给出重建它的明确规则,同时也没有合适可用的隐含规则)。那么make就使用这个规则来重建它。就是说,当所需要的重建的目标文件没有可用的命令时、就执行这个预设规则命令。

这样一个规则,我们可以使用最终万用规则。例如:调试Makefile时(可能一些源文件还没有完成),我们关心的是Makefile中所有的规则是否可正确执行,而源文件的具体内容却不需要关心。基于这一点我们就可以使用空文件(和源文件同名的文件),在Makefile中定义这样一个规则:

%::

touch $@

执行make过程中,对所有不存在的.c文件将会使用「touch」命令创建这样一个空的源文件。

实现一个预设规则的方式也可以不使用万用规则,而使用伪目标「.DEFAULT」。上边的例子也可以这样实现:

.DEFAULT :

touch $@

需要注意:没有指定命令行的伪目标「.DEFAULT」,含义是取消前边所有使用「.DEFAULT」指定的预设执行命令。

同样,也可以让这个预设的规则不执行任何命令(给它定义个一个空命令)。

另外预设规则也可用来实现在一个Makefile中重载另一个makefile文件。

10.7 后缀规则

后缀规则是一种古老定义隐含规则的方式,在新版本的make中使用模式规则作为对它的替代,模式规则相比后缀规则更加清晰明了。在现在版本中保留它的原因是为了能够兼容旧的makefile文件。后缀规则有两种类型:「双后缀」和「单后缀」。

双后缀规则定义一对后缀:目标文件的后缀和依赖目标的后缀。它匹配所有后缀为目标后缀的文件。对于一个匹配的目标文件,它的依赖文件这样形成:将匹配的目标文件名中的后缀替换为依赖文件的后缀得到。如:一个描述目标和依赖后缀的「.o」和「.c」的规则就等价于模式规则「%o : %c」。

单后缀规则只定义一个后缀:此后缀是源文件名的后缀。它可以匹配任何文件,其依赖文件这样形成:将后缀直接追加到目标文件名之后得到。例如:单后缀「.c」就等价于模式规则「% : %.c」。

判断一个后缀规则是单后缀还是双后缀的过程:判断后缀规则的目标,如果其中只存在一个可被make识别的后缀,则规则是一个「单后缀」规则;当规则目标中存在两个可被make识别的后缀时,这个规则就是一个「双后缀」规则。

例如:「.c」和「.o」都是make可识别的后缀。因此当定义了一个目标是「.c.o」的规则时。make会将它作为一个双后缀规则来处理,它的含义是所有「.o」文件的依赖文件是对应的「.c」文件。下边是使用后追规则定义的编译.c源文件的规则:

.c.o:

$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<

注意:一个后缀规则中不存在任何依赖文件。否则,此规则将被作为一个普通规则对待。因此规则:

.c.o: foo.h

$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<

就不是一个后缀规则。它是一个目标文件为「.c.o」、依赖文件是「foo.h」的普通规则。它也不等价于规则:

%.o: %.c foo.h

$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<

需要注意的是:没有命令行的后缀规则是没有任何意义的。它和没有命令行的模式规则不同,它也不能取消之前使用后追规则定义的规则。它所实现的仅仅是将这个后缀规则作为目标加入到make的资料库中。

可识别的后缀指的是特殊目标「.SUFFIXES」所有依赖的名字。通过给特殊目标「SUFFIXES」添加依赖来增加一个可被识别的后缀。像下边这样:

.SUFFIXES: .hack .win

它所实现的功能是把后缀「.hack」和「.win」加入到可识别后缀列表的末尾。

如果需要重设默认的可识别后缀,因该这样来实现:

.SUFFIXES: #删除所有已定义的可识别后缀

.SUFFIXES: .c .o .h #重新定义

首先使用没有依赖的特殊目标「.SUFFIXES」来删除所有已定义的可识别后缀;之后再重新定义。

注意:make的「-r」或「-no-builtin-rules」可以清空所有已定义的可识别后缀。

在make读取所有的makefile文件之前,变数「SUFFIXE」被定义为默认的可识别后缀列表。虽然存在这样一个变数,但是请不要通过修改这个变数值的方式来改变可识别的后缀列表,应该使用特殊目标「.SUFFIXES」来实现。

10.8 隐含规则搜索演算法

对于目标「T」,make为它搜索隐含规则的演算法如下。此演算法适合于:1. 任何没有命令行的双冒号规则;2. 任何没有命令行的普通规则;3. 那些不是任何规则的目标、但它是另外某些规则的依赖文件;4. 在递归搜索过程中,隐含规则链中前一个规则的依赖文件。

在搜索过程中没有提到后缀规则,因为所有的后缀规则在make读取Makefile时,都被转换为对应的模式规则。

对于形式为「ARCHIVE(MEMBER)」的目标,下边的演算法会执行两次,第一次的目标是整个目标名「T」(「ARCHIVE(MEMBER)」),如果搜索失败,进行第二次搜索,第二次以「member」作为目标来搜索。

搜索过程如下:

1. 将目标「T」的目录部分分离,分离后目录部分称为「D」,其它部分称「N」。例如:「T」为「src/foo.o」时,D就是「src/」,「N」就为「foo.o」。

2. 列出所有和「T」或者「N」匹配的模式规则。如果模式规则的目标中包含斜杠,则认为和「T」相匹配,否则认为此模式规则和「N」相匹配。

3. 只要这个模式规则列表中包含一个非万用规则的规则,那么将列表中所有的非最终万用规则删除。

4. 删除这个模式规则列表中的所有没有命令行的规则。

5. 对于这个模式规则列表中的所有规则:

a) 计算出模式规则的「茎」S,S应该是「T」或「N」中匹配「%」的非空的部分。

b) 计算依赖文件。把依赖中的「%」用「S」替换。如果目标模式中不包含斜杠,则把「D」加在替换之后的每一个依赖文件开头,构成完整的依赖文件名。

c) 测试规则的所有依赖文件是否存在或是应该存在(一个文件,如果在Makefile中它作为一个明确规则的目标,或者依赖文件被提及,我们就说这个文件是一个「应该存在」的文件)。如果所有的依赖文件都存在、应该存在或是这个规则没有依赖。退出查找,使用该规则。

6. 截止到第5步,合适的规则还是没有被找到,进一步搜索。对于这个模式规则列表中的每一规则:

a) 如果规则是一个终止规则,则忽略它,继续下一条模式规则。

b) 计算依赖文件(同第5步)。

c) 测试此规则的依赖文件是否存在或是应该存在。

d) 对于此规则中不存在的依赖文件,递归的调用这个演算法查找它是否可由隐含规则来创建。

e) 如果所有的依赖文件存在、应该存在、或是它可以由一个隐含规则来创建。退出查找,使用该规则。

7. 如果没有隐含规则可以创建这个规则的依赖文件,则执行特殊目标「.DEFAULT」所指定的命令(可以创建这个文件,或者给出一个错误提示)。如果在Makefile中没有定义特殊目标「DEFAULT」,就没有可用的命令来完成「T」的创建。make退出。

一旦为一类目标查找到合适的模式规则。除匹配「T」或者「N」的模式以外,对其它模式规则中的目标模式进行配置,使用「茎」S替换其中的模式字元「%」,将得到的文件名保存直到执行命令更新这个目标文件(「T」)。在命令执行以后,把每一个储存的文件名放入资料库,并且标志为已更新,其时间戳和文件「T」相同。

在执行更新文件「T」的命令时,使用自动化变数表示规则中的依赖文件和目标文件。


下一章 上一章 目录


推荐阅读:
查看原文 >>
相关文章