Linux下makefile教程(四)

隐含规则
————
在我们使用Makefile时,有一些我们会经常使用,而且使用频率非常高的东西,比如,我
们编译C/C++的源程序为中间目标文件(Unix下是[.o] 文件,Windows下是[.obj]文件)。
本章讲述的就是一些在Makefile中的“隐含的”,早先约定了的,不需要我们再写出来的
规则。

“隐含规则”也就是一种惯例,make会按照这种“惯例”心照不喧地来运行,那怕我们的
Makefile中没有书写这样的规则。例如,把[.c]文件编译成[.o]文件这一规则,你根本就
不用写出来,make会自动推导出这种规则,并生成我们需要的[.o]文件。

“隐含规则”会使用一些我们系统变量,我们可以改变这些系统变量的值来定制隐含规则
的运行时的参数。如系统变量“CFLAGS”可以控制编译时的编译器参数。

我们还可以通过“模式规则”的方式写下自己的隐含规则。用“后缀规则”来定义隐含规
则会有许多的限制。使用“模式规则”会更回得智能和清楚,但“后缀规则”可以用来保
证我们Makefile的兼容性。
我们了解了“隐含规则”,可以让其为我们更好的服务,也会让我们知道一些“约定俗成
”了的东西,而不至于使得我们在运行Makefile时出现一些我们觉得莫名其妙的东西。当
然,任何事物都是矛盾的,水能载舟,亦可覆舟,所以,有时候“隐含规则”也会给我们
造成不小的麻烦。只有了解了它,我们才能更好地使用它。

一、使用隐含规则


如果要使用隐含规则生成你需要的目标,你所需要做的就是不要写出这个目标的规则。那
么,make会试图去自动推导产生这个目标的规则和命令,如果make可以自动推导生成这个
目标的规则和命令,那么这个行为就是隐含规则的自动推导。当然,隐含规则是make事先
约定好的一些东西。例如,我们有下面的一个Makefile:

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

我们可以注意到,这个Makefile中并没有写下如何生成foo.o和bar.o这两目标的规则和命
令。因为make的“隐含规则”功能会自动为我们自动去推导这两个目标的依赖目标和生成
命令。

make 会在自己的“隐含规则”库中寻找可以用的规则,如果找到,那么就会使用。如果找
不到,那么就会报错。在上面的那个例子中,make调用的隐含规则是,把 [.o]的目标的依
赖文件置成[.c],并使用C的编译命令“cc –c $(CFLAGS) [.c]”来生成[.o]的目标。也
就是说,我们完全没有必要写下下面的两条规则:

foo.o : foo.c
cc –c foo.c $(CFLAGS)
bar.o : bar.c
cc –c bar.c $(CFLAGS)


因为,这已经是“约定”好了的事了,make和我们约定好了用C编译器“cc”生成[.o]文件
的规则,这就是隐含规则。

当然,如果我们为[.o]文件书写了自己的规则,那么make就不会自动推导并调用隐含规则
,它会按照我们写好的规则忠实地执行。

还有,在make的“隐含规则库”中,每一条隐含规则都在库中有其顺序,越靠前的则是越
被经常使用的,所以,这会导致我们有些时候即使我们显示地指定了目标依赖,make也不
会管。如下面这条规则(没有命令):

foo.o : foo.p

依赖文件“foo.p”(Pascal程序的源文件)有可能变得没有意义。如果目录下存在了“f
oo.c”文件,那么我们的隐含规则一样会生效,并会通过 “foo.c”调用C的编译器生成f
oo.o文件。因为,在隐含规则中,Pascal的规则出现在C的规则之后,所以,make找到可以
生成foo.o的 C的规则就不再寻找下一条规则了。如果你确实不希望任何隐含规则推导,那
么,你就不要只写出“依赖规则”,而不写命令。

二、隐含规则一览

这里我们将讲述所有预先设置(也就是make内建)的隐含规则,如果我们不明确地写下规
则,那么,make就会在这些规则中寻找所需要规则和命令。当然,我们也可以使用make的
参数“-r”或“--no-builtin-rules”选项来取消所有的预设置的隐含规则。

