Chinaunix首页 | 论坛 | 博客
  • 博客访问: 186539
  • 博文数量: 30
  • 博客积分: 2500
  • 博客等级: 少校
  • 技术积分: 526
  • 用 户 组: 普通用户
  • 注册时间: 2008-02-19 10:07
文章分类

全部博文(30)

文章存档

2009年(1)

2008年(29)

我的朋友

分类: LINUX

2008-03-12 10:50:17

 Makefile的规则

本章我们将讨论Makefile的一个重要内容,规则。熟悉规则对于书写Makefile至关重要。Makefile中,规则描述了在何种情况下使用什么命令来重建一个特定的文件,此文件被称为规则目标(通常规则中的目标只有一个)。规则中出目标之外的罗列的其它文件称为目标的依赖,而规则的命令是用来更新或者创建此规则的目标。

除了makefile终极目标所在的规则以外,其它规则的顺序在makefile文件中没有意义。终极目标就是当没有使用make 命令行指定具体目标时,make默认的更新的哪一个目标,它是makefile文件中第一个规则的目标。如果在makefile中第一个规则有多个目标的话,那么多个目标中的第一个将会被作为make终极目标有两种情况的例外:1. 目标名以点号“.”开始的并且其后不存在斜线“/”“./”被认为是当前目录;“../”被认为是上一级目录) (.o.c)

2. 模式规则的目标%.o:%.c)。当这两种目标所在的规则是Makefile的第一个规则时,它们并不会被作为终极目标

终极目标是执行make的唯一目的,其所在的规则作为第一个被执行的规则。而其它的规则是在完成重建终极目标的过程中被连带出来的。所以这些目标所在规则在Makefile中的顺序无关紧要。

因此,我们书写的makefile的第一个规则应该就是重建整个程序或者多个程序的依赖关系和执行命令的描述。

一个例子

我们来看一个规则的例子:

 

foo.o : foo.c defs.h       # module for twiddling the frobs

cc -c -g foo.c

 

这是一个典型的规则。看到这个例子,大家应该能够说出这个规则的各个部分之间的关系。不过我们还是要把这个例子拿出来讨论。目的是让我们更加明确地理解Makefile的规则。本例第一行中,文件“foo.o”是规则需要重建的文件,而“foo.c”“defs.h”是重建“foo.o”所要使用的文件。我们把规则所需要重建的文件称为规则的目标foo.o),而把重新目标所需要的文件称为规则的依赖(或者目标的依赖)。规则中的第二行cc -c -g foo.c是规则的命令。它描述了如何使用规则中的依赖文件重建目标。

而且,上面的规则告诉我们了两件事:

