语言: 日本語 | Español | Français | Português | 中文 | English
上一章 | 下一章 | 目录 | 英文原版(gnu.org)

10 隐含规则的使用

重新生成目标文件时,常常会用到某些固定的做法。例如,生成目标文件(object)的一种惯用方法,就是用 C 编译器 cc 从 C 源文件生成。

隐含规则(implicit rule)是一种把这类惯用做法预先告诉 make 的机制。有了它,当你想使用这些做法时,就不必每次都把它们详细写出来。例如,C 编译就有现成的隐含规则。具体会执行哪条隐含规则,由文件名决定。例如 C 编译通常是从 .c 文件生成 .o 文件。于是 make 一旦发现这种文件名末尾的组合,就会套用 C 编译用的隐含规则。

多条隐含规则也可以接连成链、依次套用。例如 make 会经由 .c 文件,从 .y 文件重新生成 .o 文件。

内置的隐含规则在其命令(recipe)中使用了若干变量。因此,只要改变这些变量的值,就能改变隐含规则的工作方式。例如,C 编译用的隐含规则传给 C 编译器的标志,就可以用变量 CFLAGS 来控制。

你也可以通过编写模式规则(pattern rule)来定义自己的隐含规则。

后缀规则(suffix rule)是定义隐含规则的一种更受限的方式。模式规则更通用、更清晰,但后缀规则为了兼容性而被保留下来。

10.1 使用隐含规则

要让 make 找到更新目标文件的惯用方法,你需要做的只是不要自己写命令。要么写一条没有命令的规则,要么干脆完全不写该目标的规则。这样 make 就会根据存在哪些源文件、或哪些源文件可以生成,来判断该使用哪条隐含规则。

例如,假设 makefile 写成下面这样:

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

由于这里提到了 foo.o 却没有给出它的规则,make 会自动去寻找一条隐含规则,以告诉它该如何更新 foo.o。无论文件 foo.o 当前是否存在,都会这样做。

如果找到了隐含规则,它会同时提供命令和一个或多个前置条件(prerequisite,即源文件)。当你需要指定某些隐含规则无法提供的额外前置条件(例如头文件)时,你会想为 foo.o 写一条没有命令的规则。

每条隐含规则都有一个目标模式和若干前置条件模式。具有相同目标模式的隐含规则可能有很多条。例如,生成「.o」文件的规则就有很多:有的用 C 编译器从「.c」文件生成,有的用 Pascal 编译器从「.p」文件生成,等等。实际被套用的,是那条其前置条件存在或可以生成的规则。也就是说,如果有文件 foo.c,make 就会运行 C 编译器;否则,如果有文件 foo.p,make 就会运行 Pascal 编译器;依此类推。

当然,在编写 makefile 时,你应当知道自己希望 make 使用哪条隐含规则。而且因为你知道哪些前置条件文件应当存在,所以你也应当知道 make 会选择那条规则。关于所有预定义隐含规则的清单,请参阅内置规则的目录

前面我们说过,当所需前置条件「存在或可以生成」时,隐含规则才会套用。某个文件「可以生成」,是指它在 makefile 中作为目标或前置条件被显式提到,或者可以递归地为它找到一条生成它的隐含规则。当某个隐含前置条件是另一条隐含规则的结果时,我们就说发生了连锁(chaining)。参阅隐含规则的链

一般来说,make 会为每个没有命令的目标、以及每条没有命令的双冒号规则搜索隐含规则。只作为前置条件被提到的文件,会被视为一个其规则什么都没指定的目标,因此也会对它进行隐含规则搜索。关于搜索如何进行的细节,请参阅隐含规则的搜索算法

请注意,显式写出的前置条件并不影响隐含规则的搜索。例如,考虑下面这条显式规则:

foo.o: foo.p

有了 foo.p 这个前置条件,并不一定意味着 make 会按照从 Pascal 源文件(.p 文件)生成目标文件(.o 文件)的隐含规则来重新生成 foo.o。例如,如果 foo.c 也存在,就会改用从 C 源文件生成目标文件的隐含规则,因为在预定义隐含规则的清单中,它出现在 Pascal 规则之前(参阅内置规则的目录)。

如果你不希望某个没有命令的目标使用隐含规则,可以通过写一个分号来给该目标一条空命令(参阅定义空命令)。

10.2 内置规则的目录

这里给出预定义隐含规则的目录。只要 makefile 没有显式地覆盖或取消它们,这些规则就始终可用。关于取消或覆盖隐含规则的方法,请参阅取消隐含规则。使用「-r」或「--no-builtin-rules」选项会取消所有预定义规则。

本手册只说明在基于 POSIX 的操作系统上可用的默认规则。VMS、Windows、OS/2 等其他操作系统的默认规则集可能不同。要查看你所使用的 GNU make 中可用的默认规则和变量的完整清单,请在一个没有 makefile 的目录里运行「make -p」。

即使没有指定「-r」选项,这些规则也不一定全部都会被定义。许多预定义隐含规则在 make 内部是以后缀规则实现的。因此,具体会定义哪些规则,取决于后缀列表(特殊目标 .SUFFIXES 的前置条件列表)。默认的后缀列表如下: .out.a.ln.o.c.cc.C.cpp.p.f.F.m.r.y.l.ym.lm.s.S.mod.sym.def.h.info.dvi.tex.texinfo.texi.txinfo.w.ch .web.sh.elc.el。 下面将要说明的隐含规则中,凡是前置条件带有这些后缀之一的,其实都是后缀规则。如果你修改了后缀列表,那么生效的预定义后缀规则就只剩下那些其后缀(一个或两个)出现在你指定的列表中的规则;后缀不在列表里的规则将被禁用。关于后缀规则的详细内容,请参阅老式的后缀规则

编译 C 程序

n.o 会自动从 n.c 生成,所用命令形如「$(CC) $(CPPFLAGS) $(CFLAGS) -c」。