当然,即使是我们指定了“-r”参数,某些隐含规则还是会生效,因为有许多的隐含规则
都是使用了“后缀规则”来定义的,所以,只要隐含规则中有“后缀列表 ”(也就一系统
定义在目标.SUFFIXES的依赖目标),那么隐含规则就会生效。默认的后缀列表是:.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)”
“.f” “$(FC) –c $(FFLAGS) $(RFLAGS)”


5、预处理Fortran/Ratfor程序的隐含规则。
“<n>.f”的目标的依赖目标会自动推导为“<n>.r”或“<n>.F”。这个规则只是转换Rat
for或有预处理的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>.o” 的目标的依赖目标会自动推导为“<n>.s”,默认使用编译品“as”,并且其生
成命令是:“$(AS) $(ASFLAGS)”。“<n>.s” 的目标的依赖目标会自动推导为“<n>.S”
,默认使用C预编译器“cpp”,并且其生成命令是:“$(AS) $(ASFLAGS)”。

8、链接Object文件的隐含规则。
“<n>” 目标依赖于“<n>.o”,通过运行C的编译器来运行链接程序生成(一般是“ld”
),其生成命令是:“$(CC) $(LDFLAGS) <n>.o $(LOADLIBES) $(LDLIBS)”。这个规则对
于只有一个源文件的工程有效,同时也对多个Object文件(由不同的源文件生成)的也有
效。例如如下规则:

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.c)和你的目标名字(如上例中的x)相关联,那么,
你最好写出自己的生成规则,不然,隐含规则会报错的。

9、Yacc C程序时的隐含规则。
“<n>.c”的依赖文件被自动推导为“n.y”(Yacc生成的文件),其生成命令是:“$(YA
CC) $(YFALGS)”。(“Yacc”是一个语法分析器,关于其细节请查看相关资料)

10、Lex C程序时的隐含规则。
“<n>.c”的依赖文件被自动推导为“n.l”(Lex生成的文件),其生成命令是:“$(LEX
) $(LFALGS)”。(关于“Lex”的细节请查看相关资料)

11、Lex Ratfor程序时的隐含规则。
“<n>.r”的依赖文件被自动推导为“n.l”(Lex生成的文件),其生成命令是:“$(LEX
) $(LFALGS)”。


12、从C程序、Yacc文件或Lex文件创建Lint库的隐含规则。
“<n>.ln” (lint生成的文件)的依赖文件被自动推导为“n.c”,其生成命令是:“$(
LINT) $(LINTFALGS) $(CPPFLAGS) -i”。对于“<n>.y”和“<n>.l”也是同样的规则。

三、隐含规则使用的变量

在隐含规则中的命令中,基本上都是使用了一些预先设置的变量。你可以在你的makefile
中改变这些变量的值,或是在make的命令行中传入这些值,或是在你的环境变量中设置这
些值,无论怎么样,只要设置了这些特定的变量,那么其就会对隐含规则起作用。当然,
你也可以利用make的“-R”或“--no– builtin-variables”参数来取消你所定义的变量
对隐含规则的作用。

例如,第一条隐含规则——编译C程序的隐含规则的命令是“$(CC) –c $(CFLAGS) $(CPP
FLAGS)”。Make默认的编译命令是“cc”,如果你把变量“$(CC)”重定义成“gcc”,把
变量“$(CFLAGS)”重定义成 “-g”,那么,隐含规则中的命令全部会以“gcc –c -g $
(CPPFLAGS)”的样子来执行了。

我们可以把隐含规则中使用的变量分成两种:一种是命令相关的,如“CC”;一种是参数
相的关,如“CFLAGS”。下面是所有隐含规则中会用到的变量:

1、关于命令的变量。


AR
函数库打包程序。默认命令是“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或Ratfor)。默认命令是“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”。

2、关于命令参数的变量

