Chinaunix首页 | 论坛 | 博客
  • 博客访问: 6318184
  • 博文数量: 2759
  • 博客积分: 1021
  • 博客等级: 中士
  • 技术积分: 4091
  • 用 户 组: 普通用户
  • 注册时间: 2012-03-11 14:14
文章分类

全部博文(2759)

文章存档

2019年(1)

2017年(84)

2016年(196)

2015年(204)

2014年(636)

2013年(1176)

2012年(463)

分类: LINUX

2013-09-11 12:10:21

第四章Makefile 的规则 

4.2  规则语法


规则中“TARGETS ”可以是空格分开的多个文件名,也可以是一个标签(例如:执行清空的“clean”)。

通常规则只有一个目标文件(建议这么做),偶尔会在一个规则中需要多个目标

 

书写规则是我们需要注意的几点

1.规则的命令部分有两种书写方式:

a. 命令可以和目标:依赖描述放在同一行。命令在依赖文件列表后并使用分号(;)和依赖文件列表分开。

b. 命令在目标:依赖的描述的下一行,作为独立的命令行。当作为独立的命令行时此行必须以[Tab] 字符开始。在Makefile 中,在第一个规则之后出现的所有以[Tab] 字符开始的行都会被当作命令来处理。

 

2.Makefile 中符号“”有特殊的含义(表示变量或者函数的引用),在规则中需

要使用符号“$”的地方,需要书写两个连续的(“$$”)。

 

3.前边已提到过,对于 Makefile 中一个较长的行,我们可以使用反斜线“”将其书写到几个独立的物理行上。虽然makeMakefile 文本行的最大长度是没有限制的,但还是建议这样做。不仅书写方便而且更有利于别人的阅读(这也是一个程序员修养的体现)。 

 

4.3  依赖的类型 

1. 以前章节所提到的规则中使用的是常规依赖,这是书写Makefile 规则时最常用的一种。2. 另外一种在我们书写Makefile 时不会经常使用,它比较特殊、称之为“order-only”依赖

 

规则依赖列表中管道符号“|”左边的是常规依赖,管道符号右边的就是“order-only”依赖。这样的规则书写格式如下: 

 

TARGETS : NORMAL-PREREQUISITES | ORDER-ONLY-PREREQUISITES

 

(简单来说,如果只有order-only依赖改的话那么目标不重建,如果普通依赖改的话,目标文件都会重建)

4.4  文件名使用通配符 

Maekfile 中表示文件名时可使用通配符。可使用的通配符有:“”、“?”和“[”。在Makefile 中通配符的用法和含义和Linuxunix )的Bourne shell 完全相同。

 

Makefile 通配符可以出现在以下两种场合:

 

1.可以用在规则的目标、依赖中,

2.可出现在规则的命令中,通配符的通配处理是在shell在执行此命令时完成的。 

除这两种情况之外的其它上下文中,不能直接使用通配符。而是需要通过函数“wildcard”(可参考  8.3  文件名处理函数  一节)来实现

 

如果规则的一个文件名包含统配字符(“”、“”等字符),在使用这样的文件时需要对文件名中的统配字符使用反斜线()进行转义处理。例如“foo\*bar ”,在 Makefile中它表示了文件“foo*bar

 

在 Linuxunix )中,以波浪线“”开始的文件名有特殊含义。单独使用它或者其后跟一个斜线(~/ ),代表了当前用户的宿主目录(在 shell下可以通过命令“echo ~(~\) ”来查看)

 

波浪线之后跟一个单词(~word ),代表由这个“word”所指定的用户的宿主目录。例如“~john/bin”就是代表用户 john 的宿主目录下的bin 目录。

 

4.4.1  统配符使用举例

例如Makefile 的清空过程文件规则: 

 

clean: 

rm -f *.o 

 

通配符也可以用在规则的依赖文件名中。看看下面这个例子。执行“make print”,

执行的结果是打印当前工作目录下所有的在上一次打印以后被修改过的“.c ”文件。 

 

print: *.c 

lpr -p $? 