1.        如何确定目标文件是否过期(需要重建目标),过期是指目标文件不存在或者目标文件“foo.o”在时间戳上比依赖文件中的任何一个(“foo.c”或者“defs.h”

2.        如何重建目标文件“foo.o”。这个规则中使用cc编译器。规则的命令中没有明确的使用到依赖文件“defs.h”我们假设在源文件“foo.c”中已经包含了此头文件。这也是为什么它作为目标依赖出现的原因。

规则语法

通常规则的语法格式如下:

 

TARGETS : PREREQUISITES

COMMAND

...

 

或者:

 

TARGETS : PREREQUISITES ; COMMAND

COMMAND

...

 

规则中TARGETS可以是空格分开的多个文件名,也可以是一个标签(例如:执行清空的“clean”)。TARGETS的文件名可以使用通配符,格式“A(M)”表示档案文件(Linux下的静态库.a文件)的成员“M”(关于静态库的重建可参考 第十一章 使用make更新静态库文件)。通常规则只有一个目标文件(建议这么做),偶尔会在一个规则中需要多个目标。

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

1.        规则的命令部分有两种书写方式:a. 命令可以和目标,依赖描述放在同一行。命令在依赖文件列表后并使用分号(;)和依赖文件列表分开。b. 命令在目标,依赖的描述的下一行,作为独立的命令行。当作为独立的命令行时此行必须以[Tab]字符开始。Makefile中,在第一个规则之后出现的所有以[Tab]字符开始的行都会被当作命令来处理。

2.        Makefile中符号“$”有特殊的含义(表示变量或者函数的引用),在规则中需要使用符号“$”的地方,需要书写两个连续的(“$$”)。

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

一个规则告诉“make”两件事:1. 目标在什么情况下已经过期; 2. 如果需要重建目标时,如何去重建这个目标。目标是否过期是由那些使用空格分割的规则的依赖文件所决定的。当目标文件不存在或者目标文件的最后修改时间比依赖文件中的任何一个晚时,目标就会被创建或者重建。就是说执行规则命令行的前提条件是以下两者之一:1. 目标文件不存在; 2. 目标文件存在,但是规则的依赖文件中存在一个依赖的最后修改时间比目标的最后修改时间晚。

规则的中心思想是:目标文件的内容是由依赖文件决定,依赖文件的任何一处改动,将导致目前已经存在的目标文件的内容过期。规则的命令为重建目标提供了方法。这些命令运行在系统shell之上。

依赖的类型

GNU make的规则中可以使用两种不同类型的依赖:1. 以前章节所提到的规则中使用的是常规依赖,这是书写Makefile规则时最常用的一种。2. 另外一种在我们书写Makefile时不会经常使用,它比较特殊、称之为“order-only”依赖。一个规则的常规依赖(通常是多个依赖文件)表明了两件事:首先,它决定了重建此规则目标所要执行规则(确切的说是执行命令)的顺序;表明在更新这个规则的目标(执行此规则的命令行)之前需要按照什么样的顺序、执行哪些规则(命令)来重建这些依赖文件(对所有依赖文件的重建,使用明确或者隐含规则。就是说对于这样的规则:A:B C,那么在重建目标A之前,首先需要完成对它的依赖文件BC的重建。重建BC的过程就是执行Makefile中以文件BC为目标的规则)。其次,它确定了一个依存关系;规则中如果依赖文件中的任何一个比目标文件新,则认为规则的目标已经过期而需要重建目标文件。

通常,如果规则中依赖文件中的任何一个被更新,则规则的目标相应地也应该被更新。

有时,需要定义一个这样的规则,在更新目标(目标文件已经存在)时只需要根据依赖文件中的部分来决定目标是否需要被重建,而不是在依赖文件的任何一个被修改后都重建目标。为了实现这一目的,相应的就需要对规则的依赖进行分类,一类是在这些依赖文件被更新后,需要更新规则的目标另一类是更新这些依赖的,可不需要更新规则的目标我们把第二类称为:“order-only”依赖书写规则时,order-only依赖使用管道符号“|”开始,作为目标的一个依赖文件。规则依赖列表中管道符号“|”左边的是常规依赖,管道符号右边的就是“order-only”依赖。这样的规则书写格式如下:

 

TARGETS : NORMAL-PREREQUISITES | ORDER-ONLY-PREREQUISITES

 

这样的规则中常规依赖文件可以是空;同样也可以对一个目标进行多次追加依赖。需要注意:规则依赖文件列表中如果一个文件同时出现在常规列表和“order-only”列表中,那么此文件被作为常规依赖处理(因为常规依赖所实现的动作是“order-only”依赖所实现的动作的一个超集)。

“order-only”依赖的使用举例:

    LIBS = libtest.a

foo : foo.c | $(LIBS)

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

make在执行这个规则时,如果目标文件“foo”已经存在。当“foo.c”被修改以后,目标“foo”将会被重建,但是当“libtest.a”被修改以后。将不执行规则的命令来重建目标“foo”

就是说,规则中依赖文件$(LIBS)只有在目标文件不存在的情况下,才会参与规则的执行。当目标文件存在时此依赖不会参与规则的执行过程。

文件名使用通配符

Makefile中表示文件名时可使用通配符。可使用的通配符有:“*”“?”“[…]”。在Makefile中通配符的用法和含义和Linuxunix)的Bourne shell完全相同。例如,“*.c”代表了当前工作目录下所有的以“.c”结尾的文件等。但是在Makefile中这些通配符并不是可以用在任何地方,Makefile中通配符可以出现在以下两种场合:

1.        可以用在规则的目标、依赖中,make在读取Makefile时会自动对其进行匹配处理(通配符展开);

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

除这两种情况之外的其它上下文中,不能直接使用通配符。而是需要通过函数“wildcard”来实现。

如果规则的一个文件名包含统配字符(“*”“.”等字符),在使用这样的文件时需要对文件名中的统配字符使用反斜线(\)进行转义处理。例如“foo\*bar”,在Makefile中它表示了文件“foo*bar”Makefile中对一些特殊字符的转义和B-SHELL以及C语言中的基本上相同。