下面的这些变量都是相关上面的命令的参数。如果没有指明其默认值,那么其默认值都是
空。

ARFLAGS
函数库打包程序AR命令的参数。默认值是“rv”。
ASFLAGS
汇编语言编译器参数。(当明显地调用“.s”或“.S”文件时)。
CFLAGS
C语言编译器参数。
CXXFLAGS
C++语言编译器参数。
COFLAGS
RCS命令参数。
CPPFLAGS
C预处理器参数。( C 和 Fortran 编译器也会用到)。
FFLAGS
Fortran语言编译器参数。
GFLAGS
SCCS “get”程序参数。
LDFLAGS
链接器参数。(如:“ld”)
LFLAGS
Lex文法分析器参数。
PFLAGS
Pascal语言编译器参数。
RFLAGS
Ratfor 程序的Fortran 编译器参数。
YFLAGS
Yacc文法分析器参数。

四、隐含规则链

有些时候,一个目标可能被一系列的隐含规则所作用。例如,一个[.o]的文件生成,可能
会是先被Yacc的[.y]文件先成[.c],然后再被C的编译器生成。我们把这一系列的隐含规则
叫做“隐含规则链”。

在上面的例子中,如果文件[.c]存在,那么就直接调用C的编译器的隐含规则,如果没有[
.c]文件,但有一个[.y]文件,那么Yacc的隐含规则会被调用,生成[.c]文件,然后,再调
用C编译的隐含规则最终由[.c]生成[.o]文件,达到目标。

我们把这种[.c]的文件(或是目标),叫做中间目标。不管怎么样,make会努力自动推导
生成目标的一切方法,不管中间目标有多少,其都会执着地把所有的隐含规则和你书写的
规则全部合起来分析,努力达到目标,所以,有些时候,可能会让你觉得奇怪,怎么我的
目标会这样生成?怎么我的makefile发疯了?

在默认情况下,对于中间目标,它和一般的目标有两个地方所不同:第一个不同是除非中
间的目标不存在,才会引发中间规则。第二个不同的是,只要目标成功产生,那么,产生
最终目标过程中,所产生的中间目标文件会被以“rm -f”删除。


通常,一个被makefile指定成目标或是依赖目标的文件不能被当作中介。然而,你可以明
显地说明一个文件或是目标是中介目标,你可以使用伪目标“.INTERMEDIATE”来强制声明
。(如:.INTERMEDIATE : mid )

你也可以阻止make自动删除中间目标,要做到这一点,你可以使用伪目标“.SECONDARY”
来强制声明(如:.SECONDARY : sec)。你还可以把你的目标,以模式的方式来指定(如
:%.o)成伪目标“.PRECIOUS”的依赖目标,以保存被隐含规则所生成的中间文件。

在“隐含规则链”中,禁止同一个目标出现两次或两次以上,这样一来,就可防止在make
自动推导时出现无限递归的情况。


Make 会优化一些特殊的隐含规则,而不生成中间文件。如,从文件“foo.c”生成目标程
序“foo”,按道理,make会编译生成中间文件“foo.o”,然后链接成“foo”,但在实际
情况下,这一动作可以被一条“cc”的命令完成(cc –o foo foo.c),于是优化过的规
则就不会生成中间文件。

五、定义模式规则

你可以使用模式规则来定义一个隐含规则。一个模式规则就好像一个一般的规则,只是在
规则中,目标的定义需要有"%"字符。"%"的意思是表示一个或多个任意字符。在依赖目标
中同样可以使用"%",只是依赖目标中的"%"的取值,取决于其目标。

有一点需要注意的是,"%"的展开发生在变量和函数的展开之后,变量和函数的展开发生在
make载入Makefile时,而模式规则中的"%"则发生在运行时。

1、模式规则介绍

模式规则中,至少在规则的目标定义中要包含"%",否则,就是一般的规则。目标中的"%"
定义表示对文件名的匹配,"%"表示长度任意的非空字符串。例如:"%.c"表示以".c"结尾
的文件名(文件名的长度至少为3),而"s.%.c"则表示以"s."开头,".c"结尾的文件名(
文件名的长度至少为 5)。