touch print 

 

1. 上述的规则中目标“print ”时一个空目标文件。(当前目录下存在一个文件“print ”,但我们不关心它的实际内容,此文件的作用只是记录最后一次执行此规则的时间。参考  4.8  空目标文件  一节)。

2. 自动环变量“$?”在这里表示依赖文件列表中被改变过的所有文件。 

 

注意:比如,在 Makefile 有这样一个变量定义:“objects = *.o ”。它表示变量“objects”的值是字符串“*.o”(并不是期望的空格分开的.o 文件列表)。当需要变量“objects”代表所有.o 文件列表示,需要使用函数“wildcard”(objects = $(wildcar *.o))。 

正好符合了4.4节说的情况。

 

4.4.3  函数wildcard

 

在规则中,通配符会被自动展开。但在变量的定义和函数引用时,通配符将失效。这种情况下如果需要通配符有效,就需要使用函数“wildcard”,它的用法是:

$(wildcard PATTERN...) 

 

比如:一般我们可以使用“$(wildcard *.c) ”来获取工作目录下的所有的.c 文件列表

 

可以使用“$(patsubst %.c,%.o,$(wildcard *.c))”,首先使用“wildcard”函数获取工作目录下的.c 文件列表;之后将列表中所有文件名的后缀.c 替换为.o 

$(patsubst) 是一个文本处理函数

4.5  目录搜寻  

当工程的目录结构发生变化后,就可以做到不更改Makefile 的规则,只更改依赖文件的搜索目录

GNU make 可以识别一个特殊变量“VPATH”。通过变量“VPATH”可以指定依赖文件的搜索路径,当规则的依赖文件在当前目录不存在时,make会在此变量所指定的目录下去寻找这些依赖文件。通常我们都是用此变量来指定规则的依赖文件的搜索路径。

 

其实“VPATH”变量所指定的是Makefile 中所有文件的搜索路径,包括了规则的依赖文件和目标文件

 

定义变量“VPATH”时,使用空格或者冒号(:)将多个需要搜索的目录分开。make搜索目录的顺序是按照变量“VPATH”定义中的目录顺序进行的(当前目录永远是第一搜索目录)。例如对变量的定义如下: 

 

VPATH = src:../headers 

这样我们就为所有规则的依赖指定了两个搜索目录,“src”和“../headers ”。对于规则“foo:foo.c ”如果“foo.c”存在于“src”目录下,此规则等价于“foo:src:/foo.c ”。 

 

通过“VPATH”变量指定的路径在Makefile 中对所有文件有效。

 

当需要为不类型

的文件指定不同的搜索目录时, vpath

4.5.2  选择性搜索(关键字vpath) 

另一个设置文件搜索路径的方法是使用make的“vpath”关键字(全小写的)

 

1vpath PATTERN DIRECTORIES  

为所有符合模式“PATTERN”的文件指定搜索目录“DIRECTORIES ”。多个目

录使用空格或者冒号(:)分开。类似上一小节的“VPATH”变量。 

2vpath PATTERN 

清除之前为符合模式“PATTERN”的文件设置的搜索路径。 

3vpath  

清除所有已被设置的文件搜索路径。

 

vapth 使用方法中的“PATTERN”需要包含模式字符“”。

 

”意思是匹配一个或者多个字符,例如,“%.h”表示所有以“.h ”结尾的文件。如果在“PATTERN”中没有包含模式字符“”,那么它就是一个明确的文件名,这样就是给定了此文件的所在目录,我们很少使用这种方式来为单独的一个文件指定搜索路径。

 

在“vpath”所指定的模式中我们可以使用反斜杠来对字符“%”进行引用(和其他的特使字符的引用一样)。 

 

PATTERN”表示了具有相同特征的一类文件,而“DIRECTORIES ”则指定了搜索此类文件目录。当规则的依赖文件列表中的文件不能在当前目录下找到时,make程序将依次在“DIRECTORIES ”所描述的目录下寻找此文件。例如: 

 

vpath %.h ../headers 

 