编译 C++ 程序

n.o 会自动从 n.ccn.cppn.C 生成,所用命令形如「$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c」。我们建议 C++ 源文件使用「.cc」或「.cpp」后缀,而不要用「.C」,以便更好地兼容不区分大小写的文件系统。

编译 Pascal 程序

n.o 会自动从 n.p 生成,所用命令为「$(PC) $(PFLAGS) -c」。

编译 Fortran 和 Ratfor 程序

n.o 会通过运行 Fortran 编译器,自动从 n.rn.Fn.f 生成。实际使用的命令如下:

.f

$(FC) $(FFLAGS) -c」。

.F

$(FC) $(FFLAGS) $(CPPFLAGS) -c」。

.r

$(FC) $(FFLAGS) $(RFLAGS) -c」。

预处理 Fortran 和 Ratfor 程序

n.f 会自动从 n.rn.F 生成。这条规则只运行预处理器,把 Ratfor 程序或需要预处理的 Fortran 程序转换成纯 Fortran 程序。实际使用的命令如下:

.F

$(FC) $(CPPFLAGS) $(FFLAGS) -F」。

.r

$(FC) $(FFLAGS) $(RFLAGS) -F」。

编译 Modula-2 程序

n.sym 会从 n.def 生成,所用命令形如「$(M2C) $(M2FLAGS) $(DEFFLAGS)」。n.o 会从 n.mod 生成,其形式为「$(M2C) $(M2FLAGS) $(MODFLAGS)」。

汇编与预处理汇编程序

n.o 会通过运行汇编器 as,自动从 n.s 生成。实际命令为「$(AS) $(ASFLAGS)」。

n.s 会通过运行 C 预处理器 cpp,自动从 n.S 生成。实际命令为「$(CPP) $(CPPFLAGS)」。

链接单个目标文件

n 会通过运行 C 编译器来链接程序,自动从 n.o 生成。实际使用的命令为「$(CC) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS)」。

对于只有一个源文件的简单程序,这条规则能恰到好处地发挥作用。此外,当有(大概来自其他若干源文件的)多个目标文件,其中一个的名字与可执行文件的名字一致时,它也能很好地工作。因此,

x: y.o z.o

这条规则在 x.cy.cz.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

在更复杂的情况下,例如不存在名字由可执行文件名派生而来的目标文件时,你就必须自己为链接编写显式命令。

各种被自动生成为「.o」目标文件的文件,都会通过不带「-c」选项地使用编译器(「$(CC)」、「$(FC)」或「$(PC)」;汇编「.s」文件时使用 C 编译器「$(CC)」)而被自动链接。这本来也可以用「.o」目标文件作为中间文件来实现,但由于把编译和链接合为一步更快,所以才这样做。

用于 C 程序的 Yacc

n.c 会通过用命令「$(YACC) $(YFLAGS)」运行 Yacc,自动从 n.y 生成。

用于 C 程序的 Lex

n.c 会通过运行 Lex,自动从 n.l 生成。实际命令为「$(LEX) $(LFLAGS)」。

用于 Ratfor 程序的 Lex

n.r 会通过运行 Lex,自动从 n.l 生成。实际命令为「$(LEX) $(LFLAGS)」。

由于存在这样一个惯例:不论 Lex 文件生成的是 C 代码还是 Ratfor 代码,都对它使用相同的「.l」后缀,因此 make 无法在具体情况下自动判别使用的是这两种语言中的哪一种。当 make 被要求从「.l」文件重新生成目标文件时,它必须猜测该用哪种编译器。make 会猜是 C 编译器,因为那更常见。如果你使用的是 Ratfor,请在 makefile 中提到 n.r,以此让 make 知道这一点。或者,如果你完全不用 C 文件、只用 Ratfor,那就按如下方式从隐含规则的后缀列表中去掉「.c」:

.SUFFIXES:
.SUFFIXES: .o .r .f .l …
从 C、Yacc、Lex 程序生成 Lint 库

n.ln 会通过运行 lint,从 n.c 生成。实际命令为「$(LINT) $(LINTFLAGS) $(CPPFLAGS) -i」。同样的命令也用于从 n.yn.l 生成的 C 代码。

TeX 与 Web

n.dvi 会用命令「$(TEX)」从 n.tex 生成。n.tex 会用「$(WEAVE)」从 n.web 生成,或用「$(CWEAVE)」从 n.w(以及 n.ch,如果它存在或可以生成的话)生成。n.p 会用「$(TANGLE)」从 n.web 生成,n.c 会用「$(CTANGLE)」从 n.w(以及 n.ch,如果它存在或可以生成的话)生成。

Texinfo 与 Info

n.dvi 会用命令「$(TEXI2DVI) $(TEXI2DVI_FLAGS)」从 n.texinfon.texin.txinfo 生成。n.info 会用命令「$(MAKEINFO) $(MAKEINFO_FLAGS)」从 n.texinfon.texin.txinfo 生成。

RCS

任意文件 n 会在需要时从名为 n,vRCS/n,v 的 RCS 文件中取出。实际使用的命令为「$(CO) $(COFLAGS)」。如果 n 已经存在,那么即使 RCS 文件更新,也不会从 RCS 中取出它。RCS 的规则是终端(terminal)的(参阅匹配任意内容的模式规则)。因此,RCS 文件不能由其他来源生成,它们必须实际存在。

SCCS

任意文件 n 会在需要时从名为 s.nSCCS/s.n 的 SCCS 文件中取出。实际使用的命令为「$(GET) $(GFLAGS)」。SCCS 的规则是终端(terminal)的(参阅匹配任意内容的模式规则)。因此,SCCS 文件不能由其他来源生成,它们必须实际存在。

为方便 SCCS,文件 n 会从 n.sh 复制而来,并被设为(对所有用户)可执行。这是为了那些签入到 SCCS 的 shell 脚本。由于 RCS 会保留文件的执行权限,因此在 RCS 中无需使用这个功能。