如果"%"定义在目标中,那么,目标中的"%"的值决定了依赖目标中的"%"的值,也就是说,
目标中的模式的"%"决定了依赖目标中"%"的样子。例如有一个模式规则如下:

%.o : %.c ; <command ......>

其含义是,指出了怎么从所有的[.c]文件生成相应的[.o]文件的规则。如果要生成的目标
是"a.o b.o",那么"%c"就是"a.c b.c"。

一旦依赖目标中的"%"模式被确定,那么,make会被要求去匹配当前目录下所有的文件名,
一旦找到,make就会规则下的命令,所以,在模式规则中,目标可能会是多个的,如果有
模式匹配出多个目标,make就会产生所有的模式目标,此时,make关心的是依赖的文件名
和生成目标的命令这两件事。

2、模式规则示例

下面这个例子表示了,把所有的[.c]文件都编译成[.o]文件.

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

其中,"$@"表示所有的目标的挨个值,"$<"表示了所有依赖目标的挨个值。这些奇怪的变
量我们叫"自动化变量",后面会详细讲述。

下面的这个例子中有两个目标是模式的:

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

这条规则告诉make把所有的[.y]文件都以"bison -d <n>.y"执行,然后生成"<n>.tab.c"和
"<n>.tab.h"文件。(其中,"<n>" 表示一个任意字符串)。如果我们的执行程序"foo"依
赖于文件"parse.tab.o"和"scan.o",并且文件"scan.o"依赖于文件"parse.tab.h",如果
"parse.y"文件被更新了,那么根据上述的规则,"bison -d parse.y"就会被执行一次,于
是,"parse.tab.o"和"scan.o"的依赖文件就齐了。(假设,"parse.tab.o" 由"parse.ta
b.c"生成,和"scan.o"由"scan.c"生成,而"foo"由"parse.tab.o"和"scan.o"链接生成,
而且foo和其[.o]文件的依赖关系也写好,那么,所有的目标都会得到满足)

3、自动化变量

在上述的模式规则中,目标和依赖文件都是一系例的文件,那么我们如何书写一个命令来
完成从不同的依赖文件生成相应的目标?因为在每一次的对模式规则的解析时,都会是不
同的目标和依赖文件。

自动化变量就是完成这个功能的。在前面,我们已经对自动化变量有所提涉,相信你看到
这里已对它有一个感性认识了。所谓自动化变量,就是这种变量会把模式中所定义的一系
列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出
现在规则的命令中。

下面是所有的自动化变量及其说明:

$@
表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于目标
中模式定义的集合。

$%
仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a(bar
.o)",那么,"$%"就是"bar.o","$@"就是"foo.a"。如果目标不是函数库文件(Unix下是
[.a],Windows下是[.lib]),那么,其值为空。

$<
依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符
合模式的一系列的文件集。注意,其是一个一个取出来的。

$?
所有比目标新的依赖目标的集合。以空格分隔。

$^
所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会
去除重复的依赖目标,只保留一份。

$+
这个变量很像"$^",也是所有依赖目标的集合。只是它不去除重复的依赖目标。

$*
这个变量表示目标模式中"%"及其之前的部分。如果目标是"dir/a.foo.b",并且目标的模
式是"a.%.b",那么,"$*"的值就是"dir /a.foo"。这个变量对于构造有关联的文件名是比
较有较。如果目标中没有模式的定义,那么"$*"也就不能被推导出,但是,如果目标文件
的后缀是 make所识别的,那么"$*"就是除了后缀的那一部分。例如:如果目标是"foo.c"
,因为".c"是make所能识别的后缀名,所以,"$*"的值就是"foo"。这个特性是GNU make的
,很有可能不兼容于其它版本的make,所以,你应该尽量避免使用"$*",除非是在隐含规
则或是静态模式中。如果目标中的后缀是make所不能识别的,那么"$*"就是空值。

