Chinaunix首页 | 论坛 | 博客
  • 博客访问: 104613868
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类: C/C++

2008-04-17 17:15:35

3 编写makefile文件
make编译系统依据的信息来源于称为makefile文件的数据库。

3.1 makefile文件的内容
makefile文件包含5方面内容:具体规则、隐含规则、定义变量、指令和注释。规则、变量和指令将在后续章节介绍。

l         具体规则用于阐述什么时间或怎样重新生成称为规则目标的一个或多个文件的。它列举了目标所依靠的文件,这些文件称为该目标的依赖。具体规则可能同时提供了创建或更新该目标的命令。详细内容参阅编写规则一章。

l         隐含规则用于阐述什么时间或怎样重新生成同一文件名的一系列文件的。它描述的目标是根据和它名字相同的文件进行创建或更新的,同时提供了创建或更新该目标的命令。详细内容参阅使用隐含规则一节。

l         定义变量是为一个变量赋一个固定的字符串值,从而在以后的文件中能够使用该变量代替这个字符串。注意在makefile文件中定义变量占一独立行。在上一章的makefile文件例子中我们定义了代表所有OBJ文件的变量objects(详细内容参阅使用变量简化makefile文件一节)。

l         指令是make根据makefile文件执行一定任务的命令。这些包括如下几方面:

n         读其它makefile文件(详细内容参见包含其它的makefile文件)。

n         判定(根据变量的值)是否使用或忽略makefile文件的部分内容(详细内容参阅makefile文件的条件语句一节)。

n         定义多行变量,即定义变量值可以包含多行字符的变量(详细内容参见定义多行变量一节)。

l         以‘#’开始的行是注释行。注释行在处理时将被make忽略,如果一个注释行在行尾是‘\’则表示下一行继续为注释行,这样注释可以持续多行。除在define指令内部外,注释可以出现在makefile文件的任何地方,甚至在命令内部(这里shell决定什么是注释内容)。

3.2 makfile文件的命名
缺省情况下,当make寻找makefile文件时,它试图搜寻具有如下的名字的文件,按顺序:‘GNUmakefile’、‘makefile’和‘Makefile’。

通常情况下您应该把您的makefile文件命名为‘makefile’或‘Makefile’。(我们推荐使用‘Makefile’,因为它基本出现在目录列表的前面,后面挨着其它重要的文件如‘README’等。)。虽然首先搜寻‘GNUmakefile’,但我们并不推荐使用。除非您的makefile文件是特为GNU make编写的,在其它make版本上不能执行,您才应该使用‘GNUmakefile’作为您的makefile的文件名。

如果make不能发现具有上面所述名字的文件,它将不使用任何makefile文件。这样您必须使用命令参数给定目标,make试图利用内建的隐含规则确定如何重建目标。详细内容参见使用隐含规则一节。

如果您使用非标准名字makefile文件,您可以使用‘-f’或‘--file’参数指定您的makefile文件。参数‘-f name’或‘--file=name’能够告诉make读名字为‘name’的文件作为makefile文件。如果您使用 ‘-f’或‘--file’参数多于一个,意味着您指定了多个makefile文件,所有的makefile文件按具体的顺序发生作用。一旦您使用了‘-f’或‘--file’参数,将不再自动检查是否存在名为‘GNUmakefile’、‘makefile’或‘Makefile’的makefile文件。

3.3 包含其它的makefile文件
include指令告诉make暂停读取当前的makefile文件,先读完include指令指定的makefile文件后再继续。指令在makefile文件占单独一行,其格式如下:

include filenames...

filenames可以包含shell文件名的格式。

在include指令行,行开始处的多余的空格是允许的,但make处理时忽略这些空格,注意该行不能以Tab字符开始(因为,以Tab字符开始的行,make认为是命令行)。include和文件名之间以空格隔开,两个文件名之间也以空格隔开,多余的空格make处理时忽略,在该行的尾部可以加上以‘#’为起始的注释。文件名可以包含变量及函数调用,它们在处理时由make进行扩展(具体内容参阅使用变量一节)。

例如,有三个‘.mk’文件:‘a.mk’、‘b.mk’和‘c.mk’,变量$(bar)扩展为bish bash,则下面的表达是:

include foo *.mk $(bar)

和‘include foo a.mk b.mk c.mk bish bash’等价。

    当make遇见include指令时, make就暂停读取当前的makefile文件,依次读取列举的makefile文件,读完之后,make再继续读取当前makefile文件中include指令以后的内容。

使用include指令的一种情况是几个程序分别有单独的makefile文件,但它们需要一系列共同的变量定义(详细内容参阅设置变量),或者一系列共同的格式规则(详细内容参阅定义与重新定义格式规则)。

另一种使用include指令情况是需要自动从源文件为目标产生依赖的情况,此时,依赖在主makefile文件包含的文件中。这种方式比其它版本的make把依赖附加在主makefile文件后部的传统方式更显得简洁。具体内容参阅自动产生依赖。

如果makefile文件名不以‘/’开头,并且在当前目录下也不能找到,则需搜寻另外的目录。首先,搜寻以‘-|’或‘--include-dir’参数指定的目录,然后依次搜寻下面的目录(如果它们存在的话):‘prefix/include' (通常为 ‘/usr/local/include') ‘/usr/gnu/include', ‘/usr/local/include', ‘/usr/include'

如果指定包含的makefile文件在上述所有的目录都不能找到,make将产生一个警告信息,注意这不是致命的错误。处理完include指令包含的makefile文件之后,继续处理当前的makefile文件。一旦完成makefile文件的读取操作,make将试图创建或更新过时的或不存在的makefile文件。详细内容参阅makefile文件重新生成的过程。只有在所有make寻求丢失的makefile文件的努力失败后,make才能断定丢失的makefile文件是一个致命的错误。

如果您希望对不存在且不能重新创建的makefile文件进行忽略,并且不产生错误信息,则使用-include指令代替include指令,格式如下:

-include filenames...
这种指令的作用就是对于任何不存在的makefile文件都不会产生错误(即使警告信息也不会产生)。如果希望保持和其它版本的make兼容,使用sinclude指令代替-include指令。

3.4 变量MAKEFILES
如果定义了环境变量MAKEFILES,make认为该变量的值是一列附加的makefile文件名,文件名之间由空格隔开,并且这些makefile文件应首先读取。Make完成这个工作和上节完成include指令的方式基本相同,即在特定的目录中搜寻这些文件。值得注意的是,缺省最终目标不会出现在这些makefile文件中,而且如果一些makefile文件没有找到也不会出现任何错误信息。