诸如“$^”等。规则命令行中的自动化变量“$^”代表所有通过目录搜索得到的依赖文件的完整路径名(目录 +  一般文件名列表)。

$@ ”代表规则的目标。所以对于一个规则我们可以进行如下的描述: 

 

foo.o : foo.c 

cc -c $(CFLAGS) $^ -o $@ 

 

变量“CFLAGS”是编译.c 文件时gcc 的编译选项

 

自动化变量“$<”代表规则中通过目录搜索得到的依赖文件列表的第一个依赖文件。

 

4.5.6  库文件和搜索目录

Makefile 中程序链接的静态库、共享库同样也可以通过搜索目录得到。这一特性需

要我们在书规则的依赖时指定一个类似“-lNAME ”的依赖文件名

1. make 在执行规则时会在当前目录下搜索一个名字为“libNAME.so ”的文件;

2. 如果当前工作目录下不存在这样一个文件,则make会继续搜索使用“VPATH”或者“vpath”指定的搜索目录。3. 还是不存在,make将搜索系统库文件存在的默认目录,顺序是:“/lib ”、“/usr/lib ”和“PREFIX/lib ”(在Linux 系统中为“/usr/local/lib”,其他的系统可能不同)

 

假设你的系统中存在“/usr/lib/libcurses.a”(不存在“/usr/lib/libcurses.so ”)这个库文件。看一个例子:  

foo : foo.c -lcurses 

cc $^ -o $@ 

 

4.6  Makefile伪目标 

我们讨论Makefile 的一个重要的特殊目标:伪目标。伪目标是这样一个目标:

它不代表一个真正的文件名,在执行make时可以指定这个目标来执行其所在规则定义

的命令,有时也可以将一个伪目标称为标签。

1.   如果我们需要书写这样一个规则:规则所定义的命令不是去创建目标文件,而

是通过make命令行明确指定它来执一些特定的命令。像常见的clean 目标: 

 

clean: 

rm *.o temp 

当工作目录下不存在“clean”这个文件时,我们输入“make clean”,“rm *.o temp”总会被执行。这是我们的初衷。 

但是如果在当前工作目录下存在文件“clean”,情况就不一样了,同样我们输入“make clean”,由于这个规则没有任何依赖文件,所以目标被认为是最新的而不去执行规则所定义的命令,因此命令“rm”将不会被执行

我们需要将目标“clean”声明为伪目标。将一个目标声明为伪目标的方法是将它作为特殊目标.PHONY ”的依赖。如下: 

 

.PHONY : clean 

 

这样目标“clean”就被声明为一个伪目标,无论在当前目录下是否存在“clean”这个文件。我们输入“make clean”之后。“rm”命令都会被执行。

 

 

当一个伪目标作为另外一个伪目标依赖时,make将其作为另外一个伪目标的子程来处理(可以这样理解:其作为另外一个伪目标的必须执行的部分,就行语言中的函数调用一样)。下边的例子就是这种用法: 

.PHONY: cleanall cleanobj cleandiff 

cleanall : cleanobj cleandiff 

rm program 

 

cleanobj : 

rm *.o 

 

cleandiff : 

rm *.diff

 

4.7  强制目标(没有命令或依赖的规则) 

如果一个规则没有命令或者依赖,并且它的目标不是一个存在的文件名。在执行此规则时,目标总会被认为是最新的。就是说:这个规则一旦被执行,make就认为它的目标已经被更新过。这样的目标在作为一个规则的依赖时,因为依赖总被认为被更新过,因此作为依赖所在的规则中定义的命令总会被执行。看一个例子: 

 

clean: FORCE 

rm $(objects) 

FORCE: 

 

这个例子中,目标“FORCE”符合上边的条件。它作为目标“clean”的依赖,在执行

make 时,总被认为被更新过。因此“clean”所在规则在被执行时其所定义的命令总会被执行。这样的一个目标通常我们将其命名为“FORCE”。 上边的例子中使用“FORCE”目标的效果和将  “clean”声明为伪目标效果相同。两种方式相比较,使用“.PHONY ”方式更加直观高效。这种方式主要用在非GNU版本的make中。 