当你希望只对更新过的依赖文件进行操作时,"$?"在显式规则中很有用,例如,假设有一
个函数库文件叫"lib",其由其它几个object文件更新。那么把object文件打包的比较有效
率的Makefile规则是:

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

在上述所列出来的自动量变量中。四个变量($@、$<、$%、$*)在扩展时只会有一个文件
,而另三个的值是一个文件列表。这七个自动化变量还可以取得文件的目录名或是在当前
目录下的符合模式的文件名,只需要搭配上"D"或"F"字样。这是GNU make中老版本的特性
,在新版本中,我们使用函数"dir"或"notdir"就可以做到了。"D"的含义就是Directory,
就是目录,"F"的含义就是File,就是文件。

下面是对于上面的七个变量分别加上"D"或是"F"的含义:

$(@D)
表示"$@"的目录部分(不以斜杠作为结尾),如果"$@"值是"dir/foo.o",那么"$(@D)"就
是"dir",而如果"$@"中没有包含斜杠的话,其值就是"."(当前目录)。

$(@F)
表示"$@"的文件部分,如果"$@"值是"dir/foo.o",那么"$(@F)"就是"foo.o","$(@F)"相
当于函数"$(notdir $@)"。

"$(*D)"
"$(*F)"
和上面所述的同理,也是取文件的目录部分和文件部分。对于上面的那个例子,"$(*D)"返
回"dir",而"$(*F)"返回"foo"

"$(%D)"
"$(%F)"
分别表示了函数包文件成员的目录部分和文件部分。这对于形同"archive(member)"形式的
目标中的"member"中包含了不同的目录很有用。

"$(<D)"
"$(<F)"
分别表示依赖文件的目录部分和文件部分。

"$(^D)"
"$(^F)"
分别表示所有依赖文件的目录部分和文件部分。(无相同的)

"$(+D)"
"$(+F)"
分别表示所有依赖文件的目录部分和文件部分。(可以有相同的)

"$(?D)"
"$(?F)"

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

最后想提醒一下的是,对于"$<",为了避免产生不必要的麻烦,我们最好给$后面的那个特
定字符都加上圆括号,比如,"$(< )"就要比"$<"要好一些。


还得要注意的是,这些变量只使用在规则的命令中,而且一般都是"显式规则"和"静态模式
规则"(参见前面"书写规则"一章)。其在隐含规则中并没有意义。

4、模式的匹配

一般来说,一个目标的模式有一个有前缀或是后缀的"%",或是没有前后缀,直接就是一个
"%"。因为"%"代表一个或多个字符,所以在定义好了的模式中,我们把"%"所匹配的内容叫
做"茎",例如"%.c"所匹配的文件"test.c"中"test"就是"茎"。因为在目标和依赖目标中同
时有"%"时,依赖目标的"茎"会传给目标,当做目标中的"茎"。

当一个模式匹配包含有斜杠(实际也不经常包含)的文件时,那么在进行模式匹配时,目
录部分会首先被移开,然后进行匹配,成功后,再把目录加回去。在进行"茎"的传递时,
我们需要知道这个步骤。例如有一个模式"e%t",文件"src/eat" 匹配于该模式,于是"sr
c/a"就是其"茎",如果这个模式定义在依赖目标中,而被依赖于这个模式的目标中又有个
模式"c%r",那么,目标就是"src/car"。("茎"被传递)

5、重载内建隐含规则

你可以重载内建的隐含规则(或是定义一个全新的),例如你可以重新构造和内建隐含规
则不同的命令,如:

%.o : %.c
$(CC) -c $(CPPFLAGS) $(CFLAGS) -D$(date)

你可以取消内建的隐含规则,只要不在后面写命令就行。如:

%.o : %.s

同样,你也可以重新定义一个全新的隐含规则,其在隐含规则中的位置取决于你在哪里写
下这个规则。朝前的位置就靠前。

六、老式风格的"后缀规则"

