1 0.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 ”),多个参数使用空格分开。当然也允许在程序的名字中包含参数。但是这种方式建议不要使用。
以下是一些作为程序名的隐含变量定义:
1 0.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 ”。
1 0.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 文法分析器参数。
1 0.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 的隐含规则表中,所有可用的优化规则处于首选地位。