另外需要注意:在Linuxunix)中,以波浪线“~”开始的文件名有特殊含义。单独使用它或者其后跟一个斜线(~/),代表了当前用户的宿主目录(在shell下可以通过命令“echo ~(~\)”来查看)。例如“~/bin”代表“/home/username/bin/”(当前用户宿主目录下的bin目录)。波浪线之后跟一个单词(~word),代表由这个“word”所指定的用户的宿主目录。例如“~john/bin”就是代表用户john的宿主目录下的bin目录。

在一些系统中(像MS-DOSMS-Windows),用户没有各自的宿主目录,此情况下可通过设置环境变量“HOME”来模拟。

4.4.1 通配符使用举例

本节开始已经提到过,通配符可被用在规则的命令中,它是在命令被执行时由shell进行处理。例如Makefile的清空过程文件规则:

 

clean:

rm -f *.o

 

通配符也可以用在规则的依赖文件名中。看看下面这个例子。执行“make print”,执行的结果是打印当前工作目录下所有的在上一次打印以后被修改过的“.c”文件。

 

print: *.c

lpr -p $?

touch print

 

两点说明:1. 上述的规则中目标“print”时一个空目标文件。(当前目录下存在一个文件“print”,但我们不关心它的实际内容,此文件的作用只是记录最后一次执行此规则的时间2. 自动环变量“$?”在这里表示依赖文件列表中被改变过的所有文件。

变量定义中使用的通配符不会被统配处理(因此在变量定义中不能使用通配符,否则在某些情况下会出现非预期的结果,下一小节将会详细讨论)。在Makefile有这样一个变量定义:objects = *.o。它表示变量“objects”的值是字符串“*.o”(并不是期望的空格分开的.o文件列表)。当需要变量“objects”代表所有.o文件列表示时,需要使用函数“wildcard”objects = $(wildcar *.o))。

4.4.2 通配符存在的缺陷

在上一小节提到过变量定义时使用通配符可能在某些情况下会导致意外的结果。本小节将对此进行详细地分析和讨论。书写Makefile时,可能存在这种不正确的使用通配符的方法。这种看似正确的方式产生的结果可能产生非期望的结果。例如在你的Makefile中,期望能够根据所有的.o文件生成可执行文件“foo”。实现如下:

 

objects = *.o

 

foo : $(objects)

cc -o foo $(CFLAGS) $(objects)

 

这里变量“objects”的值是一个字符串“*.o”。在重建“foo”的规则中对变量“objects”进行展开,目标“foo”的依赖就是“*.o”,即所有的.o文件的列表。如果在工作目录下已经存在必需的.o文件,那么这些.o文件将成为目标的依赖文件,目标“foo”将根据规则被重建。

但是如果将工作目录下所有的.o文件删除,重新执行make将会得到一个类似于没有创建*.o文件的规则的错误提示。这当然不是我们所期望的结果(可能在出现这个错误时会令你感到万分迷惑!)。为了达到我们的初衷,在对变量进行定义的时需要使用一些高级的技巧,包括使用“wildcard”函数(变量定义为“objects=$(wildcard *.o)”)和实现字符串的置换。如何实现字符串的置换,后续将进行详细地讨论。

4.4.3  函数wildcard

之前提到过,在规则中,通配符会被自动展开。但在变量的定义和函数引用时,通配符将失效。这种情况下如果需要通配符有效,就需要使用函数“wildcard”,它的用法是:$(wildcard PATTERN...) Makefile中,它被展开为已经存在的、使用空格分开的、匹配此模式的所有文件列表。如果不存在任何符合此模式的文件,函数会忽略模式字符并返回空。需要注意的是:这种情况下规则中通配符的展开和上一小节匹配通配符的区别。

一般我们可以使用“$(wildcard *.c)”来获取工作目录下的所有的.c文件列表。复杂一些用法;可以使用$(patsubst %.c,%.o,$(wildcard *.c)),首先使用“wildcard”函数获取工作目录下的.c文件列表;之后将列表中所有文件名的后缀.c替换为.o。这样我们就可以得到在当前目录可生成的.o文件列表。因此在一个目录下可以使用如下内容的Makefile来将工作目录下的所有的.c文件进行编译并最后连接成为一个可执行文件:

 

#sample Makefile

objects := $(patsubst %.c,%.o,$(wildcard *.c))

 

foo : $(objects)

cc -o foo $(objects)

 

这里我们使用了make的隐含规则来编译.c的源文件。对变量的赋值也用到了一个特殊的符号(:=)。

阅读(2487) | 评论(0) | 转发(0) |
0

上一篇:没有了

下一篇:第四章:Makefile的规则(二)

给主人留下些什么吧!~~