后缀规则是一个比较老式的定义隐含规则的方法。后缀规则会被模式规则逐步地取代。因
为模式规则更强更清晰。为了和老版本的Makefile兼容,GNU make同样兼容于这些东西。
后缀规则有两种方式:"双后缀"和"单后缀"。

双后缀规则定义了一对后缀:目标文件的后缀和依赖目标(源文件)的后缀。如".c.o"相
当于"%o : %c"。单后缀规则只定义一个后缀,也就是源文件的后缀。如".c"相当于"% : 
%.c"。

后缀规则中所定义的后缀应该是make所认识的,如果一个后缀是make所认识的,那么这个
规则就是单后缀规则,而如果两个连在一起的后缀都被make所认识,那就是双后缀规则。
例如:".c"和".o"都是make所知道。因而,如果你定义了一个规则是".c.o"那么其就是双
后缀规则,意义就是".c" 是源文件的后缀,".o"是目标文件的后缀。如下示例:

.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: .hack .win

把后缀.hack和.win加入后缀列表中的末尾。

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

先清楚默认后缀,后定义自己的后缀列表。

make的参数"-r"或"-no-builtin-rules"也会使用得默认的后缀列表为空。而变量"SUFFIX
E"被用来定义默认的后缀列表,你可以用".SUFFIXES"来改变后缀列表,但请不要改变变量
"SUFFIXE"的值。

七、隐含规则搜索算法

比如我们有一个目标叫 T。下面是搜索目标T的规则的算法。请注意,在下面,我们没有提
到后缀规则,原因是,所有的后缀规则在Makefile被载入内存时,会被转换成模式规则。
如果目标是"archive(member)"的函数库文件模式,那么这个算法会被运行两次,第一次是
找目标T,如果没有找到的话,那么进入第二次,第二次会把"member"当作T来搜索。

1、把T的目录部分分离出来。叫D,而剩余部分叫N。(如:如果T是"src/foo.o",那么,
D就是"src/",N就是"foo.o")

2、创建所有匹配于T或是N的模式规则列表。

3、如果在模式规则列表中有匹配所有文件的模式,如"%",那么从列表中移除其它的模式


4、移除列表中没有命令的规则。

5、对于第一个在列表中的模式规则:
1)推导其"茎"S,S应该是T或是N匹配于模式中"%"非空的部分。
2)计算依赖文件。把依赖文件中的"%"都替换成"茎"S。如果目标模式中没有包含斜框字符
,而把D加在第一个依赖文件的开头。
3)测试是否所有的依赖文件都存在或是理当存在。(如果有一个文件被定义成另外一个规
则的目标文件,或者是一个显式规则的依赖文件,那么这个文件就叫"理当存在")
4)如果所有的依赖文件存在或是理当存在,或是就没有依赖文件。那么这条规则将被采用
,退出该算法。

6、如果经过第5步,没有模式规则被找到,那么就做更进一步的搜索。对于存在于列表中
的第一个模式规则:
1)如果规则是终止规则,那就忽略它,继续下一条模式规则。
2)计算依赖文件。(同第5步)
3)测试所有的依赖文件是否存在或是理当存在。
4)对于不存在的依赖文件,递归调用这个算法查找他是否可以被隐含规则找到。
5)如果所有的依赖文件存在或是理当存在,或是就根本没有依赖文件。那么这条规则被采
用,退出该算法。

7、如果没有隐含规则可以使用,查看".DEFAULT"规则,如果有,采用,把".DEFAULT"的命
令给T使用。

一旦规则被找到,就会执行其相当的命令,而此时,我们的自动化变量的值才会生成。


使用make更新函数库文件
———————————

函数库文件也就是对Object文件(程序编译的中间文件)的打包文件。在Unix下,一般是
由命令"ar"来完成打包工作。

一、函数库文件的成员

一个函数库文件由多个文件组成。你可以以如下格式指定函数库文件及其组成:

archive(member)

这个不是一个命令,而一个目标和依赖的定义。一般来说,这种用法基本上就是为了"ar"
命令来服务的。如:

foolib(hack.o) : hack.o
ar cr foolib hack.o

如果要指定多个member,那就以空格分开,如:

foolib(hack.o kludge.o)

其等价于:

foolib(hack.o) foolib(kludge.o)

你还可以使用Shell的文件通配符来定义,如:

foolib(*.o)

二、函数库成员的隐含规则

当 make搜索一个目标的隐含规则时,一个特殊的特性是,如果这个目标是"a(m)"形式的,
其会把目标变成"(m)"。于是,如果我们的成员是"%.o" 的模式定义,并且如果我们使用"
make foo.a(bar.o)"的形式调用Makefile时,隐含规则会去找"bar.o"的规则,如果没有定
义bar.o的规则,那么内建隐含规则生效,make会去找bar.c文件来生成bar.o,如果找得到
的话,make执行的命令大致如下:

cc -c bar.c -o bar.o
ar r foo.a bar.o
rm -f bar.o

还有一个变量要注意的是"$%",这是专属函数库文件的自动化变量,有关其说明请参见"自
动化变量"一节。

三、函数库文件的后缀规则

你可以使用"后缀规则"和"隐含规则"来生成函数库打包文件,如:

.c.a:
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $*.o
$(AR) r $@ $*.o
$(RM) $*.o

其等效于:

(%.o) : %.c
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $*.o
$(AR) r $@ $*.o
$(RM) $*.o

四、注意事项

在进行函数库打包文件生成时,请小心使用make的并行机制("-j"参数)。如果多个ar命
令在同一时间运行在同一个函数库打包文件上,就很有可以损坏这个函数库文件。所以,
在make未来的版本中,应该提供一种机制来避免并行操作发生在函数打包文件上。

但就目前而言,你还是应该不要尽量不要使用"-j"参数。


后序
——
终于到写结束语的时候了,以上基本上就是GNU make的Makefile的所有细节了。其它的产
商的make基本上也就是这样的,无论什么样的make,都是以文件的依赖性为基础的,其基
本是都是遵循一个标准的。这篇文档中80%的技术细节都适用于任何的make,我猜测"函数
"那一章的内容可能不是其它make所支持的,而隐含规则方面,我想不同的make会有不同的
实现,我没有精力来查看GNU的make和VC的nmake、BCB的make,或是别的UNIX下的make有些
什么样的差别,一是时间精力不够,二是因为我基本上都是在Unix下使用make,以前在SC
O Unix和IBM的AIX,现在在Linux、Solaris、HP-UX、AIX和Alpha下使用,Linux和Solari
s下更多一点。不过,我可以肯定的是,在Unix下的make,无论是哪种平台,几乎都使用了
Richard Stallman开发的make和cc/gcc的编译器,而且,基本上都是GNU的make(公司里所
有的UNIX机器上都被装上了GNU的东西,所以,使用GNU的程序也就多了一些)。GNU的东西
还是很不错的,特别是使用得深了以后,越来越觉得GNU的软件的强大,也越来越觉得GNU
的在操作系统中(主要是Unix,甚至Windows)"杀伤力"。

对于上述所有的make的细节,我们不但可以利用make这个工具来编译我们的程序,还可以
利用make来完成其它的工作,因为规则中的命令可以是任何Shell之下的命令,所以,在U
nix下,你不一定只是使用程序语言的编译器,你还可以在Makefile中书写其它的命令,如
:tar、awk、mail、sed、cvs、compress、ls、rm、yacc、rpm、 ftp……等等,等等,来
完成诸如"程序打包"、"程序备份"、"制作程序安装包"、"提交代码"、"使用程序模板"、
"合并文件"等等五花八门的功能,文件操作,文件管理,编程开发设计,或是其它一些异
想天开的东西。比如,以前在书写银行交易程序时,由于银行的交易程序基本一样,就见
到有人书写了一些交易的通用程序模板,在该模板中把一些网络通讯、数据库操作的、业
务操作共性的东西写在一个文件中,在这些文件中用些诸如"@@@N、###N"奇怪字串标注一
些位置,然后书写交易时,只需按照一种特定的规则书写特定的处理,最后在make时,使
用awk和sed,把模板中的"@@@N、###N"等字串替代成特定的程序,形成C文件,然后再编译
。这个动作很像数据库的"扩展C"语言(即在C语言中用"EXEC SQL"的样子执行SQL语句,
在用 cc/gcc编译之前,需要使用"扩展C"的翻译程序,如cpre,把其翻译成标准C)。如果
你在使用make时有一些更为绝妙的方法,请记得告诉我啊。