4.8  空目标文件 

空目标文件是伪目标的一个变种;此目标所在规则执行的目的和伪目标相同——通过make 命令行指定将其作为终极目标来执行此规则所定义的命令。和伪目标不同的是:这个目标可以是一个存在的文件,但文件的具体内容我们并不关心,通常此文件是一个空文件。 

 

空目标文件只是用来记录上一次执行此规则命令的时间。在这样的规则中,命令部

分都会使用“touch ”在完成所有命令之后来更新目标文件的时间戳,记录此规则命令

的最后执行时间。: 

 

print: foo.c bar.c 

lpr -p $? 

touch print 

意思就是说有个目标文件,其也有依赖,但是没什么用,不是构造目标文件用的,起的就是时间戳的作用。

4.9  Makefile的特殊目标 

Makefile 中,有一些名字,当它们作为规则的目标时,具有特殊含义。它们是

一些特殊的目标,GNU make 所支持的特殊的目标

 

.PHONY : 

目标“.PHONY ”的所有的依赖被作为伪目标。伪目标时这样一个目标:当使用

、make命令行指定此目标时,这个目标所在规则定义的命令、无论目标文件是否存在都

会被无条件执行。

 

.SUFFIXES:  

特殊目标“SUFFIXES ”的所有依赖指出了一系列在后缀规则中需要检查的后缀名

(就是当前make需要处理的后缀)。参考  10.7 后缀规则  一节 

 

.DEFAULT 

Makefile 中,目标“.DEFAULT ”所在规则定义的命令,被用在重建那些没有具体规则的目标(明确规则和隐含规则)。就是说一个文件作为某个规则的依赖,但却不是另外一个规则的目标时。Make 程序无法找到重建此文件的规则,此种情况时就执行.DEFAULT ”所指定的命令。 

 

.PRECIOUS 

目标“.PRECIOUS ”的所有依赖文件在make过程中会被特殊处理:当命令在执行过程中被中断时,make不会删除它们(可参考  5.5  中断make的执行  一节)。而且如果目标的依赖文件是中间过程文件,同样这些文件不会被删除。这一点目标“.PRECIOUS ”和目标“.SECONDAY ”实现的功能相同。参考  10.4 make 隐含规则链  一节 另外,目标“.PRECIOUS ”的依赖文件也可以是一个模式,例如“%.o”。这样可以保留有规则创建的中间过程文件。 

.INTERMEDIATE  

目标“.INTERMEDIATE ”的依赖文件在make时被作为中间过程文件对待。没有任何依赖文件的目标“.INTERMEDIATE ”没有意义。参考  10.4 make 隐含规则链  一节 

 

.SECONDARY 

目标“.SECONDARY ”的依赖文件被作为中间过程文件对待。但这些文件不会被

自动删除(可参考  10.4 make 隐含规则链  一节) 

没有任何依赖文件的目标“.SECONDARY ”的含义是:将所有的文件作为中间过

程文件(不会自动删除任何文件)。 

 

.DELETE_ON_ERROR 

如果在Makefile中存在特殊目标“.DELETE_ON_ERROR ”,make在执行过程中,

如果规则的命令执行错误,将删除已经被修改的目标文件。参考  5.4  命令执行的错误 

一节 

.IGNORE 

如果给目标“.IGNORE”指定依赖文件,则忽略创建这个文件所执行命令的错误。

给此目标指定命令是没有意义的。当此目标没有依赖文件时,将忽略所有命令执行的错

误。参考  5.4  命令执行的错误  一节 

 

.LOW_RESOLUTION_TIME 