我们建议你避免使用 SCCS。RCS 被广泛认为更优秀,而且也是自由的。通过选择自由软件来取代同等(或更差)的专有软件,你就是在支持自由软件运动。

通常你只会想修改上表所列的那些变量。这些变量将在下一节说明。

不过,内置隐含规则的命令实际上使用的是 COMPILE.cLINK.pPREPROCESS.S 之类的变量。上面列出的命令就包含在这些变量的值之中。

make 遵循这样一个约定:编译 .x 源文件的规则使用变量 COMPILE.x。同样,从 .x 文件生成可执行文件的规则使用 LINK.x,预处理 .x 文件的规则使用 PREPROCESS.x

凡是生成目标文件的规则,都会使用变量 OUTPUT_OPTIONmake 会根据编译时的某个选项,把这个变量定义为含有「-o $@」的值,或定义为空。当源文件位于不同的目录中(例如使用 VPATH 时,参阅为前置条件搜索目录),为了让输出进入正确的文件,就需要「-o」选项。然而,某些系统上的编译器并不接受针对目标文件的「-o」开关。如果你在这样的系统上使用 VPATH,有些编译就会把输出放到错误的位置。对此问题的一种变通办法是,给 OUTPUT_OPTION 赋予「; mv $*.o $@」这样的值。

10.3 隐含规则使用的变量

内置隐含规则的命令大量使用了若干预定义变量。你可以在 makefile 中、用传给 make 的参数、或在环境中改变这些变量的值,从而在不重新定义规则本身的前提下,改变隐含规则的工作方式。隐含规则使用的所有变量,都可以用「-R」或「--no-builtin-variables」选项来取消。

例如,用来编译 C 源文件的命令实际上是「$(CC) -c $(CFLAGS) $(CPPFLAGS)」。所用变量的默认值是「cc」和空字符串,因此结果就是「cc -c」这个命令。把「CC」重新定义为「ncc」,就能让隐含规则进行的所有 C 编译都使用「ncc」。把「CFLAGS」重新定义为「-g」,就能给每次编译都传入「-g」选项。所有进行 C 编译的隐含规则都用「$(CC)」来获取编译器的程序名,并且全都会把「$(CFLAGS)」包含在传给编译器的参数之中。

隐含规则中使用的变量分为两类。一类是程序名,如 CC;另一类含有传给程序的参数,如 CFLAGS。(「程序名」中也可以含有若干命令参数,但必须以实际的可执行程序名开头。)如果某个变量的值含有多个参数,就用空格把它们隔开。

下面的表格说明了若干较常用的预定义变量。这份清单并不详尽,而且这里给出的默认值可能与 make 在你的环境中所选的不同。要查看你所使用的 GNU make 的预定义变量的完整清单,请在一个没有 makefile 的目录里运行「make -p」。

这里给出在内置规则中用作程序名的、较常用的变量表:

AR

归档管理程序。默认为「ar」。

AS

编译汇编文件的程序。默认为「as」。

CC

编译 C 程序的程序。默认为「cc」。

CXX

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

CPP

运行 C 预处理器并把结果输出到标准输出的程序。默认为「$(CC) -E」。

FC

编译或预处理 Fortran 和 Ratfor 程序的程序。默认为「f77」。

M2C

用于编译 Modula-2 源代码的程序。默认为「m2c」。

PC

编译 Pascal 程序的程序。默认为「pc」。

CO

从 RCS 中取出文件的程序。默认为「co」。

GET

从 SCCS 中取出文件的程序。默认为「get」。

LEX

用于把 Lex 文法定义转换成源代码的程序。默认为「lex」。

YACC

用于把 Yacc 文法定义转换成源代码的程序。默认为「yacc」。

LINT

用于对源代码运行 lint 的程序。默认为「lint」。

MAKEINFO

把 Texinfo 源文件转换成 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」。

这里给出一个变量表,其值是上述程序的附加参数。除非另有说明,这些变量的默认值都是空字符串。

ARFLAGS

给归档管理程序的标志。默认为「rv」。

ASFLAGS

(在针对「.s」或「.S」文件显式调用时)给汇编器的额外标志。

CFLAGS

给 C 编译器的额外标志。

CXXFLAGS

给 C++ 编译器的额外标志。

COFLAGS

给 RCS 的 co 程序的额外标志。

CPPFLAGS

给 C 预处理器以及使用它的程序(C 和 Fortran 编译器)的额外标志。

FFLAGS

给 Fortran 编译器的额外标志。

GFLAGS

给 SCCS 的 get 程序的额外标志。

LDFLAGS

当编译器需要调用链接器「ld」时,给这些编译器的额外标志,例如 -L。库(-lfoo)应改为加入 LDLIBS 变量。

LDLIBS

当编译器需要调用链接器「ld」时,给这些编译器的库标志或库名。LOADLIBESLDLIBS 的一个已弃用(但目前仍受支持)的替代。像 -L 这类非库的链接器标志,应放进 LDFLAGS 变量。

LFLAGS

给 Lex 的额外标志。

YFLAGS

给 Yacc 的额外标志。

PFLAGS

给 Pascal 编译器的额外标志。

RFLAGS

为 Ratfor 程序而给 Fortran 编译器的额外标志。

LINTFLAGS

给 lint 的额外标志。

10.4 隐含规则的链

有时,一个文件可以由一连串隐含规则接连生成。例如,文件 n.o 可以先运行 Yacc、再运行 cc,从 n.y 生成。这样的一连串称为一条(chain)。

如果文件 n.c 存在,或者在 makefile 中被提到,就不需要特别的搜索:make 会发现目标文件可以由 n.c 经 C 编译生成。随后,在考虑如何生成 n.c 时,就会用上运行 Yacc 的规则。最终 n.cn.o 都会被更新。