环境变量MAKEFILES主要在make递归调用过程中起通讯作用(详细内容参阅递归调用make)。在make顶级调用之前设置环境变量并不是十分好的主意,因为这样容易将makefile文件与外界的关系弄的更加混乱。然而如果运行make而缺少makefile文件时,环境变量MAKEFILES中makefile文件可以使内置的隐含规则更好的发挥作用,如搜寻定义的路径等(详细内容参阅在目录中搜寻依赖)。

一些用户喜欢在登录时自动设置临时的环境变量MAKEFILES,而makefile文件在该变量指定的文件无效时才使用。这是非常糟糕的主意,应为许多makefile文件在这种情况下运行失效。最好的方法是直接在makefile文件中写出具体的include指令(详细内容参看上一节)。

3.5 makefile文件重新生成的过程
有时makefile文件可以由其它文件重新生成,如从RCS或SCCS文件生成等。如果一个makefile文件可以从其它文件重新生成,一定注意让make更新makefile文件之后再读取makefile文件。

完成读取所有的makefile文件之后,make检查每一个目标,并试图更新它。如果对于一个makefile文件有说明它怎样更新的规则(无论在当前的makefile文件中或其它makefile文件中),或者存在一条隐含规则说明它怎样更新(具体内容参见使用隐含规则),则在必要的时候该makefile文件将会自动更新。在所有的makefile文件检查之后,如果发现任何一个makefile文件发生变化,make就会清空所有记录,并重新读入所有makefile文件。(然后再次试图更新这些makefile文件,正常情况下,因为这些makefile文件已被更新,make将不会再更改它们。)

如果您知道您的一个或多个makefile文件不能重新创建,也许由于执行效率缘故,您不希望make按照隐含规则搜寻或重建它们,您应使用正常的方法阻止按照隐含规则检查它们。例如,您可以写一个具体的规则,把这些makefile文件当作目标,但不提供任何命令(详细内容参阅使用空命令)。

如果在makefile文件中指定依据双冒号规则使用命令重建一个文件,但没有提供依赖,则一旦make运行就会重建该文件(详细内容参见双冒号规则)。同样,如果在makefile文件中指定依据双冒号规则使用命令重建的一个makefile文件,并且不提供依赖,则一旦make运行就会重建该makefile文件,然后重新读入所有makefile文件,然后再重建该makefile文件,再重新读入所有makefile文件,如此往复陷入无限循环之中,致使make不能再完成别的任务。如果要避免上述情况的发生,一定注意不要依据双冒号规则使用命令并且不提供依赖重建任何makefile文件。

如果您没有使用‘-f’或‘--file’指定makefile文件,make将会使用缺省的makefile文件名(详细内容参见3.2节内容)。不象使用‘-f’或‘--file’选项指定具体的makefile文件,这时make不能确定makefile文件是否存在。如果缺省的makefile文件不存在,但可以由运行的make依据规则创建,您需要运行这些规则,创建要使用的makefile文件。

如果缺省的makefile文件不存在,make将会按照搜寻的次序将它们试着创建,一直到将makefile文件成功创建或make将所有的文件名都试过来。注意make不能找到或创建makefile文件不是错误,makefile文件并不是运行make必须的。

因为即使您使用‘-t’特别指定,‘-t’或‘--touch’选项对更新makefile文件不产生任何影响, makefile文件仍然会更新,所以当您使用‘-t’或‘--touch’选项时,您不要使用过时的makefile文件来决定‘touch’哪个目标(具体含义参阅代替执行命令)。同样,因为‘-q' (或 ‘--question') 和 ‘-n' (或 ‘--just-print')也能不阻止更新makefile文件,所以过时的makefile文件对其它的目标将产生错误的输出结果。如,‘make -f mfile -n foo’命令将这样执行:更新‘mfile’,然后读入,再输出更新‘foo’的命令和依赖,但并不执行更新‘foo’,注意,所有回显的更新‘foo’的命令是在更新后的‘mfile’中指定的。

在实际使用过程中,您一定会遇见确实希望阻止更新makefile文件的情况。如果这样,您可以在makefile文件命令行中将需要更新的makefile文件指定为目标,如此则可阻止更新makefile文件。一旦makefile文件名被明确指定为一个目标,选项‘-t’等将会对它发生作用。如这样设定,‘make -f mfile -n foo’命令将这样执行:读入‘mfile’,输出更新‘foo’的命令和依赖,但并不执行更新‘foo’。回显的更新‘foo’的命令包含在现存的‘mfile’中。

3.6 重载其它makefile文件
有时一个makefile文件和另一个makefile文件相近也是很有用的。您可以使用‘include’指令把更多的makefile文件包含进来,如此可加入更多的目标和定义的变量。然而如果两个makefile文件对相同的目标给出了不同的命令,make就会产生错误。

在主makefile文件(要包含其它makefile文件的那个)中,您可以使用通配符格式规则说明只有在依靠当前makefile文件中的信息不能重新创建目标时,make才搜寻其它的makefile文件,详细内容参见定义与重新定义格式规则。

例如:如果您有一个说明怎样创建目标‘foo’(和其它目标)的makefile文件称为‘Makefile’,您可以编写另外一个称为‘GNUmakefile’的makefile文件包含以下语句:

foo:
        frobnicate > foo
 
%: force
        @$(MAKE) -f Makefile $@
force: 
如果键入‘make foo’,make就会找到‘GNUmakefile’,读入,然后运行‘frobnicate > foo’。如果键入‘make bar’,make发现无法根据‘GNUmakefile’创建‘bar’,它将使用格式规则提供的命令:‘make –f Makefile bar’。如果在‘Makefile’中提供了‘bar’更新的规则,make就会使用该规则。对其它‘GNUmakefile’不提供怎样更新的目标make也会同样处理。这种工作的方式是使用了格式规则中的格式匹配符‘%’,它可以和任何目标匹配。该规则指定了一个依赖‘force’,用来保证命令一定要执行,无论目标文件是否存在。我们给出的目标‘force’时使用了空命令,这样可防止make按照隐含规则搜寻和创建它,否则,make将把同样的匹配规则应用到目标‘force’本身,从而陷入创建依赖的循环中。

3.7 make读取makefile文件的过程
GNU make把它的工作明显的分为两个阶段。在第一阶段,make读取makefile文件,包括makefile文件本身、内置变量及其值、隐含规则和具体规则、构造所有目标的依靠图表和它们的依赖等。在第二阶段,make使用这些内置的组织决定需要重新构造的目标以及使用必要的规则进行工作。