回头看看整篇文档,不觉记起几年前刚刚开始在Unix下做开发的时候,有人问我会不会写
Makefile时,我两眼发直,根本不知道在说什么。一开始看到别人在vi中写完程序后输入
"!make"时,还以为是vi的功能,后来才知道有一个Makefile在作怪,于是上网查啊查,那
时又不愿意看英文,发现就根本没有中文的文档介绍Makefile,只得看别人写的Makefile
,自己瞎碰瞎搞才积累了一点知识,但在很多地方完全是知其然不知所以然。后来开始从
事UNIX下产品软件的开发,看到一个400人年,近200万行代码的大工程,发现要编译这样
一个庞然大物,如果没有Makefile,那会是多么恐怖的一样事啊。于是横下心来,狠命地
读了一堆英文文档,才觉得对其掌握了。但发现目前网上对Makefile介绍的文章还是少得
那么的可怜,所以想写这样一篇文章,共享给大家,希望能对各位有所帮助。

现在我终于写完了,看了看文件的创建时间,这篇技术文档也写了两个多月了。发现,自
己知道是一回事,要写下来,跟别人讲述又是另外一回事,而且,现在越来越没有时间专
研技术细节,所以在写作时,发现在阐述一些细节问题时很难做到严谨和精练,而且对先
讲什么后讲什么不是很清楚,所以,还是参考了一些国外站点上的资料和题纲,以及一些
技术书籍的语言风格,才得以完成。整篇文档的提纲是基于GNU的 Makefile技术手册的提
纲来书写的,并结合了自己的工作经验,以及自己的学习历程。因为从来没有写过这么长
,这么细的文档,所以一定会有很多地方存在表达问题,语言歧义或是错误。因些,我迫
切地得等待各位给我指证和建议,以及任何的反馈。

最后,还是利用这个后序,介绍一下自己。我目前从事于所有Unix平台下的软件研发,主
要是做分布式计算/网格计算方面的系统产品软件,并且我对于下一代的计算机革命——网
格计算非常地感兴趣,对于分布式计算、P2P、Web Service、J2EE技术方向也很感兴趣,
同时,对于项目实施、团队管理、项目管理也小有心得,希望同样和我战斗在“技术和管
理并重”的阵线上的年轻一代,能够和我多多地交流。我的MSN是:haoel@hotmail.com(
常用),QQ是:753640(不常用)。(注:请勿给我MSN的邮箱发信,由于hotmail的垃圾
邮件导致我拒收这个邮箱的所有来信)

我欢迎任何形式的交流,无论是讨论技术还是管理,或是其它海阔天空的东西。除了政治
和娱乐新闻我不关心,其它只要积极向上的东西我都欢迎!

最最后,我还想介绍一下make程序的设计开发者。

首当其冲的是: Richard Stallman

开源软件的领袖和先驱,从来没有领过一天工资,从来没有使用过Windows操作系统。对于
他的事迹和他的软件以及他的思想,我无需说过多的话,相信大家对这个人并不比我陌生
,这是他的主页:http://www.stallman.org/ 。

第二位是:Roland McGrath

个人主页是:http://www.frob.com/~roland/ ,下面是他的一些事迹:

1) 合作编写了并维护GNU make。

2) 和Thomas Bushnell一同编写了GNU Hurd。

3) 编写并维护着GNU C library。

4) 合作编写并维护着部分的GNU Emacs。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值