目标“.LOW_RESOLUTION_TIME ”的依赖文件被make认为是低分辨率时间戳文件。给目标“.LOW_RESOLUTION_TIME ”指定命令是没有意义的。 通常文件的时间辍都是高分辨率的,make在处理依赖关系时、对规则目标依赖文件的高分辨率的时间戳进行比较,判断目标是否过期。但是在系统中并没有提供一个修改文件高分辨率时间辍的机制(方式),因此类似“cp -p”这样的命令在根据源文件创建目的文件时,所产生的目的文件的高分辨率时间辍的细粒度部分被丢弃(来源于源文件)。这样可能会造成目的文件的时间戳和源文件的相等甚至不及源文件新。处理此类命令创建的文件时,需要将命令创建的文件作为目标“.LOW_RESOLUTION_TIME ”的依赖,声明这个文件是一个低分辨率时间辍的文件。例如: 

 

.LOW_RESOLUTION_TIME: dst 

dst: src 

cp -p src dst 

 

首先规则的命令“cp p src dst ”,所创建的文件“dst”在时间戳上稍稍比“src”晚(因

为命令不能更新文件“dst”的细粒度时间)。因此 make在判断文件依赖关系时会出现

误判,将文件作为目标“.LOW_RESOLUTION_TIME ”的依赖后,只要规则中目标和

依赖文件的时间戳中的初始时间相等,就认为目标已经过期。这个特殊的目标主要作用

是,弥补系统在没有提供修改文件高分辨率时间戳机制的情况下,某些命令在make

的一些缺陷。 

对于静态库文件(文档文件)成员的更新也存在这个问题。make在创建或者更新

静态库时,会自动将静态库的所有成员作为目标“.LOW_RESOLUTION_TIME ”的依

赖。 

.SILENT 

出现在目标“.SILENT ”的依赖列表中的文件,make在创建这些文件时,不打印

出重建此文件所执行的命令。同样,给目标“.SILENT ”指定命令行是没有意义的。 

没有任何依赖文件的目标“.SILENT ”告诉make在执行过程中不打印任何执行的

命令。现行版本make支持目标“.SILENT ”的这种功能和用法是为了和旧版本的兼容。

在当前版本中如果需要禁命令执行过程的打印,可以使用make的命令行参数“-s ”或

者“--silent ”。参考  9.7 make的命令行选项  一节 

.EXPORT_ALL_VARIABLES 

此目标应该作为一个简单的没有依赖的目标,它的功能含义是将之后所有的变量传

递给子make进程。参考  5.6 make的递归执行  一节 

 

.NOTPARALLEL 

Makefile 中,如果出现目标“.NOPARALLEL ”,则所有命令按照串行方式执行,即使存在make的命令行参数“-j ”。但在递归调用的字 make进程中,命令可以并行执。此目标不应该有依赖文件,所有出现的依赖文件将被忽略。 所有定义的隐含规则后缀作为目标出现时,都被视为一个特殊目标,两个后缀串联起来也是如此,例如“.c.o ”。这样的目标被称为后缀规则的目标,这种定义方式是已经过时的定义隐含规则的方法(目前,这种方式还被用在很多地方)。原则上,如果将其分为两个部分、并将它们加到后缀列表中,任何目标都可采用这种方式来表示。实际中,后缀通常以“”开始,因此,以上的这些特别目标同样是以“”开始。可参考  10.7 后缀规则  一节 

 

4.10 多目标

一个规则中可以有多个目标,规则所定义的命令对所有的目标有效。一个具有多目标的规则相当于多个规则。

 

仅需要一个描述依赖关系的规则,不需要在规则中定义命令。例如 

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

 

对于多个具有类似重建命令的目标。重建这些目标的命令并不需要是完全相同,

因为可以在命令行中使用自动环变量“$@ ”来引用具体的目标

 

例如规则: 

 

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 

 

例子中的“generate ”根据命令行参数来决定输出文件的类型。使用了make的字符串处理函数“subst”来根据目标产生对应的命令行选项

4.12 静态模式  

静态模式规则是这样一个规则:规则存在多个目标,并且不同的目标可以根据目标

文件的名字来自动构造出依赖文件

 

4.12.1  静态模式规则的语法 

首先,我们来看一下静态模式规则的基本语法: 

TARGETS ...: TARGET-PATTERN: PREREQ-PATTERNS ... 