了解make两阶段的工作方式十分重要,因为它直接影响变量、函数扩展方式;而这也是编写makefile文件时导致一些错误的主要来源之一。下面我们将对makefile文件中不同结构的扩展方式进行总结。我们称在make工作第一阶段发生的扩展是立即扩展:在这种情况下,make对makefile文件进行语法分析时把变量和函数直接扩展为结构单元的一部分。我们把不能立即执行的扩展称为延时扩展。延时扩展结构直到它已出现在上下文结构中或make已进入到了第二工作阶段时才执行展开。

您可能对这一部分内容不熟悉。您可以先看完后面几章对这些知识熟悉后再参考本节内容。

变量赋值

变量的定义语法形式如下:

immediate = deferred
immediate ?= deferred
immediate := immediate
immediate += deferred or immediate
 
define immediate
  deferred
endef
对于附加操作符‘+=’,右边变量如果在前面使用(:=)定义为简单扩展变量则是立即变量,其它均为延时变量。

条件语句

整体上讲,条件语句都按语法立即分析,常用的有:ifdef、ifeq、ifndef和inneq。

定义规则

规则不论其形式如何,都按相同的方式扩展。

immediate : immediate  deferred
         deferred
目标和依赖部分都立即扩展,用于构造目标的命令通常都是延时扩展。这个通用的规律对具体规则、格式规则、后缀规则、静态格式规则和简单依赖定义都适用。

4编写规则
makefile文件中的规则是用来说明何时以及怎样重建特定文件的,这些特定的文件称为该规则的目标(通常情况下,每个规则只有一个目标)。在规则中列举的其它文件称为目标的依赖,同时规则还给出了目标创建、更新的命令。一般情况下规则的次序无关紧要,但决定缺省最终目标时却是例外。缺省最终目标是您没有另外指定最终目标时,make认定的最终目标。缺省最终目标是makefile文件中的第一条规则的目标。如果第一条规则有多个目标,只有第一个目标被认为是缺省最终目标。有两种例外的情况:以句点(‘.’)开始的目标不是缺省最终目标(如果该目标包含一个或多个斜杠‘/’,则该目标也可能是缺省最终目标);另一种情况是格式规则定义的目标不是缺省最终目标(参阅定义与重新定义格式规则)。

所以,我们编写makefile文件时,通常将第一个规则的目标定为编译全部程序或是由makefile文件表述的所有程序(经常设定一个称为‘all’的目标)。参阅指定最终目标的参数。

4.1规则的语法
通常一条规则形式如下:

targets : prerequisites
        command
        ...
或: 

targets : prerequisites  command
        command
        ...
目标(target)是文件的名称,中间由空格隔开。通配符可以在文件名中使用(参阅在文件名中使用通配符),‘a(m)’形式的文件名表示成员m在文件a中(参阅档案成员目标)。一般情况下,一条规则只有一个目标,但偶尔由于其它原因一条规则有多个目标(参阅具有多个目标的规则)。

命令行以Tab字符开始,第一个命令可以和依赖在一行,命令和依赖之间用分号隔开,也可以在依赖下一行,以Tab字符为行的开始。这两种方法的效果一样,参阅在规则中使用命令。

因为美元符号已经用为变量引用的开始符,如果您真希望在规则中使用美元符号,您必须连写两次,‘$$’(参阅使用变量)。您可以把一长行在中间插入‘\’使其分为两行,也就是说,一行的尾部是’\’的话,表示下一行是本行的继续行。但这并不是必须的,make没有对makefile文件中行的长度进行限制。一条规则可以告诉make两件事情:何时目标已经过时,以及怎样在必要时更新它们。

判断目标过时的准则和依赖关系密切,依赖也由文件名构成,文件名之间由空格隔开,通配符和档案成员也允许在依赖中出现。一个目标如果不存在或它比其中一个依赖的修改时间早,则该目标已经过时。该思想来源于目标是根据依赖的信息计算得来的,因此一旦任何一个依赖发生变化,目标文件也就不再有效。目标的更新方式由命令决定。命令由shell解释执行,但也有一些另外的特点。参阅在规则中使用命令。

4.2 在文件名中使用通配符
一个简单的文件名可以通过使用通配符代表许多文件。Make中的通配符和Bourne shell中的通配符一样是‘*’、‘?’和‘[…]’。例如:‘*.C’指在当前目录中所有以‘.C’结尾的文件。

字符‘~’在文件名的前面也有特殊的含义。如果字符‘~’单独或后面跟一个斜杠‘/’,则代表您的home目录。如‘~/bin’扩展为‘/home/bin’。如果字符‘~’后面跟一个字,它扩展为home目录下以该字为名字的目录,如‘~John/bin’表示‘home/John/bin’。在一些操作系统(如ms-dos,ms-windows)中不存在home目录,可以通过设置环境变量home来模拟。

在目标、依赖和命令中的通配符自动扩展。在其它上下文中,通配符只有在您明确表明调用通配符函数时才扩展。

通配符另一个特点是如果通配符前面是反斜杠‘\’,则该通配符失去通配能力。如‘foo\*bar’表示一个特定的文件其名字由‘foo’、‘*’和‘bar’构成。

4.2.1通配符例子
    通配符可以用在规则的命令中,此时通配符由shell扩展。例如,下面的规则删除所有OBJ文件:

clean:

    rm –f  *.o

    通配符在规则的依赖中也很有用。在下面的makefile规则中,‘make print’将打印所有从上次您打印以后又有改动的‘.c’文件:

print: *.c
        lpr -p $?
        touch print
本规则使用‘ptint’作为一个空目标文件(参看使用空目标文件记录事件);自动变量‘$?’用来打印那些已经修改的文件,参看自动变量。

当您定义一个变量时通配符不会扩展,如果您这样写:

objects = *.o

变量objects的值实际就是字符串‘*.o’。然而,如果您在一个目标、依赖和命令中使用变量objects的值,通配符将在那时扩展。使用下面的语句可使通配符扩展:

objects=$(wildcard *.o)

详细内容参阅函数wildcard。

4.2.2使用通配符的常见错误
下面有一个幼稚使用通配符扩展的例子,但实际上该例子不能完成您所希望完成的任务。假设可执行文件‘foo’由在当前目录的所有OBJ文件创建,其规则如下:

objects = *.o
 
foo : $(objects)
        cc -o foo $(CFLAGS) $(objects)
由于变量objects的值为字符串‘*.o’,通配符在目标‘foo’的规则下扩展,所以每一个OBJ文件都会变为目标‘foo’的依赖,并在必要时重新编译自己。