然而,即使 n.c 不存在、也没有在 makefile 中被提到,make 也能把它设想为连接 n.on.y 之间的那个缺失环节!这种情况下,n.c 就被称为中间文件(intermediate file)。一旦 make 决定使用某个中间文件,它就会被登记进数据库,仿佛它本来就在 makefile 中被提到过一样,并附带那条说明如何生成它的隐含规则。

中间文件和所有其他文件一样,都按各自的规则被重新生成。但中间文件在两个方面受到不同的对待。

第一个区别在于:当中间文件不存在时会发生什么。当一个普通文件 b 不存在,而 make 正在考虑一个依赖于 b 的目标时,make 一定会先生成 b,然后再据此更新该目标。但如果 b 是中间文件,那么 make 就可以听之任之:除非 b 的某个前置条件已经过时,否则 make 不会生成 b。这意味着,除非有别的理由需要更新依赖于 b 的那个目标(例如该目标不存在,或别的前置条件比该目标更新),否则它也不会被重新生成。

第二个区别在于:如果 make 为了更新别的东西而确实生成了 b,那么在不再需要 b 之后,它会把 b 删除。因此,一个在运行 make 之前并不存在的中间文件,在 make 运行之后也不会存在。make 会通过打印一条显示它正在删除哪个文件的「rm」命令,把删除一事告诉你。

你可以把某个文件列为特殊目标 .INTERMEDIATE 的前置条件,从而显式地把它指定为中间文件。即使该文件以其他某种方式被显式提到,这一指定也仍然有效。

如果某个文件在 makefile 中作为目标或前置条件被提到,它就不能成为中间文件。因此,避免中间文件被删除的一种办法,就是把它添加为某个目标的前置条件。不过,这样做可能会使 make 在搜索模式规则时做额外的工作(参阅隐含规则的搜索算法)。

另一种办法是,把某个文件列为特殊目标 .NOTINTERMEDIATE 的前置条件,这会(就像以其他方式提到该文件一样)使它不再被视为中间文件。此外,把某条模式规则的目标模式列为 .NOTINTERMEDIATE 的前置条件,可以确保用该模式规则生成的目标都不被视为中间文件。

要在 makefile 中完全禁用中间文件,可把 .NOTINTERMEDIATE 作为一个没有前置条件的目标给出:这种情况下,它会应用于 makefile 中的每一个文件。

如果你不希望 make 仅仅因为某个文件尚不存在就去生成它,但也不希望 make 自动删除该文件,那么你可以把它指定为次级(secondary)文件。为此,把它列为特殊目标 .SECONDARY 的前置条件。把一个文件指定为次级文件,也会同时把它指定为中间文件。

一条链中可以牵涉到两条以上的隐含规则。例如,可以通过运行 RCS、Yacc 和 cc,从 RCS/foo.y,v 生成文件 foo。这种情况下,foo.yfoo.c 都是中间文件,最后都会被删除。

同一条隐含规则不能在一条链中出现两次以上。也就是说,make 根本不会去考虑诸如运行链接器两次、从 foo.o.o 生成 foo 这种荒唐的事情。这一限制还带来一个附带的好处:防止隐含规则链的搜索陷入无限循环。

有一些特殊的隐含规则,用来对那些本应由链来处理的特定情形进行优化。例如,从 foo.c 生成 foo 这件事,本可以用 foo.o 作为中间文件,通过分开的链规则来分别编译和链接处理。但实际发生的是,一条专门针对这种情形的特殊规则,用一条 cc 命令完成编译和链接。这条优化后的规则在规则的排序中出现得更早,因此会优先于逐步的链而被使用。

最后,出于性能上的原因,当 make 搜索用来生成某条隐含规则前置条件的规则时,不会考虑非终端的匹配任意内容的规则(即「%:」)(参阅匹配任意内容的模式规则)。

10.5 模式规则的定义与再定义

隐含规则是通过编写模式规则(pattern rule)来定义的。模式规则与普通规则相似,区别在于其目标中含有(恰好一个)字符「%」。该目标被当作用于匹配文件名的模式。「%」可以匹配任意非空子串,而其他字符只能匹配它们自身。前置条件也同样使用「%」,以表明它们的名字与目标名之间的关系。

因此,模式规则「%.o : %.c」说明的是如何从另一个文件 stem.c 生成任意文件 stem.o

请注意,模式规则中使用「%」的展开,是在任何变量或函数展开之后才进行的;而变量或函数的展开是在读取 makefile 时进行的。参阅变量的使用方法以及用于变换文本的函数

10.5.1 模式规则入门

模式规则的目标中含有(恰好一个)字符「%」。在其他方面,它看起来与普通规则完全一样。目标是用于匹配文件名的模式。「%」匹配任意非空子串,而其他字符只能匹配它们自身。

例如,作为模式的「%.c」匹配任何以「.c」结尾的文件名。作为模式的「s.%.c」匹配任何以「s.」开头、以「.c」结尾、且至少有 5 个字符长的文件名。(「%」至少要匹配一个字符。)被「%」匹配到的那个子串称为词干(stem)。

模式规则前置条件中的「%」,代表与目标中「%」所匹配到的相同的词干。要使某条模式规则得以套用,其目标模式必须匹配正在考虑的文件名,并且(经模式替换后的)所有前置条件都必须指向存在或可以生成的文件。这些文件就成为该目标的前置条件。

因此,下面这种形式的规则:

%.o : %.c ; recipe

指定的是:在文件 n.c 存在或可以生成的条件下,如何以另一个文件 n.c 作为前置条件来生成文件 n.o

也可以有不使用「%」的前置条件。这样的前置条件会附加到这条模式规则所生成的每一个文件上。这种不变的前置条件,偶尔会很有用。

模式规则不一定要有含「%」的前置条件,实际上甚至可以完全没有前置条件。这样的规则实际上起着通用通配符的作用。它提供了一种生成任何匹配目标模式的文件的方法。参阅定义最后手段的默认规则