COMMANDS 

 

从目标模式(TAGET-PATTERN)的目标名字中抽取一部分字符串(称为“茎”)。使用“茎”替代依赖模式(PREREQ-PATTERNS )中的相应部分来产生对应目标的依赖文件首先在目标模式和依赖模式中,一般需要包含模式字符“”。在目标模式(TAGET-PATTERN)中“”可以匹配目标文件的任何部分

 

foo.o”符合模式“%.o”,其“茎”为“ foo ”。

 

我们来看一个例子,它根据相应的.c 文件来编译生成“foo.o”和“bar.o”文件: 

 

objects = foo.o bar.o 

 

all: $(objects) 

 

$(objects): %.o: %.c 

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

 

例子中,规则描述了所有的.o 文件的依赖文件为对应的.c 文件,对于目标“foo.o”,取其茎“foo ”替代对应的依赖模式“%.c”中的模式字符“”之后可得到目标的依赖文件“foo.c”。这就是目标“foo.o”的依赖关系“foo.o: foo.c”,规则的命令行描述了如何完成由“foo.c编译生成目标“foo.o”。命令行中“$<”和“$@ ”是自动化变量,$<”表示规则中的第一个依赖文件,$@ ”表示规则中的目标文件(可参考  10.5.3 自动化变量  一小节)。上边的这个规则描述了以下两个具体的规则:

 

foo.o : foo.c 

$(CC) -c $(CFLAGS) foo.c -o foo.o 

bar.o : bar.c 

$(CC) -c $(CFLAGS) bar.c -o bar.o 

 

在使用静态模式规则时,指定的目标必须和目标模式相匹配,否则执行make时将会得到一个错误提示。如果存在一个文件列表,其中一部分符合某一种模式而另外一部分符合另外一种模式,这种情况下我们可以使用“filter ”函数(可参考  第八章 make的内嵌函数)来对这个文件列表进行分类,在分类之后对确定的某一类使用模式规则。

例如: 

 

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”。“filter ”函数过滤不符合“%.o”模式的文件名而返回所有符合此模式的文件列表。第一条静态模式规则描述了这些目标文件是通过编译对应的.c 源文件来重建的。

 

我们通过另外一个例子来看一下自动环变量“$* ”在静态模式规则中的使用方法: 

 

bigoutput littleoutput : %output : text.g 

generate text.g -$* > $@ 

 

当执行此规则的命令时,自动环变量“$* ”被展开为“茎”。在这里就是“big”和“little

 

4.13 双冒号规则

双冒号规则就是使用“:: ”代替普通规则的“”得到的规则。

 

首先需要明确的是:Makefile 中,一个目标可以出现在多个规则中。但是这些规

则必须是同一类型的规则,要么都是普通规则,要么都是双冒号规则。而不允许一个目

标同时出现在两种不同类型的规则中。双冒号规则和普通规则的处理的不同点表现在以

下几个方面: 

1.  双冒号规则中,当依赖文件比目标更新时。规则将会被执行。对于一个没有依赖而只有命令行的双冒号规则,当引用此目标时,规则的命令将会被无条件执行。而普通规则,当规则的目标文件存在时,此规则的命令永远不会被执行(目标文件永远是最新的)。 

 

2.  当同一个文件作为多个双冒号规则的目标时。这些不同的规则会被独立的处理,而不是像普通规则那样合并所有的依赖到一个目标文件。这就意味着对这些规则的处理就像多个不同的普通规则一样。就是说多个双冒号规则中的每一个的依赖文件被改变之后,make只执行此规则定义的命令,而其它的以这个文件作为目标的双冒号规则将不会被执行。 我们来看一个例子,在我们的Makefile 中包含以下两个规则: 

 

Newprog :: foo.c 

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

Newprog :: bar.c 

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

 

如果“foo.c”文件被修改,执行 make以后将根据“foo.c”文件重建目标“Newprog ”。而如果“bar.c”被修改那么“Newprog ”将根据“bar.c”被重建。