但如果您已删除了所有的OBJ文件,情况又会怎样呢?因没有和通配符匹配的文件,所以目标‘foo’就依靠了一个有着奇怪名字的文件‘*.o’。因为目录中不存在该文件,make将发出不能创建‘*.o’的错误信息。这可不是所要执行的任务。

实际上,使用通配符获得正确的结果是可能的,但您必须使用稍微复杂一点的技术,该技术包括使用函数wildcard和替代字符串等。详细内容将在下一节论述。

微软的操作系统(MS-DOS、MS-WINDOWS)使用反斜杠分离目录路径,如:

C:\foo\bar\bar.c

这和Unix风格‘c:/foo/bar/bar.c’等价(‘c:’是驱动器字母)。当make在这些系统上运行时,不但支持在路径中存在反斜杠也支持Unix风格的前斜杠。但是这种对反斜杠的支持不包括通配符扩展,因为通配符扩展时,反斜杠用作引用字符。所以,在这些场合您必须使用Unix风格的前斜杠。

4.2.3函数wildcard
通配符在规则中可以自动扩展,但设置在变量中或在函数的参数中通配符一般不能正常扩展。如果您需要在这些场合扩展通配符,您应该使用函数wildcard,格式如下:

$(wildcard pattern...)
可以在makefile文件的任何地方使用该字符串,应用时该字符串被一列在指定目录下存在的并且文件名和给出的文件名的格式相符合的文件所代替,文件名中间由空格隔开。如果没有和指定格式一致的文件,则函数wildcard的输出将会省略。注意这和在规则中通配符扩展的方式不同,在规则中使用逐字扩展方式,而不是省略方式(参阅上节)。

使用函数wildcard得到指定目录下所有的C语言源程序文件名的命令格式为:

$(wildcard *.c)
我们可以把所获得的C语言源程序文件名的字符串通过将‘.c’后缀变为‘.o’转换为OBJ文件名的字符串,其格式为:

$(patsubst %.c,%.o,$(wildcard *.c))
这里我们使用了另外一个函数:patsubst,详细内容参阅字符串替换和分析函数。

这样,一个编译特定目录下所有C语言源程序并把它们连接在一起的makefile文件可以写成如下格式:

objects := $(patsubst %.c,%.o,$(wildcard *.c))
 
foo : $(objects)
        cc -o foo $(objects)
这里使用了编译C语言源程序的隐含规则,因此没有必要为每个文件写具体编译规则。 ‘:=’是‘=’的变异,对‘:=’的解释,参阅两种风格的变量。

4.3在目录中搜寻依赖
对于大型系统,把源文件安放在一个单独的目录中,而把二进制文件放在另一个目录中是十分常见的。Make 的目录搜寻特性使自动在几个目录搜寻依赖十分容易。当您在几个目录中重新安排您的文件,您不必改动单独的规则,仅仅改动一下搜寻路径即可。

4.3.1 VPATH:所有依赖的搜寻路径
make变量VPATH的值指定了make搜寻的目录。经常用到的是那些包含依赖的目录,并不是当前的目录;但VPATH指定了make对所有文件都适用的目录搜寻序列,包括了规则的目标所需要的文件。

如果一个作为目标或依赖的文件在当前目录中不存在,make就会在VPATH指定的目录中搜寻该文件。如果在这些目录中找到要寻找的文件,则就象这些文件在当前目录下存在一样,规则把这些文件指定为依赖。参阅编写搜寻目录的shell命令。

在VPATH变量定义中,目录的名字由冒号或空格分开。目录列举的次序也是make 搜寻的次序。在MS-DOS、MS-WINDOWS系统中,VPATH变量定义中的目录的名字由分号分开,因为在这些系统中,冒号用为路径名的一部分(通常在驱动器字母后面)。例如:

    VPATH = src:../headers
指定了两个目录,‘src’和‘…/headers’,make也按照这个次序进行搜寻。使用该VPATH的值,下面的规则,

    foo.o : foo.c
在执行时就象如下写法一样会被中断:

foo.o : src/foo.c

然后在src目录下搜寻foo.c。

4.3.2 vpath指令
vpath指令(注意字母是小写)和VPATH变量类似,但却更具灵活性。vpath指令允许对符合一定格式类型的文件名指定一个搜寻路径。这样您就可以对一种格式类型的文件名指定一个搜寻路径,对另外格式类型的文件名指定另外一个搜寻路径。总共由三种形式的vpath指令:

vpath pattern directories

对一定格式类型的文件名指定一个搜寻路径。搜寻的路径由一列要搜寻的目录构成,目录由冒号(在MS-DOS、MS-WINDOWS系统中用分号)或空格隔开,和VPATH变量定义要搜寻的路径格式一样。

vpath pattern 

清除和一定类型格式相联系的搜寻路径。

vpath 

清除所有前面由vapth指令指定的搜寻路径。

一个vpath的格式pattern是一个包含一个’%’的字符串。该字符串必须和正搜寻的一个依赖的文件名匹配,字符%可和任何字符串匹配(关于格式规则,参阅定义与重新定义格式规则)。例如,%.h和任何文件名以.h结尾的文件匹配。如果不使用‘%’,格式必须与依赖精确匹配,这种情况很少使用。

在vpath指令格式中的字符‘%’可以通过前面的反斜杠被引用。引用其它字符‘%’的反斜杠也可以被更多的反斜杠引用。引用字符‘%’和其它反斜杠的反斜杠在和文件名比较之前和格式是分开的。如果反斜杠所引用的字符‘%’没有错误,则该反斜杠不会运行带来任何危害。

如果vpath指令格式和一个依赖的文件名匹配,并且在当前目录中该依赖不存在,则vpath指令中指定的目录和VPATH变量中的目录一样可以被搜寻。例如:

vpath %.h ../headers
将告诉make如果在当前目录中以‘.h’结尾文件不存在,则在‘../headers’目录下搜寻任何以‘.h’结尾依赖。

如果有几个vpath指令格式和一个依赖的文件名匹配,则make一个接一个的处理它们,搜寻所有在指令中指定的目录。Make按它们在makefile文件中出现的次序控制多个vpath指令,多个指令虽然有相同的格式,但它们是相互独立的。以下代码:

vpath %.c foo
vpath %   blish
vpath %.c bar
表示搜寻`.c'文件先搜寻目录`foo'、然后`blish',最后`bar';如果是如下代码:
vpath %.c foo:bar
vpath %   blish
表示搜寻`.c'文件先搜寻目录‘foo'、然后‘bar',最后‘blish'