可能有多条模式规则匹配同一个目标。这种情况下,make 会选择「最匹配」的那条规则。参阅模式是如何匹配的

模式规则也可以有多个目标。不过,所有目标都必须含有 % 字符。模式规则中的多个目标模式,无论使用 : 还是 &: 分隔符,都始终被当作分组目标来处理(参阅一条规则中的多个目标)。

不过有一个例外。当某个模式目标已过时或不存在,而 makefile 又无需构建它时,该目标不会成为使其他目标被视为过时的原因。请注意,这个历史性的例外预计会在 GNU make 的未来版本中被移除,因此不应依赖它。当 make 检测到这种情形时,会发出 pattern recipe did not update peer target(模式命令未更新与之配对的目标)的警告。不过,make 并不能检测出所有这样的情形。当命令运行时,务必确保所有目标模式都一定会被更新。

10.5.2 模式规则示例

这里给出 make 中实际预定义的几个模式规则示例。首先是把「.c」文件编译成「.o」文件的规则:

%.o : %.c
        $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

它定义了一条可以从 x.c 生成任意文件 x.o 的规则。这条命令使用自动变量「$@」和「$<」,在规则套用的每种情形下替换出对应的目标文件名和源文件名(参阅自动变量)。

第二条内置规则:

% :: RCS/%,v
        $(CO) $(COFLAGS) $<

它定义了一条规则,可以从子目录 RCS 中对应的文件 x,v 生成任意文件 x。由于目标是「%」,只要存在合适的前置条件文件,这条规则就能套用于任何文件。双冒号使这条规则成为终端(terminal)的。这意味着它的前置条件不能是中间文件(参阅匹配任意内容的模式规则)。

这条模式规则有两个目标:

%.tab.c %.tab.h: %.y
        bison -d $<

它告诉 make:命令「bison -d x.y」会同时生成 x.tab.cx.tab.h。如果文件 foo 依赖于文件 parse.tab.oscan.o,而文件 scan.o 又依赖于文件 parse.tab.h,那么当 parse.y 被改动时,命令「bison -d parse.y」只会执行一次,parse.tab.oscan.o 两者的前置条件就都得到了满足。(大概,文件 parse.tab.o 会从 parse.tab.c 重新编译,文件 scan.o 会从 scan.c 重新编译,而 foo 则会从 parse.tab.oscan.o 以及其他前置条件链接而成,此后便能皆大欢喜地继续工作下去。)

10.5.3 自动变量

假设你正在写一条把「.c」文件编译成「.o」文件的模式规则。要让它对正确的源文件名起作用,「cc」命令该怎么写呢?你不能把那个名字写死在命令里,因为每次套用隐含规则时名字都不一样。

这时就要用到 make 的一个特殊功能——自动变量(automatic variable)。对于每条被执行的规则,这些变量都会根据该规则的目标和前置条件,每次重新计算出值来。在这个例子中,目标文件名要用「$@」,源文件名要用「$<」。

认识到自动变量的取值范围是有限的,这一点非常重要。它们只在命令之中才有值。特别是,你不能在规则的目标列表中的任何地方使用它们,在那里它们没有值,会展开成空字符串。此外,在规则的前置条件列表中也不能直接访问它们。一个常见的错误是试图在前置条件列表中使用 $@,这是行不通的。不过,GNU make 有一个叫二次展开(参阅二次展开)的特殊功能,用它就可以在前置条件列表中使用自动变量的值。

这里给出自动变量的表:

$@

规则目标的文件名。当目标是归档成员时,「$@」就是归档文件的名字。在有多个目标的模式规则中(参阅模式规则入门),「$@」是引发该规则命令被执行的那个目标的名字。

$%

当目标是归档成员时,目标的成员名。参阅用 make 更新归档文件。例如,当目标是 foo.a(bar.o) 时,「$%」是 bar.o,「$@」是 foo.a。当目标不是归档成员时,「$%」为空。

$<

第一个前置条件的名字。如果目标的命令来自某条隐含规则,那么这就是由该隐含规则添加的第一个前置条件(参阅隐含规则的使用)。

$?

比目标更新的所有前置条件的名字,以空格分隔排列。如果目标不存在,则包含所有前置条件。对于属于归档成员的前置条件,只使用指定的成员(参阅用 make 更新归档文件)。

当你只想处理被改动过的前置条件时,「$?」在显式规则中也很有用。例如,假设有一个名为 lib 的归档,要用来存放若干目标文件的副本。下面这条规则只把被改动过的目标文件复制进归档:

lib: foo.o bar.o lose.o win.o
        ar r lib $?
$^

所有前置条件的名字,以空格分隔排列。对于属于归档成员的前置条件,只使用指定的成员(参阅用 make 更新归档文件)。一个目标对它所依赖的每个文件只有一个前置条件,无论同一个文件作为前置条件被排列了多少次。因此,即使对同一个目标把相同的前置条件排列两次以上,$^ 的值中也只会含有该名字的一个副本。这份列表包含仅次序前置条件(order-only),关于它们请参阅后面的「$|」变量。

$+

这与「$^」相似,但被排列两次以上的前置条件会按它们在 makefile 中排列的顺序重复包含进来。这主要在链接命令中有用,因为在那里按特定顺序重复库文件名是有意义的。

$|

所有仅次序前置条件(order-only)的名字,以空格分隔排列。

$*

隐含规则所匹配到的词干(参阅模式是如何匹配的)。当目标是 dir/a.foo.b、目标模式是 a.%.b 时,词干是 dir/foo。词干有助于拼出相关文件的名字。

在静态模式规则中,词干是目标模式中「%」所匹配到的那部分文件名。