回想一下,如果以上两个规则为普通规时出现的情况是什么?(make将会出错并提示错误信息) 

当同一个目标出现在多个双冒号规则中时,规则的执行顺序和普通规则的执行顺序一样,按照其在Makefile 中的书写顺序执行。 GNU make的双冒号规则给我们提供一种根据依赖的更新情况而执行不同的命令来重建同一目标的机制。一般这种需要的情况很少,所以双冒号规则的使用比较罕见。一般双冒号规则都需要定义命令,如果一个双冒号规则没有定义命令,在执行规则时将

为其目标自动查找隐含规则

 

4.14 自动产生依赖

 

Makefile 中,有时需要书写一些规则来描述一个.o 文件和头文件的依赖关系。例如,如果在main.c 中使用“#include defs.h ”,那么我们可能就需要一个像下边那样的规则来描述当头文件“defs.h ”被修改以后再次执行make,目标“main.o”应该被重建。 main.o: defs.h 这样,对于一个大型工程。就需要在 Makefile 中书写很多条类似于这样的规则。并且,当在源文件中加入或删除头文件后,也需要小心地去修改Makefile。这是一件非常费力、费时并且危险(容易出错误)的工作。为了避免这个讨厌的问题,现代的编译器提供了通过查找源文件中的“#include ”来自动产生这种依赖关系的功能。

 

gcc 的“-M”选项时,其输出结果中也包含对标准库的头文件的依赖关系描述。当不需要在依赖关系中考虑标准库头文件时,对于gcc 需要使用“-MM”参数对于一个源文件

 

NAME.c”,对应的这个 makefile 文件为“NAME.d”。NAME.d ”中描述了文件“NAME.o ”所要依赖的所有头文件。采用这种方式,只有源文件在修改之后才会重新用命令生成新的依赖关系描述文件“NAME.o”。 我们可以使用如下的模式规则来自动生成每一个.c 文件对应的.d 文件: 

 

%.d: %.c 

$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \ 

sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \ 

rm -f  $@.$$$$

 

此规则的含义是:所有的.d 文件依赖于同名的.c 文件。 

第一行;使用编译器自自动生成依赖文件($<)的头文件的依赖关系,并输出成为一个临时文件,$$$$”表示当前进程号。如果$(CC) GNU编译工具,产生的依赖关系的规则中,依赖头文件包括了所有的使用的系统头文件和用户定义的头文件。如果需要生成的依赖描述文件不包含系统头文件,可使用“-MM”代替“-M”。 

第二行;使用sed 处理第二行已产生的那个临时文件并生成此规则的目标文件。

这里sed 完成了如下的转换过程。例如对已一个.c 源文件。

(sed不太明白

将编译器产生的依赖关系: 

main.o : main.c defs.h 

转成: 

main.o main.d : main.c defs.h 

 

这样就将.d 加入到了规则的目标中,其和对应的.o 文件文件一样依赖于对应的.c 源文件和源文件所包含的头文件。当.c 源文件或者头文件被改变之后规则将会被执行,相应的.d 文件同样会被更新。 

 

第三行;删除临时文件。 

使用上例的规则就可以建立一个描述目标文件依赖关系的.d 文件。我们可以在Makefile中使用include指示符将描述将这个文件包含进来。在执行make时,Makefile所包含的所有.d 文件就会被自动创建或者更新(具体过程可参考  3.7 makefile文件的重建  一节)。Makefile中对当前目录下.d 文件处理可以参考如下: 

 

sources = foo.c bar.c 

sinclude $(sources:.c=.d) 

 

例子中,变量“sources ”定义了当前目录下的需要编译的源文件。变量引用置换“$(sources : .c=.d) ”的功能是根据变量“source”指定的.c 文件自动产生对应的.d 

件(参考  6.3 变量的高级用法  一节),并在当前Makefile文件中包含这些.d 文件。.d 

件和其它的makefile文件一样,make在执行时读取并试图重建它们。其实这些.d 文件也

是一些可被make解析的makefile文件。 

sinclude ”来代替“-include 

阅读(1283) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~