4.3.3目录搜寻过程
    当通过目录搜寻找到一个文件,该文件有可能不是您在依赖列表中所列出的依赖;有时通过目录搜寻找到的路径也可能被废弃。Make决定对通过目录搜寻找到的路径保存或废弃所依据的算法如下:

1、如果一个目标文件在makefile文件所在的目录下不存在,则将会执行目录搜寻。

2、如果目录搜寻成功,则路径和所得到的文件暂时作为目标文件储存。

3、所有该目标的依赖用相同的方法考察。

4、把依赖处理完成后,该目标可能需要或不需要重新创建:

1、如果该目标不需要重建,目录搜寻时所得到的文件的路径用作该目标所有依赖的路径,同时包含该目标文件。简而言之,如果make不必重建目标,则您使用通过目录搜寻得到的路径。

2、如果该目标需要重建,目录搜寻时所得到的文件的路径将废弃,目标文件在makefile文件所在的目录下重建。简而言之,如果make要重建目标,是在makefile文件所在的目录下重建目标,而不是在目录搜寻时所得到的文件的路径下。

该算法似乎比较复杂,但它却可十分精确的解释实际您所要的东西。

其它版本的make使用一种比较简单的算法:如果目标文件在当前目录下不存在,而它通过目录搜寻得到,不论该目标是否需要重建,始终使用通过目录搜寻得到的路径。

实际上,如果在GNU make中使您的一些或全部目录具备这种行为,您可以使用GPATH变量来指定这些目录。

GPATH变量和VPATH变量具有相同的语法和格式。如果通过目录搜寻得到一个过时的目标,而目标存在的目录又出现在GPATH变量,则该路径将不废弃,目标将在该路径下重建。 

4.3.4编写目录搜寻的shell命令
即使通过目录搜寻在其它目录下找到一个依赖,不能改变规则的命令,这些命令同样按照原来编写的方式执行。因此,您应该小心的编写这些命令,以便它们可以在make能够在发现依赖的目录中处理依赖。

借助诸如‘$^’的自动变量可更好的使用shell命令(参阅自动变量)。例如,‘$^’的值代表所有的依赖列表,并包含寻找依赖的目录;‘$@’的值是目标。

foo.o : foo.c
        cc -c $(CFLAGS) $^ -o $@
变量CFLAGS存在可以方便您利用隐含规则指定编译C语言源程序的旗标。我们这里使用它是为了保持编译C语言源程序一致性。参阅隐含规则使用的变量。

依赖通常情况下也包含头文件,因自动变量‘$<’的值是第一个依赖,因此这些头文件您可以不必在命令中提及,例如:

VPATH = src:../headers
foo.o : foo.c defs.h hack.h
        cc -c $(CFLAGS) $< -o $@
4.3.5 目录搜寻和隐含规则
搜寻的目录是由变量VPATH或隐含规则引入的vpath指令指定的(详细参阅使用隐含规则)。例如,如果文件‘foo.o’没有具体的规则,make则使用隐含规则:如文件foo.c存在,make使用内置的规则编译它;如果文件foo.c不在当前目录下,就搜寻适当的目录,如在别的目录下找到foo.c,make同样使用内置的规则编译它。

隐含规则的命令使用自动变量是必需的,所以隐含规则可以自然地使用目录搜寻得到的文件。

4.3.6 连接库的搜寻目录
对于连接库文件,目录搜寻采用一种特别的方式。这种特别的方式来源于个玩笑:您写一个依赖,它的名字是‘-|name’的形式。(您可以在这里写一些奇特的字符,因为依赖正常是一些文件名,库文件名通常是‘libname.a’ 的形式,而不是‘-|name’ 的形式。)