显式规则没有词干,所以无法用这种方式确定「$*」。但作为替代,如果目标名以某个已知后缀(参阅老式的后缀规则)结尾,那么「$*」就会被设为去掉该后缀后的目标名。例如,如果目标名是「foo.c」,由于「.c」是后缀,「$*」就会被设为「foo」。GNU make 之所以做这种古怪的事,纯粹是为了与其他 make 实现兼容。一般来说,除了在隐含规则或静态模式规则中,都不应使用「$*」。

当显式规则中的目标名并不以某个已知后缀结尾时,该规则中的「$*」会被设为空字符串。

在上面列出的变量中,有四个以单个文件名为值,有三个以文件名列表为值。这七个变量都有一种变体,可以只取出文件的目录名,或只取出目录内的文件名。变体变量的名字,是在原名末尾分别加上「D」或「F」构成的。用函数 dirnotdir 也能得到类似的效果(参阅用于文件名的函数)。不过请注意,所有「D」变体都会省去末尾的斜杠;而 dir 函数的输出中必定会出现末尾的斜杠。这里给出变体的表:

$(@D)

目标文件名的目录部分,去掉末尾的斜杠。如果「$@」的值是 dir/foo.o,那么「$(@D)」就是 dir。如果「$@」不含斜杠,这个值就是 .

$(@F)

目标文件名中、目录内的文件名部分。如果「$@」的值是 dir/foo.o,那么「$(@F)」就是 foo.o。「$(@F)」等价于「$(notdir $@)」。

$(*D)
$(*F)

词干的目录部分和目录内的文件名部分。在这个例子中分别是 dirfoo

$(%D)
$(%F)

目标归档成员名的目录部分和目录内的文件名部分。这只对形如 archive(member) 的归档成员目标有意义,而且只有在 member 可能含有目录名时才有用。(参阅作为目标的归档成员。)

$(<D)
$(<F)

第一个前置条件的目录部分和目录内的文件名部分。

$(^D)
$(^F)

所有前置条件的目录部分列表和目录内文件名部分列表。

$(+D)
$(+F)

所有前置条件的目录部分列表和目录内文件名部分列表,也包含重复前置条件的多个实例。

$(?D)
$(?F)

比目标更新的所有前置条件的目录部分列表和目录内文件名部分列表。

请注意,在谈到这些自动变量时,我们使用了一种特殊的文体惯例。我们不像写 objectsCFLAGS 这类普通变量时那样写「变量 <」,而是写「「$<」的值」,因为我们认为在这个特殊情形下,那种惯例看起来更自然。请不要以为这有什么深意。「$<」指的就是名为 < 的变量,正如「$(CFLAGS)」指的是名为 CFLAGS 的变量一样。把「$<」写成「$(<)」,完全是一回事。

10.5.4 模式是如何匹配的

目标模式由前缀、后缀以及夹在它们之间的「%」构成。前缀、后缀两者中的任何一个,或两者,都可以为空。模式匹配某个文件名,当且仅当该文件名以前缀开头、以后缀结尾,且二者不重叠。前缀和后缀之间的那段文本称为词干(stem)。因此,当模式「%.o」匹配文件名 test.o 时,词干就是「test」。模式规则的前置条件,会通过把字符「%」替换成词干而被转换成实际的文件名。因此,在同一个例子中,如果某个前置条件写成「%.c」,它就会展开成「test.c」。

当目标模式不含斜杠时(通常都不含),文件名中的目录名会在与目标的前缀、后缀比较之前先从文件名中去掉。在完成文件名与目标模式的比较之后,目录名连同其结尾的斜杠,会被加回到模式规则的前置条件模式和由文件名生成的前置条件文件名上。目录之所以被忽略,仅仅是为了寻找该用哪条隐含规则,而不是在该规则的套用中。因此,「e%t」匹配文件名 src/eat,词干为「src/a」。当前置条件被转换成文件名时,来自词干的目录会被加到开头,而词干的其余部分则替换进「%」。由词干「src/a」和前置条件模式「c%r」,可得到文件名 src/car

某条模式规则只有在以下情况下才能用来构建某个文件:有一个目标模式匹配它的文件名,并且该规则的所有前置条件都存在或可以构建。你写的规则优先于内置规则。但请注意,无需链接其他隐含规则即可满足的规则(例如没有前置条件、或前置条件已经存在或被提到的规则),始终优先于那些前置条件必须由链接其他隐含规则才能生成的规则。

满足这些标准的模式规则可能有不止一条。这种情况下,make 会选择词干最短的那条规则(也就是匹配得最具体的那个模式)。如果有多条模式规则的词干一样短,make 会选择在 makefile 中最先找到的那条。

这个算法的结果是:更具体的规则优先于更通用的规则。例如:

%.o: %.c
        $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

%.o : %.f
        $(COMPILE.F) $(OUTPUT_OPTION) $<

lib/%.o: lib/%.c
        $(CC) -fPIC -c $(CFLAGS) $(CPPFLAGS) $< -o $@

有了这些规则,当 bar.cbar.f 都存在、又被要求构建 bar.o 时,make 会选择第一条规则,把 bar.c 编译成 bar.o。在同样的情形下,如果 bar.c 不存在,make 就会选择第二条规则,把 bar.f 编译成 bar.o

make 被要求构建 lib/bar.o,且 lib/bar.clib/bar.f 都存在时,会选中第三条规则,因为这条规则的词干(「bar」)比第一条规则的词干(「lib/bar」)更短。如果 lib/bar.c 不存在,第三条规则就不再合格,即使词干更长,也会改用第二条规则。

10.5.5 匹配任意内容的模式规则

当一条模式规则的目标只是单纯的「%」时,它就能匹配任何文件名。我们把这样的规则称为匹配任意内容(match-anything)的规则。它们非常有用,但 make 考虑它们时可能要花很多时间,因为对于每个作为目标或前置条件被排列的文件名,都必须把所有这类规则都考虑一遍。

假设 makefile 提到了 foo.c。对于这个目标,make 就不得不去考虑诸如此类的种种可能:把它当作链接目标文件 foo.c.o 生成,或从 foo.c.c 一步完成 C 编译并链接生成,或从 foo.c.p 进行 Pascal 编译并链接生成,等等,还有许多其他可能。

