原文:http://lsec.cc.ac.cn/%7Epeace/articles/gnumaketranslated.html
GNU Make 使用手册(中译版)
翻译:于凤昌
译者注:本人在阅读Linux源代码过程中发现如果要全面了解Linux的结构、理解Linux的编程总体设计及思想必须首先全部读通Linux源代码中各级的Makefile文件。目前,在网上虽然有一些著作,但都不能全面的解释Linux源代码中各级的Makefile文件,因此本人认真阅读了GNU Make 使用手册(3.79)版原文,在此基础上翻译了该手册,以满足对Linux源代码有兴趣或者希望采用GCC编写程序但对缺乏GNU Make全面了解之人士的需要。本人是业余爱好不是专业翻译人士,如果有问题请通过电子信箱与我联系共同商讨,本人的E-mail为:yfc70@public2.lyptt.ha.cn 。注意在文章中出现的斜体加粗字表示章节。
GNU make
Version 3.79
April 2000
Richard M. Stallman and Roland McGrath
目录
1 make概述
1.1 怎样阅读本手册
1.2 问题和BUG
2.1 规则的格式
2.5让make推断命令
2.7在目录中删除文件的规则
3.4变量MAKEFILES
4 编写规则
4.1规则的语法
4.2在文件名中使用通配符
4.2.1通配符例子
4.2.2使用通配符的常见错误
4.2.3函数wildcard
4.3在目录中搜寻依赖
4.3.1VPATH:所有依赖的搜寻路径
4.3.2vpath指令
4.3.3目录搜寻过程
4.3.4编写搜寻目录的shell命令
4.3.5目录搜寻和隐含规则
4.3.6连接库的搜寻目录
4.4假想目标
4.5没有命令或依赖的规则
4.6使用空目录文件记录事件
4.7内建的特殊目标名
4.8具有多个目标的规则
4.9具有多条规则的目标
4.10静态格式规则
4.10.1静态格式规则的语法
4.10.2静态格式规则和隐含规则
4.11双冒号规则
4.12自动生成依赖
5 在规则中使用命令
5.1命令回显
5.2执行命令
5.3并行执行
5.4命令错误
5.5中断或关闭make
5.6递归调用make
5.6.1变量MAKE的工作方式
5.6.2与子make通讯的变量
5.6.3与子make通讯的选项
5.7定义固定次序命令
5.8使用空命令
6 使用变量
6.1变量引用基础
6.2变量的两个特色
6.3变量高级引用技术
6.3.1替换引用
6.3.2嵌套变量引用
6.4变量取值
6.5设置变量
6.6为变量值追加文本
6.7override指令
6.8定义多行变量
6.9环境变量
6.10特定目标变量的值
6.11特定格式变量的值
7.1条件语句的例子
7.2条件语句的语法
7.3测试标志的条件语句
8 文本转换函数
8.1函数调用语法
8.2字符串替换和分析函数
8.3文件名函数
8.4函数foreach
8.5函数if
8.6函数call
8.7函数origin
8.8函数shell
8.9控制Make的函数
9 运行make
9.2指定最终目标的参数
9.3代替执行命令
9.4避免重新编译文件
9.5变量重载
9.6测试编译程序
9.7选项概要
10 使用隐含规则
10.1使用隐含规则
10.2隐含规则目录
10.3隐含规则使用的变量
10.4隐含规则链
10.5定义与重新定义格式规则
10.5.1格式规则简介
10.5.2格式规则的例子
10.5.3自动变量
10.5.4格式匹配
10.5.5万用规则
10.5.6删除隐含规则
10.6定义最新类型的缺省规则
10.7过时的后缀规则
10.8隐含规则搜寻算法
11 使用make更新档案文件
11.1档案成员目标
11.2档案成员目标的隐含规则
11.2.1更新档案成员的符号索引表
11.3使用档案的危险
11.4档案文件的后缀规则
12 GNU make的特点
13 不兼容性和失去的特点
14 Makefile文件惯例
14.1makefile文件的通用惯例
14.2makefile文件的工具
14.3指定命令的变量
14.4安装路径变量
14.5用户标准目标
14.6安装命令分类
15快速参考
附录 名词翻译对照表
1 Make 概述
Make 可自动决定一个大程序中哪些文件需要重新编译,并发布重新编译它们的命令。本版本GNU Make使用手册由Richard M. Stallman and Roland McGrath编著,是从Paul D. Smith撰写的V3.76版本发展过来的。
GNU Make符合IEEE Standard 1003.2-1992 (POSIX.2) 6.2章节的规定。
因为C语言程序更具有代表性,所以我们的例子基于C语言程序,但Make并不是仅仅能够处理C语言程序,它可以处理那些编译器能够在Shell命令下运行的的各种语言的程序。事实上,GNU Make不仅仅限于程序,它可以适用于任何如果一些文件变化导致另外一些文件必须更新的任务。
如果要使用Make,必须先写一个称为Makefile的文件,该文件描述程序中各个文件之间的相互关系,并且提供每一个文件的更新命令。在一个程序中,可执行程序文件的更新依靠OBJ文件,而OBJ文件是由源文件编译得来的。
一旦合适的Makefile文件存在,每次更改一些源文件,在shell命令下简单的键入:
make
就能执行所有的必要的重新编译任务。Make程序根据Makefile文件中的数据和每个文件更改的时间戳决定哪些文件需要更新。对于这些需要更新的文件,Make基于Makefile文件发布命令进行更新,进行更新的方式由提供的命令行参数控制。具体操作请看运行Make章节。
1.1怎样阅读本手册
如果您现在对Make一无所知或者您仅需要了解对make 的普通性介绍,请查阅前几章内容,略过后面的章节。前几章节是普通介绍性内容,后面的章节是具体的专业、技术内容。
如果您对其它Make程序十分熟悉,请参阅GNU Make的特点和不兼容性和失去的特点部分,GNU Make的特点这一章列出了GNU Make对make程序的扩展,不兼容和失去的特点一章解释了其它Make程序有的特征而GNU Make缺乏的原因。
对于快速浏览者,请参阅选项概要、快速参考和内建的特殊目标名部分。
1.2问题和BUG
如果您有关于GNU Make的问题或者您认为您发现了一个BUG,请向开发者报告;我们不能许诺我们能干什么,但我们会尽力修正它。在报告BUG之前,请确定您是否真正发现了BUG,仔细研究文档后确认它是否真的按您的指令运行。如果文档不能清楚的告诉您怎么做,也要报告它,这是文档的一个BUG。
在您报告或者自己亲自修正BUG之前,请把它分离出来,即在使问题暴露的前提下尽可能的缩小Makefile文件。然后把这个Makefile文件和Make给出的精确结果发给我们。同时请说明您希望得到什么,这可以帮助我们确定问题是否出在文档上。
一旦您找到一个精确的问题,请给我们发E-mail,我们的E-mail地址是:
在邮件中请包含您使用的GNU Make的版本号。您可以利用命令‘make--version’得到版本号。同时希望您提供您的机器型号和操作系统类型,如有可能的话,希望同时提供config.h文件(该文件有配置过程产生)。
2 Makefile文件介绍
Make程序需要一个所谓的Makefile文件来告诉它干什么。在大多数情况下,Makefile文件告诉Make怎样编译和连接成一个程序。
本章我们将讨论一个简单的Makefile文件,该文件描述怎样将8个C源程序文件和3个头文件编译和连接成为一个文本编辑器。Makefile文件可以同时告诉Make怎样运行所需要的杂乱无章的命令(例如,清除操作时删除特定的文件)。如果要看更详细、复杂的Makefile文件例子,请参阅复杂的Makefile文件例子一章。
当Make重新编译这个编辑器时,所有改动的C语言源文件必须重新编译。如果一个头文件改变,每一个包含该头文件的C语言源文件必须重新编译,这样才能保证生成的编辑器是所有源文件更新后的编辑器。每一个C语言源文件编译后产生一个对应的OBJ文件,如果一个源文件重新编译,所有的OBJ文件无论是刚刚编译得到的或原来编译得到的必须从新连接,形成一个新的可执行文件。
2.1 规则的格式
一个简单的Makefile文件包含一系列的“规则”,其样式如下:
目标(target)…: 依赖(prerequiries)…
<tab>命令(command)
…
…
目标(target)通常是要产生的文件的名称,目标的例子是可执行文件或OBJ文件。目标也可是一个执行的动作名称,诸如‘clean’(详细内容请参阅假想目标一节)。
依赖是用来输入从而产生目标的文件,一个目标经常有几个依赖。
命令是Make执行的动作,一个规则可以含有几个命令,每个命令占一行。注意:每个命令行前面必须是一个Tab字符,即命令行第一个字符是Tab。这是不小心容易出错的地方。
通常,如果一个依赖发生变化,则需要规则调用命令对相应依赖和服务进行处理从而更新或创建目标。但是,指定命令更新目标的规则并不都需要依赖,例如,包含和目标‘clern’相联系的删除命令的规则就没有依赖。
规则一般是用于解释怎样和何时重建特定文件的,这些特定文件是这个详尽规则的目标。Make需首先调用命令对依赖进行处理,进而才能创建或更新目标。当然,一个规则也可以是用于解释怎样和何时执行一个动作,详见编写规则一章。
一个Makefile文件可以包含规则以外的其它文本,但一个简单的Makefile文件仅仅需要包含规则。虽然真正的规则比这里展示的例子复杂,但格式却是完全一样。
2.2一个简单的Makefile文件
一个简单的Makefile文件,该文件描述了一个称为文本编辑器(edit)的可执行文件生成方法,该文件依靠8个OBJ文件(.o文件),它们又依靠8个C源程序文件和3个头文件。
在这个例子中,所有的C语言源文件都包含‘defs.h’ 头文件,但仅仅定义编辑命令的源文件包含‘command.h’头文件,仅仅改变编辑器缓冲区的低层文件包含‘buffer.h’头文件。
edit : main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o
我们把每一个长行使用反斜杠-新行法分裂为两行或多行,实际上它们相当于一行,这样做的意图仅仅是为了阅读方便。
使用Makefile文件创建可执行的称为‘edit’的文件,键入:make
使用Makefile文件从目录中删除可执行文件和目标,键入:make clean
在这个Makefile文件例子中,目标包括可执行文件‘edit’和OBJ文件‘main.o’及‘kdb.o’。依赖是C语言源文件和C语言头文件如‘main.c’和‘def.h’等。事实上,每一个OBJ文件即是目标也是依赖。所以命令行包括‘cc -c main.c’和‘cc -c kbd.c’。
当目标是一个文件时,如果它的任一个依赖发生变化,目标必须重新编译和连接。任何命令行的第一个字符必须是‘Tab’字符,这样可以把Makefile文件中的命令行与其它行分别开来。(一定要牢记:Make并不知道命令是如何工作的,它仅仅能向您提供保证目标的合适更新的命令。Make的全部工作是当目标需要更新时,按照您制定的具体规则执行命令。)
目标‘clean’不是一个文件,仅仅是一个动作的名称。正常情况下,在规则中‘clean’这个动作并不执行,目标‘clean’也不需要任何依赖。一般情况下,除非特意告诉make执行‘clean’命令,否则‘clean’命令永远不会执行。注意这样的规则不需要任何依赖,它们存在的目的仅仅是执行一些特殊的命令。象这些不需要依赖仅仅表达动作的目标称为假想目标。详细内容参见假想目标;参阅命令错误可以了解rm或其它命令是怎样导致make忽略错误的。
2.3 make处理makefile文件的过程
缺省情况下,make开始于第一个目标(假想目标的名称前带‘.’)。这个目标称为缺省最终目标(即make最终更新的目标,具体内容请看指定最终目标的参数一节)。
在上节的简单例子中,缺省最终目标是更新可执行文件‘edit’,所以我们将该规则设为第一规则。这样,一旦您给出命令:
make
make就会读当前目录下的makefile文件,并开始处理第一条规则。在本例中,第一条规则是连接生成‘edit’,但在make全部完成本规则工作之前,必须先处理‘edit’所依靠的OBJ文件。这些OBJ文件按照各自的规则被处理更新,每个OBJ文件的更新规则是编译其源文件。重新编译根据其依靠的源文件或头文件是否比现存的OBJ文件更‘新’,或者OBJ文件是否存在来判断。
其它规则的处理根据它们的目标是否和缺省最终目标的依赖相关联来判断。如果一些规则和缺省最终目标无任何关联则这些规则不会被执行,除非告诉Make强制执行(如输入执行make clean命令)。
在OBJ文件重新编译之前,Make首先检查它的依赖C语言源文件和C语言头文件是否需要更新。如果这些C语言源文件和C语言头文件不是任何规则的目标,make将不会对它们做任何事情。Make也可以自动产生C语言源程序,这需要特定的规则,如可以根据Bison或Yacc产生C语言源程序。
在OBJ文件重新编译(如果需要的话)之后,make决定是否重新连接生成edit可执行文件。如果edit可执行文件不存在或任何一个OBJ文件比存在的edit可执行文件‘新’,则make重新连接生成edit可执行文件。
这样,如果我们修改了‘insert.c’文件,然后运行make,make将会编译‘insert.c’文件更新‘insert.o’文件,然后重新连接生成edit可执行文件。如果我们修改了‘command.h’文件,然后运行make,make将会重新编译‘kbd.o’和‘command.o’文件,然后重新连接生成edit可执行文件。
2.4使用变量简化makefile文件
在我们的例子中,我们在‘edit’的生成规则中把所有的OBJ文件列举了两次,这里再重复一遍:
edit : main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o
这样的两次列举有出错的可能,例如在系统中加入一个新的OBJ文件,我们很有可能在一个需要列举的地方加入了,而在另外一个地方却忘记了。我们使用变量可以简化makefile文件并且排除这种出错的可能。变量是定义一个字符串一次,而能在多处替代该字符串使用(具体内容请阅读使用变量一节)。
在makefile文件中使用名为objects
, OBJECTS
, objs
, OBJS
, obj
, 或 OBJ的变量代表所有OBJ文件已是约定成俗。
在这个makefile文件我们定义了名为objects的变量,其定义格式如下:
objects = main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o
然后,在每一个需要列举OBJ文件的地方,我们使用写为
`$(objects)'形式的变量代替(具体内容请阅读使用变量一节)。下面是使用变量后的完整的makefile文件:
objects = main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit $(objects)
2.5 让make推断命令
编译单独的C语言源程序并不需要写出命令,因为make可以把它推断出来:make有一个使用‘CC –c’命令的把C语言源程序编译更新为相同文件名的OBJ文件的隐含规则。例如make可以自动使用‘cc -c main.c -o main.o’命令把‘main.c’编译 ‘main.o’。因此,我们可以省略OBJ文件的更新规则。详细内容请看使用隐含规则一节。
如果C语言源程序能够这样自动编译,则它同样能够自动加入到依赖中。所以我们可在依赖中省略C语言源程序,进而可以省略命令。下面是使用隐含规则和变量objects的完整makefile文件的例子:
objects = main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h
.PHONY : clean
clean :
-rm edit $(objects)
这是我们实际编写makefile文件的例子。(和目标‘clean’联系的复杂情况在别处阐述。具体参见假想目标及命令错误两节内容。)因为隐含规则十分方便,所以它们非常重要,在makefile文件中经常使用它们。
2.6 另一种风格的makefile文件
当时在makefile文件中使用隐含规则创建OBJ文件时,采用另一种风格的makefile文件也是可行的。在这种风格的makefile文件中,可以依据依赖分组代替依据目标分组。下面是采用这种风格的makefile文件:
objects = main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
$(objects) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h
这里的defs.h是所有OBJ文件的共同的一个依赖;command.h和bufffer.h是具体列出的OBJ文件的共同依赖。
虽然采用这种风格编写makefile文件更具风味:makefile文件更加短小,但一部分人以为把每一个目标的信息放到一起更清晰易懂而不喜欢这种风格。
2.7 在目录中删除文件的规则
编译程序并不是编写make规则的唯一事情。Makefile文件可以告诉make去完成编译程序以外的其它任务,例如,怎样删除OBJ文件和可执行文件以保持目录的‘干净’等。下面是删除利用make规则编辑器的例子:
clean:
rm edit $(objects)
在实际应用中,应该编写较为复杂的规则以防不能预料的情况发生。更接近实用的规则样式如下:
.PHONY : clean
clean :
-rm edit $(objects)
这样可以防止make因为存在名为’clean’的文件而发生混乱,并且导致它在执行rm命令时发生错误(具体参见假想目标及命令错误两节内容)。
诸如这样的规则不能放在makefile文件的开始,因为我们不希望它变为缺省最终目标。应该象我们的makefile文件例子一样,把关于edit的规则放在前面,从而把编译更新edit可执行程序定为缺省最终目标。
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
如果您特别为目标