当一个依赖的名字是‘-|name’的形式时,make特别地在当前目录下、与vpath匹配的目录下、VPATH指定的目录下以及‘/lib’, ‘/usr/lib', 和 ‘prefix/lib'(正常情况为`/usr/local/lib',但是MS-DOS、MS-Windows版本的make的行为好像是prefix定义为DJGPP安装树的根目录的情况)目录下搜寻名字为‘libname.so'的文件然后再处理它。如果没有搜寻到‘libname.so'文件,然后在前述的目录下搜寻‘libname.a'文件。

例如,如果在您的系统中有‘/usr/lib/libcurses.a'的库文件,则:

foo : foo.c -lcurses
        cc $^ -o $@
如果‘foo’比‘foo.c’更旧,将导致命令‘cc foo.c /usr/lib/libcurses.a -o foo'执行。

缺省情况下是搜寻‘libname.so' 和‘libname.a'文件,具体搜寻的文件及其类型可使用.LIBPATTERNS变量指定,这个变量值中的每一个字都是一个字符串格式。当寻找名为‘-|name’的依赖时,make首先用name替代列表中第一个字中的格式部分形成要搜寻的库文件名,然后使用该库文件名在上述的目录中搜寻。如果没有发现库文件,则使用列表中的下一个字,其余以此类推。

.LIBPATTERNS变量缺省的值是"‘lib%.so lib%.a'",该值对前面描述的缺省行为提供支持。您可以通过将该值设为空值从而彻底关闭对连接库的扩展。

4.4假想目标
假想目标并不是一个真正的文件名,它仅仅是您制定的一个具体规则所执行的一些命令的名称。使用假想目标有两个原因:避免和具有相同名称的文件冲突和改善性能。

如果您写一个其命令不创建目标文件的规则,一旦由于重建而提及该目标,则该规则的命令就会执行。这里有一个例子:

clean:
        rm *.o temp
因为rm命令不创建名为‘clean’的文件,所以不应有名为‘clean’的文件存在。因此不论何时您发布`make clean'指令,rm命令就会执行。

假想目标能够终止任何在目录下创建名为‘clean’的文件工作。但如在目录下存在文件clean,因为该目标clean没有依赖,所以文件clean始终会认为已经该更新,因此它的命令将永不会执行。为了避免这种情况,您应该使用象如下特别的.PHONY目标格式将该目标具体的声明为一个假想目标:

.PHONY : clean
一旦这样声明,‘make clean’命令无论目录下是否存在名为‘clean’的文件,该目标的命令都会执行。

因为make知道假想目标不是一个需要根据别的文件重新创建的实际文件,所以它将跳过隐含规则搜寻假想目标的步骤(详细内容参阅使用隐含规则)。这是把一个目标声明为假想目标可以提高执行效率的原因,因此使用假想目标您不用担心在目录下是否有实际文件存在。这样,对前面的例子可以用假想目标的写出,其格式如下:

.PHONY: clean
clean:
        rm *.o temp
另外一个使用假想目标的例子是使用make的递归调用进行连接的情况:此时,makefile文件常常包含列举一系列需要创建的子目录的变量。不用假想目标完成这种任务的方法是使用一条规则,其命令是一个在各个子目录下循环的shell命令,如下面的例子:

subdirs:
        for dir in $(SUBDIRS); do \
          $(MAKE) -C $$dir; \
        done
但使用这个方法存在下述问题:首先,这个规则在创建子目录时产生的任何错误都不及时发现,因此,当一个子目录创建失败时,该规则仍然会继续创建剩余的子目录。虽然该问题可以添加监视错误产生并退出的shell命令来解决,但非常不幸的是如果make使用了‘-k’选项,这个问题仍然会产生。第二,也许更重要的是您使用了该方法就失去使用make并行处理的特点能力。

使用假想目标(如果一些子目录已经存在,您则必须这样做,否则,不存在的子目录将不会创建)则可以避免上述问题:

SUBDIRS = foo bar baz
 
.PHONY: subdirs $(SUBDIRS)
 
subdirs: $(SUBDIRS)
 
$(SUBDIRS):
        $(MAKE) -C $
 
foo: baz
此时,如果子目录‘baz’没有创建完成,子目录’foo’将不会创建;当试图使用并行创建时这种关系的声明尤其重要。

一个假想目标不应该是一个实际目标文件的依赖,如果这样,make每次执行该规则的命令,目标文件都要更新。只要假想目标不是一个真实目标的依赖,假想目标的命令只有在假想目标作为特别目标时才会执行(参阅指定最终目标的参数)。

假想目标也可以有依赖。当一个目录下包含多个程序时,使用假想目标可以方便的在一个makefile文件中描述多个程序的更新。重建的最终目标缺省情况下是makefile文件的第一个规则的目标,但将多个程序作为假想目标的依赖则可以轻松的完成在一个makefile文件中描述多个程序的更新。如下例:

all : prog1 prog2 prog3
.PHONY : all
 
prog1 : prog1.o utils.o
        cc -o prog1 prog1.o utils.o
 
prog2 : prog2.o
        cc -o prog2 prog2.o
 
prog3 : prog3.o sort.o utils.o
        cc -o prog3 prog3.o sort.o utils.o
这样,您可以重建所有程序,也可以参数的形式重建其中的一个或多个(如‘make prog1 prog3')。

当一个假想目标是另一个假想目标的依赖,则该假想目标将作为一个假想目标的子例程。例如,这里‘make cleanall'用来删除OBJ文件、diff文件和程序文件:

.PHONY: cleanall cleanobj cleandiff
 
cleanall : cleanobj cleandiff
        rm program
 
cleanobj :
        rm *.o
 
cleandiff :
        rm *.diff
4.5 没有命令或依赖的规则
如果一个规则没有依赖、也没有命令,而且这个规则的目标也不是一个存在的文件,则make认为只要该规则运行,该目标就已被更新。这意味着,所有以这种规则的目标为依赖的目标,它们的命令将总被执行。这里举一个例子:

clean: FORCE
        rm $(objects)
FORCE:
这里的目标‘FORCR’满足上面的特殊条件,所以以其为依赖的目标‘clean’将总强制它的命令执行。关于‘FORCR’的名字没有特别的要求,但‘FORCR’是习惯使用的名字。

也许您已经明白,使用‘FORCR’的方法和使用假想目标(.PHONY: clean)的结果一样,但使用假想目标更具体更灵活有效,由于一些别的版本的make不支持假想目标,所以‘FORCR’出现在许多makefile文件中。参阅假想目标。

4.6使用空目标文件记录事件
空目标是一个假想目标变量,它用来控制一些命令的执行,这些命令可用来完成一些经常需要的具体任务。但又不象真正的假想目标,它的目标文件可以实际存在,但文件的内容与此无关,通常情况下,这些文件没有内容。

空目标文件的用途是用来记录规则的命令最后一次执行的时间,也是空目标文件最后更改的时间。它之所以能够这样执行是因为规则的命令中有一条用于更新目标文件的‘touch’命令。另外,空目标文件应有一些依赖(否则空目标文件没有存在的意义)。如果空目标比它的依赖旧,当您命令重建空目标文件时,有关的命令才会执行。下面有一个例子:

print: foo.c bar.c
        lpr -p $?
        touch print
按照这个规则,如果任何一个源文件从上次执行‘make print'以来发生变化,键入‘make print'则执行lpr命令。自动变量‘$?’用来打印那些发生变化的文件(参阅自动变量)。

4.7 内建的特殊目标名
一些名字作为目标使用则含有特殊的意义:

l         .PHONY

特殊目标.PHONY的依赖是假想目标。假想目标是这样一些目标,make无条件的执行它命令,和目录下是否存在该文件以及它最后一次更新的时间没有关系。详细内容参阅假想目标。

l         .SUFFIXES

特殊目标.SUFFIXES的依赖是一列用于后缀规则检查的后缀。详细内容参阅过时的后缀规则。

l         .DEFAULT

.DEFAULT指定一些命令,这些命令用于那些没有找到规则(具体规则或隐含规则)更新的目标。详细内容参阅定义最新类型的-缺省规则。如果.DEFAULT指定了一些命令,则所有提及到的文件只能作为依赖,而不能作为任何规则的目标;这些指定的命令也只按照他们自己的方式执行。详细内容参阅隐含规则搜寻算法。

l         .PRECIOUS

特殊目标.PRECIOUS的依赖将按照下面给定的特殊方式进行处理:如果在执行这些目标的命令的过程中,make被关闭或中断,这些目标不能被删除,详细内容参阅关闭和中断make;如果目标是中间文件,即使它已经没有任何用途也不能被删除,具体情况和该目标正常完成一样,参阅隐含规则链;该目标的其它功能和特殊目标.SECONDARY的功能重叠。如果规则的目标格式与依赖的文件名匹配,您可以使用隐含规则的格式(如‘%.O’)列举目标作为特殊目标.PRECIOUS的依赖文件来保存由这些规则创建的中间文件。

l         .INTERMEDIATE

特殊目标.INTERMEDIATE的依赖被处理为中间文件。详细内容参见隐含规则链。.INTERMEDIATE如果没有依赖文件,它将不会发生作用。

l         .SECONDARY

特殊目标.SECONDARY的依赖被处理为中间文件,但它们永远不能自动删除。详细内容参见隐含规则链。.SECONDARY如果没有依赖文件,则所有的makefile文件中的目标都将被处理为中间文件。

l         .DELETE_ON_ERROR

如果在makefile文件的某处.DELETE_ON_ERROR作为一个目标被提及,则如果该规则发生变化或它的命令没有正确完成而退出,make将会删除该规则的目标,具体行为和它受到了删除信号一样。详细内容参阅命令错误。

l         .IGNORE

如果您特别为目标.IGNORE指明依赖,则MAKE将会忽略处理这些依赖文件时执行命令产生的错误。如果.IGNORE作为一个没有依赖的目标提出来,MAKE将忽略处理所有文件时产生的错误。.IGNORE命令并没有特别的含义,.IGNORE的用途仅是为了和早期版本的兼容。因为.IGNORE影响所有的命令,所以它的用途不大;我们推荐您使用其它方法来忽略特定命令产生的错误。详细内容参阅命令错误。

l         .SILENT

如果您特别为.SILENT指明依赖,则在执行之前MAKE将不会回显重新构造文件的命令。如果.SILENT作为一个没有依赖的目标提出来,任何命令在执行之前都不会打印。.SILENT并没有特别的含义,其用途仅是为了和早期版本的兼容。我们推荐您使用其它方法来处理那些不打印的命令。详细内容参阅命令回显。如果您希望所有的命令都不打印,请使用‘-s’或‘-silent’选项(详细参阅选项概要)。

l         .EXPORT_ALL_VARIABLES

如该特殊目标简单的作为一个目标被提及,MAKE将缺省地把所有变量都传递到子进程中。参阅使与子MAKE通信的变量。

l         .NOTPARALLEL

如果.NOTPARALLEL作为一个目标提及,即使给出‘-j’选项,make也不使用并行执行。但递归调用的make命令仍可并行执行(在调用的makefile文件中包含.NOTPARALLEL的目标的例外)。.NOTPARALLEL的任何依赖都将忽略。

任何定义的隐含规则后缀如果作为目标出现都会视为一个特殊规则,即使两个后缀串联起来也是如此,例如‘.c.o’。这些目标称为后缀规则,这种定义方法是过时的定义隐含规则的方法(目前仍然广泛使用的方法)。原则上,如果您要把它分为两个并把它们加到后缀列表中,任何目标名都可采用这种方法指定。实际上,后缀一般以‘.’开始,因此,这些特别的目标同样以‘.’开始。具体参阅过时的后缀规则。

4.8 具有多个目标的规则
具有多个目标的规则等同于写多条规则,这些规则除了目标不同之外,其余部分完全相同。相同的命令应用于所有目标,但命令执行的结果可能有所差异,因此您可以在命令中使用‘$@’分配不同的实际目标名称。这条规则同样意味着所有的目标有相同的依赖。

在以下两种情况下具有多个目标的规则相当有用:

l         您仅仅需要依赖,但不需要任何命令。例如:

kbd.o command.o files.o: command.h

为三个提及的目标文件给出附加的共同依赖。

l         所有的目标使用相同的命令。但命令的执行结果未必完全相同,因为自动变量‘$@’可以在重建时指定目标(参阅自动变量)。例如:

bigoutput littleoutput : text.g
        generate text.g -$(subst output,,$@) > $@
等同于:

bigoutput : text.g
        generate text.g -big > bigoutput
littleoutput : text.g
        generate text.g -little > littleoutput
这里我们假设程序可以产生两种输出文件类型:一种给出‘-big’,另一种给出‘-little’。参阅字符串代替和分析函数,对函数subst的解释。

如果您喜欢根据目标变换依赖,象使用变量‘$@’变换命令一样。您不必使用具有多个目标的规则,您可以使用静态格式规则。详细内容见下文。

4.9 具有多条规则的目标
一个目标文件可以有多个规则。在所有规则中提及的依赖都将融合在一个该目标的依赖列表中。如果该目标比任何一个依赖‘旧’,所有的命令将执行重建该目标。

但如果一条以上的规则对同一文件给出多条命令,make将使用最后给出的规则,同时打印错误信息。(作为特例,如果文件名以点‘.’开始,不打印出错信息。这种古怪的行为仅仅是为了和其它版本的make兼容)。您没有必要这样编写您的makefile文件,这正是make给您发出错误信息的原因。

一条特别的依赖规则可以用来立即给多条目标文件提供一些额外的依赖。例如,使用名为‘objects’的变量,该变量包含系统产生的所有输出文件列表。如果‘congfig.h’发生变化所有的输出文件必须重新编译,可以采用下列简单的方法编写:

objects = foo.o bar.o
foo.o : defs.h
bar.o : defs.h test.h
$(objects) : config.h
这些可以自由插入或取出而不影响实际指定的目标文件生成规则,如果您希望断断续续的为目标添加依赖,这是非常方便的方法。

另外一个添加依赖的方法是定义一个变量,并将该变量作为make命令的参数使用。详细内容参阅变量重载。例如:

extradeps=
$(objects) : $(extradeps)
命令`make extradeps=foo.h'含义是将‘foo.h’作为所有OBJ文件的依赖,如果仅仅输入‘make’命令则不是这样。

如果没有具体的规则为目标的生成指定命令,那么make将搜寻合适的隐含规则进而确定一些命令来完成生成或重建目标。详细内容参阅使用隐含规则。

4.10 静态格式规则
静态格式规则是指定多个目标并能够根据每个目标名构造对应的依赖名的规则。静态格式规则在用于多个目标时比平常的规则更常用,因为目标可以不必有完全相同的依赖;也就是说,这些目标的依赖必须类似,但不必完全相同。

4.10.1 静态格式规则的语法
这里是静态格式规则的语法格式:

targets ...: target-pattern: dep-patterns ...
        commands
        ...
目标列表指明该规则应用的目标。目标可以含有通配符,具体使用和平常的目标规则基本一样(参阅在文件名中使用通配符)。

目标的格式和依赖的格式是说明如何计算每个目标依赖的方法。从匹配目标格式的目标名中依据格式抽取部分字符串,这部分字符串称为径。将径分配到每一个依赖格式中产生依赖名。

每一个格式通常包含字符‘%’。目标格式匹配目标时,‘%’可以匹配目标名中的任何字符串;这部分匹配的字符串称为径;剩下的部分必须完全相同。如目标‘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 $@
这里‘$<’是自动变量,控制依赖的名称,‘$@’也是自动变量,掌握目标的名称。详细内容参阅自动变量。

每一个指定目标必须和目标格式匹配,如果不符则产生警告。如果您有一列文件,仅有其中的一部分和格式匹配,您可以使用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',第一个静态格式规则是将相应的C语言源文件编译更新为OBJ文件,‘$(filter %.elc,$(files))' 的结果是‘foo.elc',它由‘foo.el’构造。

另一个例子是阐明怎样在静态格式规则中使用‘$*’:

bigoutput littleoutput : %output : text.g
        generate text.g -$* > $@
当命令generate执行时,$*扩展为径,即‘big’或‘little’二者之一。

4.10.2静态格式规则和隐含规则
静态格式规则和定义为格式规则的隐含规则有很多相同的地方(详细参阅定义与重新定义格式规则)。双方都有对目标的格式和构造依赖名称的格式,差异是make使用它们的时机不同。

隐含规则可以应用于任何于它匹配的目标,但它仅仅是在目标没有具体规则指定命令以及依赖可以被搜寻到的情况下应用。如果有多条隐含规则适合,仅有执行其中一条规则,选择依据隐含规则的定义次序。

相反,静态格式规则用于在规则中指明的目标。它不能应用于其它任何目标,并且它的使用方式对于各个目标是固定不变的。如果使用两个带有命令的规则发生冲突,则是错误。

静态格式规则因为如下原因可能比隐含规则更好:

l         对一些文件名不能按句法分类的但可以给出列表的文件,使用静态格式规则可以重载隐含规则链。

l         如果不能精确确定使用的路径,您不能确定一些无关紧要的文件是否导致make使用错误的隐含规则(因为隐含规则的选择根据其定义次序)。使用静态格式规则则没有这些不确定因素:每一条规则都精确的用于指定的目标上。

4.11双冒号规则
双冒号规则是在目标名后使用‘::’代替‘:’的规则。当同一个目标在一条以上的规则中出现时,双冒号规则和平常的规则处理有所差异。

当一目标在多条规则中出现时,所有的规则必须是同一类型:要么都是双冒号规则,要么都是普通规则。如果他们都是双冒号规则,则它们之间都是相互独立的。如果目标比一个双冒号规则的依赖‘旧’,则该双冒号规则的命令将执行。这可导致具有同一目标双冒号规则全部或部分执行。

双冒号规则实际就是将具有相同目标的多条规则相互分离,每一条双冒号规则都独立的运行,就像这些规则的目标不同一样。

对于一个目标的双冒号规则按照它们在makefile文件中出现的顺序执行。然而双冒号规则真正有意义的场合是双冒号规则和执行顺序无关的场合。

双冒号规则有点模糊难以理解,它仅仅提供了一种在特定情况下根据引起更新的依赖文件不同,而采用不同方式更新目标的机制。实际应用双冒号规则的情况非常罕见。

每一个双冒号规则都应该指定命令,如果没有指定命令,则会使用隐含规则。详细内容参阅使用隐含规则。

4.12 自动生成依赖
在为一个程序编写的makefile文件中,常常需要写许多仅仅是说明一些OBJ文件依靠头文件的规则。例如,如果‘main.c’通过一条#include语句使用‘defs.h’,您需要写入下的规则:

main.o: defs.h
您需要这条规则让make知道如果‘defs.h’一旦改变必须重新构造‘main.o’。由此您可以明白对于一个较大的程序您需要在makefile文件中写很多这样的规则。而且一旦添加或去掉一条#include语句您必须十分小心地更改makefile文件。

为避免这种烦恼,现代C编译器根据原程序中的#include语句可以为您编写这些规则。如果需要使用这种功能,通常可在编译源程序时加入‘-M’开关,例如,下面的命令:

cc -M main.c
产生如下输出:

main.o : main.c defs.h
这样您就不必再亲自写这些规则,编译器可以为您完成这些工作。

注意,由于在makefile文件中提及构造‘main.o’,因此‘main.o’将永远不会被隐含规则认为是中间文件而进行搜寻,这同时意味着make不会在使用它之后自动删除它;参阅隐含规则链。

对于旧版的make程序,通过一个请求命令,如‘make depend’,利用编译器的特点生成依赖是传统的习惯。这些命令将产生一个‘depend’文件,该文件包含所有自动生成的依赖;然后makefile文件可以使用include命令将它们读入(参阅包含其它makefile文件)。

在GNU make中,重新构造makefile文件的特点使这个惯例成为了过时的东西――您永远不必具体告诉make重新生成依赖,因为GNU make总是重新构造任何过时的makefile文件。参阅Makefile文件的重新生成的过程。

我们推荐使用自动生成依赖的习惯是把makefile文件和源程序文件一一对应起来。如,对每一个源程序文件‘name.c’有一名为‘name.d’的makefile文件和它对应,该makefile文件中列出了名为‘name.o’的OBJ文件所依赖的文件。这种方式的优点是仅在源程序文件改变的情况下才有必要重新扫描生成新的依赖。

这里有一个根据C语言源程序‘name.c’生成名为‘name.d’依赖文件的格式规则:

%.d: %.c
        set -e; $(CC) -M $(CPPFLAGS) $< \
                  | sed 's/\($*\)\.o[ :]*/\1.o $@ : /g' > $@; \
                [ -s $@ ] || rm -f $@
关于定义格式规则的信息参阅定义与重新定义格式规则。‘-e’开关是告诉shell如果$(CC)命令运行失败(非零状态退出)立即退出。正常情况下,shell退出时带有最后一个命令在管道中的状态(sed),因此make不能注意到编译器产生的非零状态。

对于GNU C编译器您可以使用‘-MM’开关代替‘-M’,这是省略了有关系统头文件的依赖。详细内容参阅《GNU CC使用手册》中控制预处理选项。

命令Sed的作用是翻译(例如):

main.o : main.c defs.h
到:

main.o main.d : main.c defs.h
这使每一个‘.d’文件和与之对应的‘.o’文件依靠相同的源程序文件和头文件,据此,Make可以知道如果任一个源程序文件和头文件发生变化,则必须重新构造依赖文件。

一旦您定义了重新构造‘.d’文件的规则,您可以使用使用include命令直接将它们读入,(参阅包含其它makefile文件),例如:

sources = foo.c bar.c
include $(sources:.c=.d)
(这个例子中使用一个代替变量参照从源程序文件列表‘foo.c bar.c'翻译到依赖文件列表‘foo.d bar.d'。详细内容参阅替换引用。)所以,‘.d’的makefile文件和其它makefile文件一样,即使没用您的任何进一步的指令,make同样会在必要的时候重新构建它们。参阅Makefile文件的重新生成过程。
阅读(247) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~