由于 foo.c 是 C 源文件而不是可执行文件,我们知道这些可能性都很荒唐。即便 make 考虑了这些可能,由于 foo.c.ofoo.c.p 之类的文件并不存在,它最终也会把它们都否决掉。但这些可能性数量非常庞大,如果 make 不得不去考虑它们,就会变得非常慢。

为了提高速度,我们对 make 考虑匹配任意内容的规则的方式设置了种种限制。可施加的限制有两种,每当定义一条匹配任意内容的规则时,都必须为它二选其一。

一种选择是,通过用双冒号来定义这条匹配任意内容的规则,把它指定为终端(terminal)的。当一条规则是终端的时,只有在它的前置条件实际存在时,它才会套用;能用其他隐含规则生成的前置条件并不算数。换句话说,在终端规则之外,不允许再有进一步的链接。

例如,从 RCS 文件或 SCCS 文件取出源的内置隐含规则就是终端的。其结果是,当文件 foo.c,v 不存在时,make 根本不会去考虑把它当作从 foo.c,v.oRCS/SCCS/s.foo.c,v 生成的中间文件。RCS 文件和 SCCS 文件通常是终极的源文件,不应由任何其他文件重新生成。因此 make 通过不去寻找重新生成它们的方法,从而节省了时间。

如果你不把一条匹配任意内容的规则指定为终端的,它就成了非终端的。非终端的匹配任意内容的规则,不能套用于隐含规则的前置条件,也不能套用于表示某种特定数据的文件名。所谓某个文件名表示某种特定数据,是指它能匹配某条非匹配任意内容的隐含规则的目标。

例如,文件名 foo.c 匹配模式规则「%.c : %.y」(运行 Yacc 的规则)的目标。不论这条规则是否真的可套用(只有在存在 foo.y 文件时才会如此),仅凭其目标得以匹配这一事实,就足以阻止针对文件 foo.c 去考虑非终端的匹配任意内容的规则。因此 make 根本不会去考虑把 foo.c 当作从 foo.c.ofoo.c.cfoo.c.p 等生成的可执行文件。

这个限制的动机如下:非终端的匹配任意内容的规则,用于生成含有某种特定数据(例如可执行文件)的文件;而带有已知后缀的文件名,则表示另一种特定数据(例如 C 源文件)。

专门为了识别特定的文件名、使其不被考虑用非终端的匹配任意内容的规则,系统准备了特殊的内置虚设(dummy)模式规则。这些虚设规则既没有前置条件也没有命令,对于其他一切用途都被忽略。例如,下面这条内置隐含规则:

%.p :

存在的目的,是让 foo.p 这样的 Pascal 源文件能匹配某个特定的目标模式,从而避免把时间白白浪费在寻找 foo.p.ofoo.p.c 上。

像针对「%.p」这样的虚设模式规则,会为每一个被列为可用于后缀规则的后缀(参阅老式的后缀规则)而生成。

10.5.6 取消隐含规则

内置隐含规则(或你自己定义的规则),可以通过定义一条具有相同目标和前置条件、但带有不同命令的新模式规则来覆盖。一旦定义了新规则,内置规则就被替换。新规则在隐含规则序列中的位置,由你把这条新规则写在何处决定。

内置隐含规则可以通过定义一条具有相同目标和前置条件、但没有命令的模式规则来取消。例如,下面这条会取消运行汇编器的规则:

%.o : %.s

10.6 定义最后手段的默认规则

通过写一条没有前置条件、终端的匹配任意内容的模式规则,可以定义一条最后手段的隐含规则(参阅匹配任意内容的模式规则)。它与其他任何模式规则完全一样。唯一特别之处在于,它会匹配任何目标。因此,这种规则的命令会被用于所有那些既没有自己的命令、又没有任何其他隐含规则可套用的目标和前置条件。

例如,在测试 makefile 时,你也许并不在意源文件是否含有真实的数据,只在意它们是否存在。这种时候,你可以这样做:

%::
        touch $@

这样就能让所有(作为前置条件)需要的源文件被自动生成。

作为替代,你也可以定义一条命令,用于那些完全没有规则——甚至包括没有指定任何命令——的目标。这是通过为目标 .DEFAULT 写一条规则来实现的。这种规则的命令,会被用于所有既不作为任何显式规则的目标出现、又没有任何隐含规则可套用的前置条件。当然,除非你自己写,否则 .DEFAULT 规则并不存在。

如果你使用不带命令也不带前置条件的 .DEFAULT:

.DEFAULT:

那么此前积存在 .DEFAULT 上的命令就会被清除。于是 make 的行为就仿佛你从未定义过 .DEFAULT 一样。

如果你不希望某个目标从匹配任意内容的模式规则或 .DEFAULT 那里得到命令,但也不希望对该目标执行任何命令,那么你可以给它一条空命令(参阅定义空命令)。

你也可以用最后手段的规则来覆盖另一个 makefile 的一部分。参阅覆盖另一个 Makefile 的一部分

10.7 老式的后缀规则

后缀规则(suffix rule)是定义 make 隐含规则的老式方法。后缀规则已经过时,因为模式规则更通用、更清晰。GNU make 支持后缀规则,是为了兼容旧的 makefile。后缀规则有两种:双后缀(double-suffix)和单后缀(single-suffix)。

双后缀规则由一对后缀——目标后缀和源后缀——来定义。它匹配任何名字以目标后缀结尾的文件。对应的隐含前置条件,是把文件名的目标后缀替换成源后缀而得到的。双后缀规则「.c.o」(目标后缀和源后缀分别为「.o」和「.c」)等价于模式规则「%.o : %.c」。

单后缀规则由单个后缀来定义,这个后缀就是源后缀。它匹配任意文件名,对应的隐含前置条件名是把源后缀加到末尾而得到的。源后缀为「.c」的单后缀规则,等价于模式规则「% : %.c」。

后缀规则的定义,是通过把每条规则的目标与已知后缀的预定义列表相对照来识别的。当 make 发现一条其目标是某个已知后缀的规则时,就把它视为单后缀规则。当 make 发现一条其目标是两个已知后缀相连接的规则时,就把它视为双后缀规则。

例如,「.c」和「.o」都在默认的已知后缀列表中。因此,如果你定义一条目标为「.c.o」的规则,make 就会把它视为源后缀为「.c」、目标后缀为「.o」的双后缀规则。这里给出用老式方法定义的、编译 C 源文件的规则:

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

后缀规则不能有自己的前置条件。如果它有前置条件,这些前置条件就不会被当作后缀规则,而会被当作名字古怪的普通文件来对待。因此,下面这条规则:

.c.o: foo.h
        $(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<

说的是如何从前置条件文件 foo.h 生成文件 .c.o,这与下面这条模式规则:

%.o: %.c foo.h
        $(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<

完全是两码事。后者说的是如何从「.c」文件生成「.o」文件,同时让这条模式规则生成的所有「.o」文件也都依赖于 foo.h

没有命令的后缀规则也毫无意义。它并不像没有命令的模式规则那样会移除先前的规则(参阅取消隐含规则),它只是把该后缀、或相连的那对后缀,作为目标登记进数据库而已。

所谓已知后缀,说到底就是特殊目标 .SUFFIXES 的前置条件的名字。你可以通过写一条为 .SUFFIXES 添加前置条件的规则来添加自己的后缀。像这样:

.SUFFIXES: .hack .win

这会把「.hack」和「.win」添加到后缀列表的末尾。

如果你想去掉默认的已知后缀、而不是在其上添加,那就写一条没有前置条件的、针对 .SUFFIXES 的规则。通过一种特殊的安排,这会去掉 .SUFFIXES 的全部现有前置条件。之后,你可以再写一条规则来添加你想要的后缀。例如:

.SUFFIXES:            # 删除默认后缀
.SUFFIXES: .c .o .h   # 定义自己的后缀列表

使用「-r」或「--no-builtin-rules」标志,会使默认的后缀列表为空。

变量 SUFFIXESmake 读取任何 makefile 之前,就被定义为默认的后缀列表。你可以用针对特殊目标 .SUFFIXES 的规则来改变后缀列表,但那并不会改变这个变量。

10.8 隐含规则的搜索算法

这里给出 make 针对目标 t 搜索隐含规则时所遵循的步骤。对于每条没有命令的双冒号规则、每条没有命令的普通规则的每个目标、以及不作为任何规则目标的每个前置条件,都会遵循这一步骤。此外,对于来自隐含规则的前置条件,在寻找规则链的过程中也会递归地遵循它。

这个算法之所以没有提到后缀规则,是因为后缀规则会在 makefile 被读取之后转换成等价的模式规则。

对于形如「archive(member)」的归档成员目标,下面的算法会执行两次。第一次使用整个目标名 t;如果第一次没有找到规则,第二次就用「(member)」作为目标 t

  1. t 分成目录部分(称为 d)和其余部分(称为 n)。例如,如果 t 是「src/foo.o」,那么 d 就是「src/」,n 就是「foo.o」。
  2. 列出所有那些有一个目标匹配 tn 的模式规则。如果目标模式含有斜杠,就拿它和 t 相对照,否则和 n 相对照。
  3. 如果这份列表中有任何一条不是匹配任意内容的规则,或者 t 是某条隐含规则的前置条件,那么就从列表中去掉所有非终端的匹配任意内容的规则。
  4. 从列表中去掉所有没有命令的规则。
  5. 对列表中的每条模式规则:
    1. 找出词干 s。它是目标模式中「%」所匹配到的 tn 的非空部分。
    2. s 代入「%」来计算前置条件名。如果目标模式不含斜杠,就把 d 加到每个前置条件名的开头。
    3. 测试是否所有前置条件都存在或应当存在。(如果某个文件名在 makefile 中作为目标、或作为目标 T 的显式前置条件被提到,我们就说它应当存在。)

      如果所有前置条件都存在或应当存在,或者根本没有前置条件,那么这条规则就套用。

  6. 如果到此还没有找到模式规则,那就再加把劲。对列表中的每条模式规则:
    1. 如果这条规则是终端的,就忽略它,转到下一条规则。
    2. 像前面一样计算前置条件名。
    3. 测试是否所有前置条件都存在或应当存在。
    4. 对每个不存在的前置条件,递归地遵循这个算法,看看该前置条件能否用隐含规则生成。
    5. 如果所有前置条件都存在、或应当存在、或可以用隐含规则生成,那么这条规则就套用。
  7. 如果还是没找到模式规则,那就修改「应当存在」的定义,再次尝试步骤 5 和步骤 6。也就是说,如果某个文件名作为目标、或作为任何目标的显式前置条件被提到,就认为它应当存在。这项检查只是为了与旧版本的 GNU Make 向后兼容才存在的。不建议依赖它。
  8. 如果没有任何隐含规则可套用,那么只要有针对 .DEFAULT 的规则,它就会被套用。这种情况下,t 会被赋予 .DEFAULT 所拥有的那条命令。否则,t 就没有命令。

一旦找到了可套用的规则,那么对于该规则中除匹配 tn 的那个之外的每个目标模式,模式中的「%」都会被替换成 s,而得到的文件名会一直保存到重新生成目标文件 t 的命令被执行为止。命令执行之后,这些被保存的文件名中的每一个都会被登记进数据库,并被标记为已更新、且具有与文件 t 相同的更新状态。

当一条模式规则的命令针对 t 执行时,自动变量会按其目标和前置条件相应地设置。参阅自动变量


上一章 | 下一章 | 目录 | 英文原版(gnu.org)