GNU Make 使用手册(中译版) 第1部分

来源:互联网 发布:淘宝推广方案怎么写 编辑:程序博客网 时间:2024/05/14 11:02

原文链接:http://blog.chinaunix.net/resserver.php?blogId=2652&resource=gunmake.htm


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 Makefile文件介绍

2.1 规则的格式

2.2一个简单的Makefile文件

2.3make处理Makefile文件的过程

2.4使用变量简化Makefile文件

2.5让make推断命令

2.6另一种风格的Makefile文件

2.7在目录中删除文件的规则

3         编写Makefile文件

3.1Makefile文件的内容

3.2Makefile文件的命名

3.3包含其它的Makefile文件

3.4变量MAKEFILES

3.5Makefile文件重新生成的过程

3.6重载其它Makefile文件

3.7make读取Makefile文件的过程

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.6.4`--print-directory'选项

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 Makefile文件的条件语句

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的函数

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地址是:

bug-make@gnu.org

在邮件中请包含您使用的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

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

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

一些用户喜欢在登录时自动设置临时的环境变量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) ___FCKpd___132lt; -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) ___FCKpd___202lt; -o $@

这里‘$<’是自动变量,控制依赖的名称,‘$@’也是自动变量,掌握目标的名称。详细内容参阅自动变量

每一个指定目标必须和目标格式匹配,如果不符则产生警告。如果您有一列文件,仅有其中的一部分和格式匹配,您可以使用filter函数把不符合的文件移走(参阅字符串替代和分析函数):

files = foo.elc bar.o lose.o
 
$(filter %.o,$(files)): %.o: %.c
        $(CC) -c $(CFLAGS) ___FCKpd___206lt; -o $@
$(filter %.elc,$(files)): %.elc: %.el
        emacs -f batch-byte-compile ___FCKpd___208lt;

在这个例子中,$(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) ___FCKpd___215lt; /
                  | 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文件的重新生成过程

5在规则中使用命令

规则中的命令由一系列shell命令行组成,它们一条一条的按顺序执行。除第一条命令行可以分号为开始附属在目标-依赖行后面外,所有的命令行必须以TAB开始。空白行与注释行可在命令行中间出现,处理时它们被忽略。(但是必须注意,以TAB开始的‘空白行’不是空白行,它是空命令,参阅使用空命令。)

用户使用多种不同的shell程序,如果在makefile文件中没有指明其它的shell,则使用缺省的‘/bin/sh’解释makefile文件中的命令。参阅命令执行

使用的shell种类决定了是否能够在命令行上写注释以及编写注释使用的语法。当使用‘/bin/sh’作为shell,以‘#’开始的注释一直延伸到该行结束。‘#’不必在行首,而且‘#’不是注释的一部分。

5.1 命令回显

正常情况下make在执行命令之前首先打印命令行,我们因这样可将您编写的命令原样输出故称此为回显。

以‘@’起始的行不能回显,‘@’在传输给shell时被丢弃。典型的情况,您可以在makefile文件中使用一个仅仅用于打印某些内容的命令,如echo命令来显示makefile文件执行的进程:

@echo About to make distribution files

当使用make时给出‘-n’或‘--just-print’标志,则仅仅回显命令而不执行命令。参阅选项概要。在这种情况下也只有在这种情况下,所有的命令行都回显,即使以‘@’开始的命令行也回显。这个标志对于在不使用命令的情况下发现make认为哪些是必要的命令非常有用。

-s’或‘--silent’标志可以使make阻止所有命令回显,好像所有的行都以‘@’开始一样。在makefile文件中使用不带依赖的特别目标‘.SILENT’的规则可以达到相同的效果(参阅内建的特殊目标名)。因为‘@’使用更加灵活以至于现在已基本不再使用特别目标.SILENT。

5.2执行命令

需要执行命令更新目标时,每一命令行都会使用一个独立的子shell环境,保证该命令行得到执行。(实际上,make可能走不影响结果的捷径。)

请注意这意味着设置局部变量的shell命令如cd等将不影响紧跟着的命令行;如果您需要使用cd命令影响到下一个命令,请把这两个命令放到一行,它们中间用分号隔开,这样make将认为它们是一个单一的命令行,把它们放到一起传递给shell,然后按顺序执行它们。例如:

foo : bar/lose
        cd bar; gobble lose > ../foo

如果您喜欢将一个单一的命令分割成多个文本行,您必须用反斜杠作为每一行的结束,最后一行除外。这样,多个文本行通过删除反斜杠按顺序组成一新行,然后将它传递给shell。如此,下面的例子和前面的例子是等同的:

foo : bar/lose
        cd bar;  /
        gobble lose > ../foo

用作shell的程序是由变量SHELL指定,缺省情况下,使用程序‘/bin/sh’作为shell。

MS_DOS上运行,如果变量SHELL没有指定,变量COMSPEC的值用来代替指定shell。

MS_DOS上运行和在其它系统上运行,对于makefile文件中设置变量SHELL的行的处理也不一样。因为MS_DOS的shell,‘command.com’,功能十分有限,所以许多make用户倾向于安装一个代替的shell。因此,在MS_DOS上运行,make检测变量SHELL的值,并根据它指定的Unix风格或DOS风格的shell变化它的行为。即使使用变量SHELL指向‘command.com’ ,make依然检测变量SHELL的值。

如果变量SHELL指定Unix风格的shell,在MS_DOS上运行的make将附加检查指定的shell是否能真正找到;如果不能找到,则忽略指定的shell。在MS_DOS上,GNU make按照下述步骤搜寻shell:

1、在变量SHELL指定的目录中。例如,如果makefile指明`SHELL = /bin/sh'make将在当前路径下寻找子目录‘/bin’。

2、在当前路径下。

3、按顺序搜寻变量PATH指定的目录。

在所有搜寻的目录中,make首先寻找指定的文件(如例子中的‘sh’)。如果该文件没有存在,make将在上述目录中搜寻带有确定的可执行文件扩展的文件。例如:.exe', .com', .bat', .btm', .sh'文件和其它文件等。

如果上述过程中能够成功搜寻一个shell,则变量SHELL的值将设置为所发现shell的全路径文件名。然而如果上述努力全部失败,变量SHELL的值将不改变,设置shell的行的有效性将被忽略。这是在make运行的系统中如果确实安装了Unix风格的shell,make仅支持指明的Unix风格shell特点的原因。

注意这种对shell的扩展搜寻仅仅限制在makefile文件中设置变量SHELL的情况。如果在环境或命令行中设置,希望您指定shell的全路径文件名,而且全路径文件名需象在Unix系统中运行的一样准确无误。

经过上述的DOS特色的处理,而且您还把 ‘sh.exe’安装在变量PATH指定的目录中,或在makefile文件内部设置`SHELL = /bin/sh' (和多数Unix的makefile文件一样),则在MS_DOS上的运行效果和在Unix上运行完全一样。

不像其它大多数变量,变量SHELL从不根据环境设置。这是因为环境变量SHELL是用来指定您自己选择交互使用的shell程序。如果变量SHELL在环境中设置,它将影响makefile文件的功能,这是非常不划算的,参阅环境变量。然而在MS-DOS和MS-WINDOWS中在环境中设置变量SHELL的值是要使用的,因为在这些系统中,绝大多数用户并不设置该变量的值,所以make很可能特意为该变量指定要使用的值。在MS-DOS上,如果变量SHELL的设置对于make不合适,您可以设置变量MAKESHELL用来指定make使用的shell;这种设置将使变量SHELL的值失效。

5.3 并行执行

GNU make可以同时执行几条命令。正常情况下,make一次执行一个命令,待它完成后在执行下一条命令。然而,使用‘-j’和‘--jobs’选项将告诉make同时执行多条命令。在MS-DOS上,‘-j’选项没有作用,因为该系统不支持多进程处理。

如果‘-j’选项后面跟一个整数,该整数表示一次执行的命令的条数;这称为job slots数。如果‘-j’选项后面没有整数,也就是没有对job slots的数目限制。缺省的job slots数是一,这意味着按顺序执行(一次执行一条命令)。同时执行多条命令的一个不太理想的结果是每条命令产生的输出与每条命令发送的时间对应,即命令产生的消息回显可能较为混乱。

另一个问题是两个进程不能使用同一设备输入,所以必须确定一次只能有一条命令从终端输入,make只能保证正在运行的命令的标准输入流有效,其它的标准输入流将失效。这意味着如果有几个同时从标准输入设备输入的话,对于绝大多数子进程将产生致命的错误(即产生一个‘Broken pipe’信号)。

命令对一个有效的标准输入流(它从终端输入或您为make改造的标准输入设备输入)的需求是不可预测的。第一条运行的命令总是第一个得到标准输入流,在完成一条命令后第一条启动的另一条命令将得到下一个标准输入流,等等。

如果我们找到一个更好替换方案,我们将改变make的这种工作方式。在此期间,如果您使用并行处理的特点,您不应该使用任何需要标准输入的命令。如果您不使用该特点,任何需要标准输入的命令将都能正常工作。

最后,make的递归调用也导致出现问题。更详细的内容参阅与子make通讯的选项

如果一个命令失败(被一个信号中止,或非零退出),且该条命令产生的错误不能忽略(参阅命令错误),剩余的构建同一目标的命令行将会停止工作。如果一条命令失败,而且‘-k’或‘--keep-going’选项也没有给出(参阅选项概要),make将放弃继续执行。如果make由于某种原因(包括信号)要中止,此时又子进程正在运行,它将等到这些子进程结束之后再实际退出。

当系统正满负荷运行时,您或许希望在负荷轻的时再添加任务。这时,您可以使用‘-|’选项告诉make根据平均负荷限制同一时刻运行的任务数量。‘-|’或‘--max-load’选项一般后跟一个浮点数。例如:

-| 2.5

将不允许make在平均负荷高于2.5时启动一项任务。‘-|’选项如果没有跟数据,则取消前面‘-|’给定的负荷限制。

更精确地讲,当make启动一项任务时,而它此时已经有至少一项任务正在运行,则它将检查当前的平均负荷;如果不低于‘-|’选项给定的负荷限制时,make将等待直到平均负荷低于限制或所有其它任务完成后再启动其它任务。

缺省情况下没有负荷限制。

5.4命令错误

在每一个shell命令返回后,make检查该命令退出的状态。如果该命令成功地完成,下一个命令行就会在新的子shell环境中执行,当最后一个命令行完成后,这条规则也宣告完成。如果出现错误(非零退出状态),make将放弃当前的规则,也许是所有的规则。

有时一个特定的命令失败并不是出现了问题。例如:您可能使用mkdir命令创建一个目录存在,如果该目录已经存在,mkdir将报告错误,但您此时也许要make继续执行。

要忽略一个命令执行产生的错误,请使用字符‘-’(在初始化TAB的后面)作为该命令行的开始。字符‘-’在命令传递给shell执行时丢弃。例如:

clean:
        -rm -f *.o

这条命令即使在不能删除一个文件时也强制rm继续执行。

在运行make时使用‘-i’或‘--ignore-errors’选项,将会忽略所有规则的命令运行产生的错误。在makefile文件中使用如果没有依赖的特殊目标.IGNORE规则,也具有同样的效果。但因为使用字符‘-’更灵活,所以该条规则已经很少使用。

一旦使用‘-’或‘-i’选项,运行命令时产生的错误被忽略,此时make象处理成功运行的命令一样处理具有返回错误的命令,唯一不同的地方是打印一条消息,告诉您命令退出时的编码状态,并说明该错误已经被忽略。如果发生错误而make并不说明其被忽略,则暗示当前的目标不能成功重新构造,并且和它直接相关或间接相关的目标同样不能重建。因为前一个过程没有完成,所以不会进一步执行别的命令。

在上述情况下,make一般立即放弃任务,返回一个非零的状态。然而,如果指定‘-k’或‘--keep-goning’选项,make则继续考虑这个目标的其它依赖,如果有必要在make放弃返回非零状态之前重建它们。例如,在编译一个OBJ文件发生错误后,即使make已经知道将所有OBJ文件连接在一起是不可能的,make -k'选项也继续编译其它OBJ文件。详细内容参阅选项概要。通常情况下,make的行为基于假设您的目的是更新指定的目标,一旦make得知这是不可能的,它将立即报告失败。‘-k’选项是告诉make真正的目的是测试程序中所有变化的可行性,或许是寻找几个独立的问题以便您可以在下次编译之前纠正它们。这是Emacs编译命令缺省情况下传递‘-k’选项的原因。

通常情况下,当一个命令运行失败时,如果它已经改变了目标文件,则该文件很可能发生混乱而不能使用或该文件至少没有完全得到更新。但是,文件的时间戳却表明该文件已经更新到最新,因此在make下次运行时,它将不再更新该文件。这种状况和命令被发出的信号强行关闭一样,参阅中断或关闭make。因此,如果在开始改变目标文件后命令出错,一般应该删除目标文件。如果.DELETE_ON_ERROR作为目标在makefile文件中出现,make将自动做这些事情。这是您应该明确要求make执行的动作,不是以前的惯例;特别考虑到兼容性问题时,您更应明确提出这样的要求。

5.5中断或关闭make

如果make在一条命令运行时得到一个致命的信号, 则make将根据第一次检查的时间戳和最后更改的时间戳是否发生变化决定它是否删除该命令要更新的目标文件。

删除目标文件的目的是当make下次运行时确保目标文件从原文件得到更新。为什么?假设正在编译文件时您键入Ctrl-c,而且这时已经开始写OBJ文件‘foo.o’,Ctrl-c关闭了该编译器,结果得到不完整的OBJ文件‘foo.o’的时间戳比源程序‘foo.c’的时间戳新,如果make收到Ctrl-c的信号而没有删除OBJ文件‘foo.o’,下次请求make更新OBJ文件‘foo.o’时,make将认为该文件已更新到最新而没有必要更新,结果在linker将OBJ文件连接为可执行文件时产生奇怪的错误信息。

您可以将目标文件作为特殊目标.PRECIOUS的依赖从而阻止make这样删除该目标文件。在重建一个目标之前,make首先检查该目标文件是否出现在特殊目标.PRECIOUS的依赖列表中,从而决定在信号发生时是否删除该目标文件。您不删除这种目标文件的原因可能是:目标更新是一种原子风格,或目标文件存在仅仅为了记录更改时间(其内容无关紧要),或目标文件必须一直存在,用来防止其它类型的错误等。

5.6递归调用make

递归调用意味着可以在makefile文件中将make作为一个命令使用。这种技术在包含大的系统中把makefile分离为各种各样的子系统时非常有用。例如,假设您有一个子目录‘subdir’,该目录中有它自己的makefile文件,您希望在该子目录中运行make时使用该makefile文件,则您可以按下述方式编写:

  subsystem:
         cd subdir && $(MAKE)

, 等同于这样写 (参阅选项概要):

subsystem:
        $(MAKE) -C subdir

您可以仅仅拷贝上述例子实现make的递归调用,但您应该了解它们是如何工作的,它们为什么这样工作,以及子make和上层make的相互关系。

为了使用方便,GNU make把变量CURDIR的值设置为当前工作的路径。如果‘-C’选项有效,它将包含的是新路径,而不是原来的路径。该值和它在makefile中设置的值有相同的优先权(缺省情况下,环境变量CURDIR不能重载)。注意,操作make时设置该值无效。

5.6.1 变量MAKE的工作方式

递归调用make的命令总是使用变量MAKE,而不是明确的命令名‘make’,如下所示:

subsystem:
         cd subdir && $(MAKE)

该变量的值是调用make的文件名。如果这个文件名是‘/bin/make’,则执行的命令是`cd subdir && /bin/make'。如果您在上层makefile文件时用特定版本的make,则执行递归调用时也使用相同的版本。

在命令行中使用变量MAKE可以改变‘-t' (‘--touch'), ‘-n' (‘--just-print'), 或 ‘-q' (‘--question')选项的效果。如果在使用变量MAKE的命令行首使用字符‘+’也会起到相同的作用。参阅代替执行命令

设想一下在上述例子中命令‘make -t’的执行过程。(‘-t’选项标志目标已经更新,但却不执行任何命令,参阅代替执行命令。)按照通常的定义,命令‘make –t’在上例中仅仅创建名为‘subsystem’的文件而不进行别的工作。您实际要求运行‘cd subdir && make –t’干什么?是执行命令或是按照‘-t’的要求不执行命令?

Make的这个特点是这样的:只要命令行中包含变量MAKE,标志`-t', `-n'`-q'将不对本行起作用。虽然存在标志不让命令执行,但包含变量MAKE的命令行却正常运行,make实际上是通过变量MAKEFLAGS将标志值传递给了子make(参阅与子make通讯的选项)。所以您的验证文件、打印命令的请求等都能传递给子系统。

5.6.2与子make通讯的变量

通过明确要求,上层make变量的值可以借助环境传递给子make,这些变量能在子make中缺省定义,在您不使用‘-e’开关的情况下,传递的变量的值不能代替子make使用的makefile文件中指定的值(参阅命令概要)。

向下传递、或输出一个变量时,make将该变量以及它的值添加到运行每一条命令的环境中。子make,作为响应,使用该环境初始化它的变量值表。参阅环境变量

除了明确指定外,make仅向下输出在环境中定义并初始化的或在命令行中设置的变量,而且这些变量的变量名必须仅由字母、数字和下划线组成。一些shell不能处理名字中含有字母、数字和下划线以外字符的环境变量。特殊变量如SHELL和MAKEFLAGS一般总要向下输出(除非您不输出它们)。即使您把变量MAKEFILE设为其它的值,它也向下输出。

Make自动传递在命令行中定义的变量的值,其方法是将它们放入MAKEFLAGS变量中。详细内容参阅下节。Make缺省创造的变量的值不能向下传递,子make可以自己定义它们。如果您要将指定变量输出给子make,请用export指令,格式如下:

export variable ...

您要将阻止一些变量输出给子make,请用unexport指令,格式如下:

unexport variable ...

为方便起见,您可以同时定义并输出一个变量:

export variable = value

下面的格式具有相同的效果:

variable = value
export variable

以及

export variable := value
具有相同的效果:
variable := value
export variable

同样,

export variable += value

亦同样:

variable += value
export variable

参阅为变量值追加文本

您可能注意到export和unexport指令在make与shell中的工作方式相同,如sh。

如果您要将所有的变量都输出,您可以单独使用export:

export

这告诉make 把export和unexport没有提及的变量统统输出,但任何在unexport提及的变量仍然不能输出。如果您单独使用export作为缺省的输出变量方式,名字中含有字母、数字和下划线以外字符的变量将不能输出,这些变量除非您明确使用export指令提及才能输出。

单独使用export的行为是老板本GNU make缺省定义的行为。如果您的makefile依靠这些行为,而且您希望和老板本GNU make兼容,您可以为特殊目标.EXPORT_ALL_VARIABLES 编写一条规则代替export指令,它将被老板本GNU make忽略,但如果同时使用export指令则报错。

同样,您可以单独使用unexport告诉make缺省不要输出变量,因为这是缺省的行为,只有前面单独使用了export(也许在一个包括的makefile中)您才有必要这样做。您不能同时单独使用export和unexport指令实现对某些命令输出对其它的命令不输出。最后面的一条指令(export或unexport)将决定make的全部运行结果。

作为一个特点,变量MAKELEVEL的值在从一个层次向下层传递时发生变化。该变量的值是字符型,它用十进制数表示层的深度。‘0’代表顶层make,‘1’代表子make,‘2’代表子-子-make,以此类推。Make为一个命令建立一次环境,该值增加1。

该变量的主要作用是在一个条件指令中测试(参阅makefile文件的条件语句);采用这种方法,您可以编写一个makefile,如果递归调用采用一种运行方式,由您控制直接执行采用另一种运行方式。

您可以使用变量MAKEFILES使所有的子make使用附加的makefile文件。变量MAKEFILES的值是makefile文件名的列表,文件名之间用空格隔开。在外层makefile中定义该变量,该变量的值将通过环境向下传递;因此它可以作为子make的额外的makefile文件,在子make读正常的或指定的makefile文件前,将它们读入。参阅变量MAKEFILES

5.6.3与子make通讯的选项

诸如‘-s’和‘-k’标志通过变量MAKEFLAGS自动传递给子make。该变量由make自动建立,并包含make收到的标志字母。所以,如果您是用‘make –ks’变量MAKEFLAGS就得到值‘ks’。

作为结果,任一个子make都在它的运行环境中为变量MAKEFLAGS赋值;作为响应,make使用该值作为标志并进行处理,就像它们作为参数被给出一样。参阅选项概要

同样,在命令行中定义的变量也将借助变量MAKEFLAGS传递给子make。变量MAKEFLAGS值中的字可以包含‘=’,make将它们按变量定义处理,其过程和在命令行中定义的变量一样。参阅变量重载

选项`-C', `-f', `-o', 和 ‘-W’不能放入变量MAKEFLAGS中;这些选项不能向下传递。

-j’选项是一个特殊的例子(参阅并行执行)。如果您将它设置为一些数值‘N’,而且您的操作系统支持它(大多数Unix系统支持,其它操作系统不支持),父make和所有子make通讯保证在它们中间同时仅有‘N’个任务运行。注意,任何包含递归调用的任务(参阅代替执行命令)不能计算在总任务数内(否则,我们仅能得到‘N’个子make运行,而没有多余的时间片运行实在的工作)。

如果您的操作系统不支持上述通讯机制,那么‘-j 1’将放到变量MAKEFLAGS中代替您指定的值。这是因为如果‘-j’选项传递给子make,您可能得到比您要求多很多的并行运行的任务数。如果您给出‘-j’选项而没有数字参数,意味着尽可能并行处理多个任务,这样向下传递,因为倍数的无限制性所以至多为1。

如果您不希望其它的标志向下传递,您必须改变变量MAKEFLAGS的值,其改变方式如下:

subsystem:
        cd subdir && $(MAKE) MAKEFLAGS=

该命令行中定义变量的实际上出现在变量MAKEOVERRIDES中,而且变量MAKEFLAGS包含了该变量的引用值。如果您要向下传递标志,而不向下传递命令行中定义的变量,这时,您可以将变量MAKEOVERRIDES的值设为空,格式如下:

MAKEOVERRIDES =

这并不十分有用。但是,一些系统对环境的大小有固定限制,而且该值较小,将这么多的信息放到变量MAKEFLAGS的值中可能超过该限制。如果您看到‘Arg list too long'的错误信息,很可能就是由于该问题造成的。(按照严格的POSIX.2的规定,如果在makefile文件定义特殊目标‘.POSIX’,改变变量MAKEOVERRIDES的值并不影响变量MAKEFLAGS。也许您并不关心这些。)

为了和早期版本兼容,具有相同功能的变量MFLAGS也是存在的。除了它不能包含命令行定义变量外,它和变量MAKEFLAGS有相同的值,而且除非它是空值,它的值总是以短线开始(MAKEFLAGS只有在和多字符选项一起使用时才以短线开始,如和‘--warn-undefined-variables’连用)。变量MFLAGS传统的使用在明确的递归调用make的命令中,例如:

subsystem:
        cd subdir && $(MAKE) $(MFLAGS)

但现在,变量MAKEFLAGS使这种用法变得多余。如果您要您的makefile文件和老版本的make程序兼容,请使用这种方式;这种方式在现代版本make中也能很好的工作。

如果您要使用每次运行make都要设置的特定选项,例如‘-k’选项(参阅选项概要),变量MAKEFLAGS十分有用。您可以简单的在环境中将给变量MAKEFLAGS赋值,或在makefile文件中设置变量MAKEFLAGS,指定的附加标志可以对整个makefile文件都起作用。(注意:您不能以这种方式使用变量MFLAGS,变量MFLAGS存在仅为和早期版本兼容,采用其它方式设置该变量make将不予解释。)

make解释变量MAKEFLAGS值的时候(不管在环境中定义或在makefile文件中定义),如果该值不以短线开始,则make首先为该值假设一个短线;接着将该值分割成字,字与字间用空格隔开,然后将这些字进行语法分析,好像它们是在命令行中给出的选项一样。(‘-C', ‘-f',‘-h',‘-o',‘-W'选项以及它们的长名字版本都将忽略,对于无效的选项不产生错误信息。)

如果您在环境中定义变量MAKEFLAGS,您不要使用严重影响make运行,破坏makefile文件的意图以及make自身的选项。例如‘-t', ‘-n', 和‘-q'选项,如果将它们中的一个放到变量MAKEFLAGS的值中,可能产生灾难性的后果,或至少产生让人讨厌的结果。

5.6.4 ‘--print-directory’选项

如果您使用几层make递归调用,使用‘-w’或‘--print-directory’选项,通过显示每个make开始处理以及处理完成的目录使您得到比较容易理解的输出。例如,如果使用‘make –w’命令在目录‘/u/gnu/make’中运行make,则make将下面格式输出信息:

make: Entering directory `/u/gnu/make'.

说明进入目录中,还没有进行任何任务。下面的信息:

make: Leaving directory `/u/gnu/make'.

说明任务已经完成。

通常情况下,您不必具体指明这个选项,因为make已经为您做了:当您使用‘-C’选项时,‘-w’选项已经自动打开,在子make中也是如此。如果您使用‘-s’选项,‘-w’选项不会自动打开,因为‘-s’选项是不打印信息,同样使用`--no-print-directory'选项‘-w’选项也不会自动打开。

5.7定义固定次序命令

在创建各种目标时,相同次序的命令十分有用时,您可以使用define指令定义固定次序的命令,并根据这些目标的规则引用固定次序。固定次序实际是一个变量,因此它的名字不能和其它的变量名冲突。

下面是定义固定次序命令的例子:

define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef

run-yacc是定义的变量的名字;endef标志定义结束;中间的行是命令。define指令在固定次序中不对变量引用和函数调用扩展;字符‘$’、圆括号、变量名等等都变成您定义的变量的值的一部分。定义多行变量一节对指令define有详细解释。

在该例子中,对于任何使用该固定次序的规则,第一个命令是对其第一个依赖运行Yacc命令,Yacc命令执行产生的输出文件一律命名为‘y.tab.c’;第二条命令,是将该输出文件的内容移入规则的目标文件中。

在使用固定次序时,规则中命令使用的变量应被确定的值替代,您可以象替代其它变量一样替代这些变量(详细内容参阅变量引用基础)。因为由define指令定义的变量是递归扩展的变量,所以在使用时所有变量引用才扩展。例如:

foo.c : foo.y
        $(run-yacc)

当固定次序‘run-yacc’运行时,‘foo.y’将代替变量‘$^’,‘foo.c’将代替变量‘$@’。

这是一个现实的例子,但并不是必要的,因为make有一条隐含规则可以根据涉及的文件名的类型确定所用的命令。参阅使用隐含规则

在命令执行时,固定次序中的每一行被处理为和直接出现在规则中的命令行一样,前面加上一个Tab,make也特别为每一行请求一个独立的子shell。您也可以在固定次序的每一行上使用影响命令行的前缀字符(`@', `-',和 `+'),参阅在规则中使用命令。例如使用下述的固定次序:

@echo "frobnicating target $@"
frob-step-1 ___FCKpd___262lt; -o $@-step-1
frob-step-2 $@-step-1 -o $@
endef

make将不回显第一行,但要回显后面的两个命令行。

另一方面,如果前缀字符在引用固定次序的命令行中使用,则该前缀字符将应用到固定次序的每以行中。例如这个规则:

frob.out: frob.in
        @$(frobnicate)

将不回显固定次序的任何命令。具体内容参阅命令回显

5.8 使用空命令

定义什么也不干的命令有时很有用,定义空命令可以简单的给出一个仅仅含有空格而不含其它任何东西的命令即可。例如:

target: ;

为字符串‘target’定义了一个空命令。您也可以使用以Tab字符开始的命令行定义一个空命令,但这由于看起来空白容易造成混乱。

也许您感到奇怪,为什么我们定义一个空命令?唯一的原因是为了阻止目标更新时使用隐含规则提供的命令。(参阅使用隐含规则以及定义最新类型的缺省规则

也许您喜爱为实际不存在的目标文件定义空命令,因为这样它的依赖可以重建。然而这样做并不是一个好方法,因为如果目标文件实际存在,则依赖有可能不重建,使用假想目标是较好的选择,参阅假想目标

6 使用变量

变量是在makefile中定义的名字,其用来代替一个文本字符串,该文本字符串称为该变量的值。在具体要求下,这些值可以代替目标、依赖、命令以及makefile文件中其它部分。(在其它版本的make中,变量称为宏(macros)。)

makefile文件读入时,除规则中的shell命令、使用‘=’定义的‘=’右边的变量、以及使用define指令定义的变量体此时不扩展外,makefile文件其它各个部分的变量和函数都将扩展。

变量可以代替文件列表、传递给编译器的选项、要执行的程序、查找源文件的目录、输出写入的目录,或您可以想象的任何文本。

变量名是不包括:',‘#',‘='、前导或结尾空格的任何字符串。然而变量名包含字母、数字以及下划线以外的其它字符的情况应尽量避免,因为它们可能在将来被赋予特别的含义,而且对于一些shell它们也不能通过环境传递给子make(参阅与子make通讯的变量)。变量名是大小写敏感的,例如变量名‘foo', ‘FOO', 和 ‘Foo'代表不同的变量。

使用大写字母作为变量名是以前的习惯,但我们推荐在makefile内部使用小写字母作为变量名,预留大写字母作为控制隐含规则参数或用户重载命令选项参数的变量名。参阅变量重载

一少部分的变量使用一个标点符号或几个字符作为变量名,这些变量是自动变量,它们又特定的用途。参阅自动变量

6.1 变量引用基础

写一个美元符号后跟用圆括号或大括号括住变量名则可引用变量的值:$(foo)'‘${foo}'都是对变量‘foo’的有效引用。‘$’的这种特殊作用是您在命令或文件名中必须写‘$$’才有单个‘$’的效果的原因。

变量的引用可以用在上下文的任何地方:目标、依赖、命令、绝大多数指令以及新变量的值等等。这里有一个常见的例子,在程序中,变量保存着所有OBJ文件的文件名:

objects = program.o foo.o utils.o
program : $(objects)
        cc -o program $(objects)
 
$(objects) : defs.h

变量的引用按照严格的文本替换进行,这样该规则

foo = c
prog.o : prog.$(foo)
        $(foo)$(foo) -$(foo) prog.$(foo)

可以用于编译C语言源程序‘prog.c’。因为在变量分配时,变量值前面的空格被忽略,所以变量foo的值是‘C’。(不要在您的makefile文件这样写!)

美元符号后面跟一个字符但不是美元符号、圆括号、大括号,则该字符将被处理为单字符的变量名。因此可以使用‘$x’引用变量x。然而,这除了在使用自动变量的情况下,在其它实际工作中应该完全避免。参阅自动变量

6.2 变量的两个特色

GNU make中可以使用两种方式为变量赋值,我们将这两种方式称为变量的两个特色(two flavors)。两个特色的区别在于它们的定义方式和扩展时的方式不同。

变量的第一个特色是递归调用扩展型变量。这种类型的变量定义方式:在命令行中使用‘=’定义(参阅设置变量)或使用define指令定义(参阅定义多行变量)。变量替换对于您所指定的值是逐字进行替换的;如果它包含对其它变量的引用,这些引用在该变量替换时(或在扩展为其它字符串的过程中)才被扩展。这种扩展方式称为递归调用型扩展。例如:

foo = $(bar)
bar = $(ugh)
ugh = Huh?
 
all:;echo $(foo)

将回显Huh?':‘$(foo)’扩展为‘$(bar)’,进一步扩展为‘$(ugh)’,最终扩展为‘Huh?’。

这种特色的变量是其它版本make支持的变量类型,有缺点也有优点。大多数人认为的该类型的变量的优点是:

CFLAGS = $(include_dirs) -O
include_dirs = -Ifoo -Ibar

即能够完成希望它完成的任务:当‘CFLAGS’在命令中扩展时,它将最终扩展为‘-Ifoo -Ibar’。其最大的缺点是不能在变量后追加内容,如在:

CFLAGS = $(CFLAGS) -O

在变量扩展过程中可能导致无穷循环(实际上make侦测到无穷循环就会产生错误信息)。

它的另一个缺点是在定义中引用的任何函数时(参阅文本转换函数)变量一旦展开函数就会立即执行。这可导致make运行变慢,性能变坏;并且导致通配符与shell函数(因不能控制何时调用或调用多少次)产生不可预测的结果。

为避免该问题和递归调用扩展型变量的不方便性,出现了另一个特色变量:简单扩展型变量。

简单扩展型变量在命令行中用‘:=’定义(参阅设置变量)。简单扩展型变量的值是一次扫描永远使用,对于引用的其它变量和函数在定义的时候就已经展开。简单扩展型变量的值实际就是您写的文本扩展的结果。因此它不包含任何对其它变量的引用;在该变量定义时就包含了它们的值。所以:

x := foo
y := $(x) bar
x := later

等同于:

y := foo bar
x := later

引用一个简单扩展型变量时,它的值也是逐字替换的。这里有一个稍复杂的例子,说明了‘:=’和shell函数连接用法(参阅函数shell)。该例子也表明了变量MAKELEVEL的用法,该变量在层与层之间传递时值发生变化。(参阅与子make通讯的变量,可获得变量MAKELEVEL关于的信息。)

ifeq (0,${MAKELEVEL})
cur-dir   := $(shell pwd)
whoami    := $(shell whoami)
host-type := $(shell arch)
MAKE := ${MAKE} host-type=${host-type} whoami=${whoami}
endif

按照这种方法使用‘:=’的优点是看起来象下述的典型的‘下降到目录’的命令:

${subdirs}:
      ${MAKE} cur-dir=${cur-dir}/$@ -C $@ all

简单扩展型变量因为在绝大多数程序设计语言中可以象变量一样工作,因此它能够使复杂的makefile程序更具有预测性。它们允许您使用它自己的值重新定义(或它的值可以被一个扩展函数以某些方式处理),它们还允许您使用更有效的扩展函数(参阅文本转换函数)。

您可以使用简单扩展型变量将控制的前导空格引入到变量的值中。前导空格字符一般在变量引用和函数调用时被丢弃。简单扩展型变量的这个特点意味着您可以在一个变量的值中包含前导空格,并在变量引用时保护它们。象这样:

nullstring :=
space := $(nullstring) # end of the line

这里变量space的值就是一个空格,注释‘# end of the line’包括在这里为了让人更易理解。因为尾部的空格不能从变量值中分离出去,仅在结尾留一个空格也有同样的效果(但是此时相当难读),如果您在变量值后留一个空格,象这样在行的结尾写上注释清楚表明您的打算是很不错的主意。相反,如果您在变量值后不要空格,您千万记住不要在行的后面留下几个空格再随意放入注释。例如:

dir := /foo/bar    # directory to put the frobs in

这里变量dir的值是‘/foo/bar ’(四个尾部空格),这不是预期的结果。(假设‘/foo/bar’是预期的值)。

另一个给变量赋值的操作符是‘?=’,它称为条件变量赋值操作符,因为它仅仅在变量还没有定义的情况下有效。这声明:

FOO ?= bar

和下面的语句严格等同(参阅函数origin

ifeq ($(origin FOO), undefined)
  FOO = bar
endif

注意,一个变量即使是空值,它仍然已被定义,所以使用‘?=’定义无效。

6.3变量引用高级技术

本节内容介绍变量引用的高级技术。

6.3.1替换引用

替换引用是用您指定的变量替换一个变量的值。它的形式‘$(var:a=b)’(或‘${var:a=b}’),它的含义是把变量var的值中的每一个字结尾的a用b替换。

我们说‘在一个字的结尾’,我们的意思是a一定在一个字的结尾出现,且a的后面要么是空格要么是该变量值的结束,这时的a被替换,值中其它地方的a不被替换。例如:

foo := a.o b.o c.o
bar := $(foo:.o=.c)
将变量‘bar’的值设为‘a.c b.c c.c’。参阅变量设置
替换引用实际是使用扩展函数patsubst的简写形式(参阅字符串替换和分析函数)。我们提供替换引用也是使扩展函数patsubst与make的其它实现手段兼容的措施。
另一种替换引用是使用强大的扩展函数patsubst。它的形式和上述的‘$(var:a=b)’一样,不同在于它必须包含单个‘%’字符,其实这种形式等同于‘$(patsubst a,b,$(var))’。有关于函数patsubst扩展的描述参阅字符串替换和分析函数。例如:
foo := a.o b.o c.o
bar := $(foo:%.o=%.c)

社值变量bar'的值为a.c b.c c.c'

6.3.2嵌套变量引用(计算的变量名)

嵌套变量引用(计算的变量名)是一个复杂的概念,仅仅在十分复杂的makefile程序中使用。绝大多数情况您不必考虑它们,仅仅知道创建名字中含有美元标志的变量可能有奇特的结果就足够了。然而,如果您是要把一切搞明白的人或您实在对它们如何工作有兴趣,请认真阅读以下内容。
变量可以在它的名字中引用其它变量,这称为嵌套变量引用(计算的变量名)。例如:
x = y
y = z
a := $($(x))
定义阿a为‘z’:‘$(x)’在‘$($(x))’中扩展为‘y’,因此‘$($(x))’扩展为‘$(y)’,最终扩展为‘z’。这里对引用的变量名的陈述不太明确;它根据‘$(x)’的扩展进行计算,所以引用‘$(x)’是嵌套在外层变量引用中的。
前一个例子表明了两层嵌套,但是任何层次数目的嵌套都是允许的,例如,这里有一个三层嵌套的例子:
x = y
y = z
z = u
a := $($($(x)))
这里最里面的‘$(x)’ 扩展为‘y’,因此‘$($(x))’扩展为‘$(y)’,‘$(y)’ 扩展为‘z’,最终扩展为‘u’。
在一个变量名中引用递归调用扩展型变量,则按通常的风格再扩展。例如:
x = $(y)
y = z
z = Hello
a := $($(x))
定义的a是‘Hello’:‘$($(x))’扩展为‘$($(y))’,‘$($(y))’变为‘$(z)’, $(z)’最终扩展为‘Hello’。
嵌套变量引用和其它引用一样也可以包含修改引用和函数调用(参阅文本转换函数)。例如,使用函数subst(参阅字符串替换和分析函数):
x = variable1
variable2 := Hello
y = $(subst 1,2,$(x))
z = y
a := $($($(z)))
定义的a是‘Hello’。任何人也不会写象这样令人费解的嵌套引用程序,但它确实可以工作:‘$($($(z)))’ 扩展为‘$($(y))’,‘$($(y))’变为‘$(subst 1,2,$(x))’。它从变量‘x’得到值‘variable1’,变换替换为‘variable2’,所以整个字符串变为‘$( variable2)’,一个简单的变量引用,它的值为‘Hello’。
嵌套变量引用不都是简单的变量引用,它可以包含好几个变量引用,同样也可包含一些固定文本。例如,
a_dirs := dira dirb
1_dirs := dir1 dir2
 
a_files := filea fileb
1_files := file1 file2
 
ifeq "$(use_a)" "yes"
a1 := a
else
a1 := 1
endif
 
ifeq "$(use_dirs)" "yes"
df := dirs
else
df := files
endif
 
dirs := $($(a1)_$(df))
根据设置的use_a和use_dirs的输入可以将dirs这个相同的值分别赋给a_dirs, 1_dirs, a_files1_files。
嵌套变量引用也可以用于替换引用:
a_objects := a.o b.o c.o
1_objects := 1.o 2.o 3.o
 
sources := $($(a1)_objects:.o=.c)
根据a1的值,定义的sources可以是`a.c b.c c.c'`1.c 2.c 3.c'。
使用嵌套变量引用唯一的限制是它们不能只部分指定要调用的函数名,这是因为用于识别函数名的测试在嵌套变量引用扩展之前完成。例如:
ifdef do_sort
func := sort
else
func := strip
endif
 
bar := a d b g q c
 
foo := $($(func) $(bar))
则给变量‘foo’的值赋为‘sort a d b g q c'‘strip a d b g q c',而不是将‘a d b g q c’作为函数sort或strip的参数。如果在将来去掉这种限制是一个不错的主意。
您也可以变量赋值的左边使用嵌套变量引用,或在define指令中。如:
dir = foo
$(dir)_sources := $(wildcard $(dir)/*.c)
define $(dir)_print
lpr $($(dir)_sources)
endef
该例子定义了变量dir',‘foo_sources', 和‘foo_print'
注意:虽然嵌套变量引用和递归调用扩展型变量都是用在复杂的makefile文件中,但二者不同(参阅变量的两个特色)。

6.4变量取值

变量有以下几种方式取得它们的值:
l         您可以在运行make时为变量指定一个重载值。参阅变量重载
l         您可以在makefile文件中指定值,即变量赋值(参阅设置变量)或使用逐字定义变量(参阅定义多行变量)。
l         把环境变量变为make的变量。参阅环境变量
l         自动变量可根据规则提供值,它们都有简单的习惯用法,参阅自动变量
l         变量可以用常量初始化。参阅隐含规则使用的变量

6.5设置变量

makefile文件中设置变量,编写以变量名开始后跟‘=’或‘:=’的一行即可。任何跟在‘=’或‘:=’后面的内容就变为变量的值。例如:
objects = main.o foo.o bar.o utils.o
定义一个名为objects的变量,变量名前后的空格和紧跟‘=’的空格将被忽略。
使用‘=’定义的变量是递归调用扩展型变量;以‘:=’定义的变量是简单扩展型变量。简单扩展型变量定义可以包含变量引用,而且变量引用在定义的同时就被立即扩展。参阅变量的两种特色
变量名中也可以包含变量引用和函数调用,它们在该行读入时扩展,这样可以计算出能够实际使用的变量名。
变量值的长度没有限制,但受限于计算机中的实际交换空间。当定义一个长变量时,在合适的地方插入反斜杠,把变量值分为多个文本行是不错的选择。这不影响make的功能,但可使makefile文件更加易读。
绝大多数变量如果您不为它设置值,空字符串将自动作为它的初值。虽然一些变量有内建的非空的初始化值,但您可随时按照通常的方式为它们赋值(参阅隐含规则使用的变量。)另外一些变量可根据规则自动设定新值,它们被称为自动变量。参阅自动变量
如果您喜欢仅对没有定义过的变量赋给值,您可以使用速记符‘?=’代替‘=’。下面两种设置变量的方式完全等同(参阅函数origin):
FOO ?= bar

ifeq ($(origin FOO), undefined)
FOO = bar
endif

6.6 为变量值追加文本

为已经定以过的变量的值追加更多的文本一般比较有用。您可以在独立行中使用‘+=’来实现上述设想。如:
objects += another.o
这为变量objects的值添加了文本‘another.o’(其前面有一个前导空格)。这样:
objects = main.o foo.o bar.o utils.o
objects += another.o

变量objects 设置为‘main.o foo.o bar.o utils.o another.o'

使用 `+=' 相同于:

objects = main.o foo.o bar.o utils.o
objects := $(objects) another.o
对于使用复杂的变量值,不同方法的差别非常重要。如变量在以前没有定义过,则‘+=’的作用和‘=’相同:它定义一个递归调用型变量。然而如果在以前有定义,‘+=’的作用依赖于您原始定义的变量的特色,详细内容参阅变量的两种特色
当您使用‘+=’为变量值附加文本时,make的作用就好象您在初始定义变量时就包含了您要追加的文本。如果开始您使用‘:=’定义一个简单扩展型变量,再用‘+=’对该简单扩展型变量值追加文本,则该变量按新的文本值扩展,好像在原始定义时就将追加文本定义上一样,详细内容参阅设置变量。实际上,
variable := value
variable += more

等同于:

variable := value
variable := $(variable) more
另一方面,当您把‘+=’和首次使用无符号‘=’定义的递归调用型变量一起使用时,make的运行方式会有所差异。在您引用递归调用型变量时,make并不立即在变量引用和函数调用时扩展您设定的值;而是将它逐字储存起来,将变量引用和函数调用也储存起来,以备以后扩展。当您对于一个递归调用型变量使用‘+=’时,相当于对一个不扩展的文本追加新文本。
variable = value
variable += more

粗略等同于:

temp = value
variable = $(temp) more
当然,您从没有定义过叫做temp的变量,如您在原始定义变量时,变量值中就包含变量引用,此时可以更为深刻地体现使用不同方式定义的的重要性。拿下面常见的例子,
CFLAGS = $(includes) -O
...
CFLAGS += -pg # enable profiling
第一行定义了变量CFLAGS,而且变量CFLAGS引用了其它变量,includes。(变量CFLAGS用于C编译器的规则,参阅隐含规则目录。)由于定义时使用‘=’,所以变量CFLAGS是递归调用型变量,意味着‘$(includes) -O’在make处理变量CFLAGS定义时是不扩展的;也就是变量includes在生效之前不必定义,它仅需要在任何引用变量CFLAGS之前定义即可。如果我们试图不使用‘+=’为变量CFLAGS追加文本,我们可能按下述方式:
CFLAGS := $(CFLAGS) -pg # enable profiling
这似乎很好,但结果绝不是我们所希望的。使用‘:=’重新定义变量CFLAGS为简单扩展型变量,意味着make在设置变量CFLAGS之前扩展了‘$(CFLAGS) -pg’。如果变量includes此时没有定义,我们将得到‘-0 -pg’,并且以后对变量includes的定义也不会有效。相反,使用‘+=’ 设置变量CFLAGS我们得到没有扩展的‘$(CFLAGS) –0 -pg’,这样保留了对变量includes的引用,在后面一个地方如果变量includes得到定义,‘$(CFLAGS)’仍然可以使用它的值。

6.7 override指令

如果一个变量设置时使用了命令参数(参阅变量重载),那么在makefile文件中通常的对该变量赋值不会生效。此时对该变量进行设置,您需要使用override指令,其格式如下:
override variable = value

override variable := value
为该变量追加更多的文本,使用:
override variable += more text
参阅为变量值追加文本
override指令不是打算扩大makefile和命令参数冲突,而是希望用它您可以改变和追加哪些设置时使用了命令参数的变量的值。
例如,假设您在运行C编译器时总是使用‘-g’开关,但您允许用户像往常一样使用命令参数指定其它开关,您就可以使用override指令:
override CFLAGS += -g

您也可以在define指令中使用override指令,下面的例子也许就是您想要得:

override define foo
bar
endef
关于define指令的信息参阅下节。

6.8定义多行变量

设置变量值的另一种方法时使用define指令。这个指令有一个特殊的用法,既可以定义包含多行字符的变量。这使得定义命令的固定次序十分方便(参阅定义固定次序命令)。
define指令同一行的后面一般是变量名,当然,也可以什么也没有。变量的值由下面的几行给出,值的结束由仅仅包含endef的一行标示出。除了上述在语法上的不同之外,define指令象‘=’一样工作:它创建了一个递归调用型变量(参阅变量的两个特色)。变量的名字可以包括函数调用和变量引用,它们在指令读入时扩展,以便能够计算出实际的变量名。
define two-lines
echo foo
echo $(bar)
endef
变量的值在通常的赋值语句中只能在一行中完成,但在define指令中在define指令行以后endef行之前中间所有的行都是变量值的一部分(最后一行除外,因为标示endef那一行不能认为是变量值的一部分)。前面的例子功能上等同于:
two-lines = echo foo; echo $(bar)
因为两命令之间用分号隔开,其行为很接近于两个分离的shell命令。然而,注意使用两个分离的行,意味着make请求shell两次,每一行都在独立的子shell中运行。参阅执行命令
如果您希望使用define指令的变量定义比使用命令行定义的变量优先,您可以把define指令和override指令一块使用:
override define two-lines
foo
$(bar)
endef
参阅override指令

6.9 环境变量

make使用的变量可以来自make的运行环境。任何make能够看见的环境变量,在make开始运行时都转变为同名同值的make变量。但是,在makefile文件中对变量的具体赋值,或使用带有参数的命令,都可以对环境变量进行重载(如果明确使用‘-e’标志,环境变量的值可以对makefile文件中的赋值进行重载,参阅选项概要,但是这在实际中不推荐使用。)
这样,通过在环境中设置变量CFLAGS,您可以实现在绝大多数makefile文件中的所有C源程序的编译使用您选择的开关。因为您知道没有makefile将该变量用于其它任务,所以这种使用标准简洁含义的变量是安全的(但这也是不可靠的,一些makefile文件可能设置变量CFLAGS,从而使环境中变量CFLAGS的值失效)。当使用递归调用的make时,在外层make环境中定义的变量,可以传递给内层的make(参阅递归调用make)。缺省方式下,只有环境变量或在命令行中定义的变量才能传递给内层make。您可以使用export指令传递其它变量,参阅与子make通讯的变量
环境变量的其它使用方式都不推荐使用。将makefile的运行完全依靠环境变量的设置、超出makefile文件的控制范围,这种做法是不明智的,因为不同的用户运行同一个makefile文件有可能得出不同的结果。这和大部分makefile文件的意图相违背。
变量SHELL在环境中存在,用来指定用户对交互的shell的选择,因此使用变量SHELL也存字类似的问题。这种根据选定值影响make运行的方式是很不受欢迎的。所以,make将忽略环境中变量SHELL的值(在MS-DOS 和 MS-Windows中运行例外,但此时变量SHELL通常不设置值,参阅执行命令)。

6.10 特定目标变量的值

make中变量的值一般是全局性的;既,无论它们在任何地方使用,它们的值是一样的(当然,您重新设置除外);自动变量是一个例外(参阅自动变量)。
另一个例外是特定目标变量的值,这个特点允许您可以根据make建造目标的变化改变变量的定义。象自动变量一样,这些值只能在一个目标的命令脚本的上下文起作用。
可以象这样设置特定目标变量的值:
target ... : variable-assignment

或这样:

target ... : override variable-assignment
target ...’中可含有多个目标,如此,则设置的特定目标变量的值可在目标列表中的任一个目标中使用。‘variable-assignment’使用任何赋值方式都是有效的:递归调用型(‘=’)、静态(‘:=’)、追加(‘+=’)或条件(‘?=’)。所有出现在‘variable-assignment’中的变量能够在特定目标target ...的上下文中使用:也就是任何以前为特定目标target ...定义的特定目标变量的值在这些特定目标中都是有效的。注意这种变量值和全局变量值相比是局部的值:这两种类型的变量不必有相同的类型(递归调用vs.静态)。
特定目标变量的值和其它makefile变量具有相同的优先权。一般在命令行中定义的变量(和强制使用‘-e’情况下的环境变量)的值占据优先的地位,而使用override指令定义的特定目标变量的值则占据优先地位。
特定目标变量的值有另外一个特点:当您定义一个特定目标变量时,该变量的值对特定目标target ...的所有依赖有效,除非这些依赖用它们自己的特定目标变量的值将该变量重载。例如:
prog : CFLAGS = -g
prog : prog.o foo.o bar.o
将在目标prog的命令脚本中设置变量CFLAGS的值为‘-g’,同时在创建`prog.o', `foo.o', 和 `bar.o'的命令脚本中变量CFLAGS的值也是‘-g’,以及prog.o',‘foo.o', 和‘bar.o'的依赖的创建命令脚本中变量CFLAGS的值也是‘-g’。

6.11 特定格式变量的值

除了特定目标变量的值(参阅上小节)外,GNU make也支持特定格式变量的值。使用特定格式变量的值,可以为匹配指定格式的目标定义变量。在为目标定义特定目标变量后将搜寻按特定格式定义的变量,在为该目标的父目标定义的特定目标变量前也要搜寻按特定格式定义的变量。
设置特定格式变量格式如下:
pattern ... : variable-assignment

或这样:

pattern ... : override variable-assignment
这里的‘pattern’是%-格式。象特定目标变量的值一样,‘pattern ...’中可含有多个格式,如此,则设置的特定格式变量的值可在匹配列表中的任一个格式中的目标中使用。‘variable-assignment’使用任何赋值方式都是有效的,在命令行中定义的变量的值占据优先的地位,而使用override指令定义的特定格式变量的值则占据优先地位。例如:
%.o : CFLAGS = -O
搜寻所有匹配格式%.o的目标,并将它的变量CFLAGS的值设置为‘-0’。

7 makefile文件的条件语句

一个条件语句可以导致根据变量的值执行或忽略makefile文件中一部分脚本。条件语句可以将一个变量与其它变量的值相比较,或将一个变量与一字符串常量相比较。条件语句用于控制make实际看见的makefile文件部分,不能用于在执行时控制shell命令。

7.1条件语句的例子

下述的条件语句的例子告诉make如果变量CC的值是‘gcc’时使用一个数据库,如不是则使用其它数据库。它通过控制选择两命令行之一作为该规则的命令来工作。‘CC=gcc’作为make改变的参数的结果不仅用于决定使用哪一个编译器,而且决定连接哪一个数据库。
libs_for_gcc = -lgnu
normal_libs =
 
foo: $(objects)
ifeq ($(CC),gcc)
        $(CC) -o foo $(objects) $(libs_for_gcc)
else
        $(CC) -o foo $(objects) $(normal_libs)
endif
该条件语句使用三个指令:ifeq、else和endif。
Ifeq指令是条件语句的开始,并指明条件。它包含两个参数,它们被逗号分开,并被扩在圆括号内。运行时首先对两个参数变量替换,然后进行比较。在makefile中跟在ifeq后面的行是符合条件时执行的命令;否则,它们将被忽略。
如果前面的条件失败,else指令将导致跟在其后面的命令执行。在上述例子中,意味着当第一个选项不执行时,和第二个选项连在一起的命令将执行。在条件语句中,else指令是可选择使用的。
Endif指令结束条件语句。任何条件语句必须以endif指令结束,后跟makefile文件中的正常内容。
上例表明条件语句工作在原文水平:条件语句的行根据条件要么被处理成makefile文件的一部分或要么被忽略。这是makefile文件重大的语法单位(例如规则)可以跨越条件语句的开始或结束的原因。
当变量CC的值是gcc,上例的效果为:
foo: $(objects)
        $(CC) -o foo $(objects) $(libs_for_gcc)
当变量CC的值不是gcc而是其它值的时候,上例的效果为:
foo: $(objects)
        $(CC) -o foo $(objects) $(normal_libs)
相同的结果也能使用另一种方法获得:先将变量的赋值条件化,然后再使用变量:
libs_for_gcc = -lgnu
normal_libs =
 
ifeq ($(CC),gcc)
  libs=$(libs_for_gcc)
else
  libs=$(normal_libs)
endif
 
foo: $(objects)
        $(CC) -o foo $(objects) $(libs)

7.2条件语句的语法

对于没有else指令的条件语句的语法为:
conditional-directive
text-if-true
endif
text-if-true’可以是任何文本行,在条件为‘真’时它被认为是makefile文件的一部分;如果条件为‘假’,将被忽略。完整的条件语句的语法为:
conditional-directive
text-if-true
else
text-if-false
endif
如果条件为‘真’,使用‘text-if-true’;如果条件为‘假’,使用‘text-if-false’。‘text-if-false’可以是任意多行的文本。
关于‘conditional-directive’的语法对于简单条件语句和复杂条件语句完全一样。有四种不同的指令用于测试不同的条件。下面是指令表:

ifeq (arg1, arg2)

ifeq 'arg1' 'arg2'

ifeq "arg1" "arg2"

ifeq "arg1" 'arg2'

ifeq 'arg1' "arg2"

 扩展参数arg1、arg2中的所有变量引用,并且比较它们。如果它们完全一致,则使用‘text-if-true’,否则使用‘text-if-false’(如果存在的话)。您经常要测试一个变量是否有非空值,当经过复杂的变量和函数扩展得到一个值,对于您认为是空值,实际上有可能由于包含空格而被认为不是空值,由此可能造成混乱。对于此,您可以使用strip函数从而避免空格作为非空值的干扰。例如:
ifeq ($(strip $(foo)),)
text-if-empty
endif
即使$(foo)中含有空格,也使用‘text-if-empty’。

ifneq (arg1, arg2)

ifneq 'arg1' 'arg2'

ifneq "arg1" "arg2"

ifneq "arg1" 'arg2'

ifneq 'arg1' "arg2"

扩展参数arg1、arg2中的所有变量引用,并且比较它们。如果它们不同,则使用‘text-if-true’,否则使用‘text-if-false’(如果存在的话)。

ifdef variable-name

如果变量‘variable-name’是非空值,‘text-if-true’有效,否则,‘text-if-false’有效(如果存在的话)。变量从没有被定义过则变量是空值。注意ifdef仅仅测试变量是否有值。它不能扩展到看变量是否有非空值。因而,使用ifdef测试所有定义过的变量都返回‘真’,但那些象‘foo=’情况除外。测试空值请使用ifeq($(foo),)。例如:
bar =
foo = $(bar)
ifdef foo
frobozz = yes
else
frobozz = no
endif

设置frobozz'的值为yes', 而::

foo =
ifdef foo
frobozz = yes
else
frobozz = no
endif

设置frobozz'‘no'

ifndef variable-name

如果变量‘variable-name’是空值,‘text-if-true’有效,否则,‘text-if-false’有效(如果存在的话)。
在指令行前面允许有多余的空格,它们在处理时被忽略,但是不允许有Tab(如果一行以Tab开始,那么该行将被认为是规则的命令行)。除此之外,空格和Tab可以插入到行的任何地方,当然指令名和参数中间除外。以‘#’开始的注释可以在行的结尾。
在条件语句中另两个有影响的指令是else和endif。这两个指令以一个单词的形式出现,没有任何参数。在指令行前面允许有多余的空格,空格和Tab可以插入到行的中间,以‘#’开始的注释可以在行的结尾。
条件语句影响make使用的makefile文件。如果条件为‘真’,make读入‘text-if-true’包含的行;如果条件为‘假’,make读入‘text-if-false’包含的行(如果存在的话);makefile文件的语法单位,例如规则,可以跨越条件语句的开始或结束。
当读入makefile文件时,Make计算条件的值。因而您不能在测试条件时使用自动变量,因为他们是命令执行时才被定义(参阅自动变量)。
为了避免不可忍受的混乱,在一个makefile文件中开始一个条件语句,而在另外一个makefile文件中结束这种情况是不允许的。然而如果您试图引入包含的makefile文件不中断条件语句,您可以在条件语句中编写include指令。

7.3测试标志的条件语句

您可以使用变量MAKEFLAGS和findstring函数编写一个条件语句,用它来测试例如‘-t’等的make命令标志(参阅字符串替换和分析的函数)。这适用于仅使用touch标志不能完全更改文件的时间戳的场合。
findstring函数检查一个字符串是否为另一个字符串的子字符串。如果您要测试‘-t’标志,使用‘-t’作为第一个字符串,将变量MAKEFLAGS的值作为另一个字符串。例如下面的例子是安排使用‘ranlib –t’完成一个档案文件的更新:
archive.a: ...
ifneq (,$(findstring t,$(MAKEFLAGS)))
        +touch archive.a
        +ranlib -t archive.a
else
        ranlib archive.a
endif
前缀‘+’表示这些命令行是递归调用行,即使是用‘-t’标志它们一样要执行。参阅递归调用make

8 文本转换函数

函数允许您在makefile文件中处理文本、计算文件、操作使用命令等。在函数调用时您必须指定函数名以及函数操作使用的参数。函数处理的结果将返回到makefile文件中的调用点,其方式和变量替换一样。

8.1函数调用语法

函数调用和变量引用类似,它的格式如下:
$(function arguments)

或这样:

${function arguments}
这里‘function’是函数名,是make内建函数列表中的一个。当然您也可以使用创建函数call创建的您自己的函数。‘arguments’是该函数的参数。参数和函数名之间是用空格或Tab隔开,如果有多个参数,它们之间用逗号隔开。这些空格和逗号不是参数值的一部分。包围函数调用的定界符,无论圆括号或大括号,可以在参数中成对出现,在一个函数调用中只能有一种定界符。如果在参数中包含变量引用或其它的函数调用,最好使用同一种定界符,如写为‘$(subst a,b,$(x))', 而不是 `$(subst a,b,${x})'。这是因为这种方式不但比较清楚,而且也有在一个函数调用中只能有一种定界符的规定。
为每一个参数写的文本经过变量替换或函数调用处理,最终得到参数的值,这些值是函数执行必须依靠的文本。另外,变量替换是按照变量在参数中出现的次序进行处理的。
逗号和不成对出现的圆括号、大括号不能作为文本出现在参数中,前导空格也不能出现在第一个参数中。这些字符不能被变量替换处理为参数的值。如果需要使用这些字符,首先定义变量comma和space,它们的值是单独的逗号和空格字符,然后在需要的地方因用它们,如下例:
comma:= ,
empty:=
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))
# bar is now `a,b,c'.
这里函数subst的功能是将变量foo中的空格用逗号替换,然后返回结果。

8.2字符串替换和分析函数

这里有一些用于操作字符串的函数:

$(subst from,to,text)

在文本‘text’中使用‘to’替换每一处‘from’。例如:
$(subst ee,EE,feet on the street)
结果为‘fEEt on the street’。

$(patsubst pattern,replacement,text)

寻找‘text’中符合格式‘pattern’的字,用‘replacement’替换它们。这里‘pattern’中包含通配符‘%’,它和一个字中任意个数的字符相匹配。如果‘replacement’中也含有通配符‘%’,则这个‘%’被和‘pattern’中通配符‘%’匹配的文本代替。在函数patsubst中的‘%’可以用反斜杠(‘/’)引用。引用字符‘%’的反斜杠可以被更多反斜杠引用。引用字符‘%’和其它反斜杠的反斜杠在比较文件名或有一个stem(径)代替它之前从格式中移出。使用反斜杠引用字符‘%’不会带来其它麻烦。例如,格式the/%weird//%pattern//'是the%weird/' 加上通配符‘%'然后和字符串‘pattern//'连接。最后的两个反斜杠由于不能影响任何统配符‘%’所以保持不变。在字之间的空格间被压缩为单个空格,前导以及结尾空格被丢弃。例如:

$(patsubst %.c,%.o,x.c.c bar.c)
的结果为:x.c.o bar.o'。替换引用是实现函数patsubst功能一个简单方法:
$(var:pattern=replacement)

等同于 :

$(patsubst pattern,replacement,$(var))
另一个通常使用的函数patsubst的简单方法是:替换文件名的后缀。
$(var:suffix=replacement)

等同于:

$(patsubst %suffix,%replacement,$(var))
例如您可能有一个OBJ文件的列表:
objects = foo.o bar.o baz.o

要得到这些文件的源文件,您可以简单的写为:

$(objects:.o=.c)

代替规范的格式:

$(patsubst %.o,%.c,$(objects))

$(strip string)

去掉前导和结尾空格,并将中间的多个空格压缩为单个空格。这样,$(strip a b c )'结果为‘a b c’。函数strip和条件语句连用非常有用。当使用ifeq或ifneq把一些值和空字符串‘’比较时,您通常要将一些仅由空格组成的字符串认为是空字符串(参阅makefile中的条件语句)。如此下面的例子在实现预期结果时可能失败:
.PHONY: all
ifneq   "$(needs_made)" ""
all: $(needs_made)
else
all:;@echo 'Nothing to make!'
endif
在条件指令ifneq中用函数调用‘$(strip $(needs_made))'代替变量引用‘$(needs_made)'将不再出现问题。

$(findstring find,in)

在字符串‘in’中搜寻‘find’,如果找到,则返回值是‘find’,否则返回值为空。您可以在一个条件中使用该函数测试给定的字符串中是否含有特定的子字符串。这样,下面两个例子:
$(findstring a,a b c)
$(findstring a,b c)
将分别产生值‘a’和‘’。对于函数findstring的特定用法参阅测试标志的条件语句

$(filter pattern...,text)

返回在‘text’中由空格隔开且匹配格式‘pattern...’的字,对于不符合格式‘pattern...’的字移出。格式用‘%’写出,和前面论述过的函数patsubst的格式相同。函数filter可以用来变量分离类型不同的字符串。例如:
sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
        cc $(filter %.c %.s,$(sources)) -o foo
表明foo' 依靠‘foo.c',‘bar.c',‘baz.s'‘ugh.h';但仅有‘foo.c',‘bar.c'‘baz.s' 指明用命令编译。

$(filter-out pattern...,text)

返回在‘text’中由空格隔开且不匹配格式‘pattern...’的字,对于符合格式‘pattern...’的字移出。只是函数filter的反函数。例如:
objects=main1.o foo.o main2.o bar.o
mains=main1.o main2.o

下面产生不包含在变量‘mains’中的OBJ文件的文件列表:

$(filter-out $(mains),$(objects))

$(sort list)

将‘list’中的字按字母顺序排序,并取掉重复的字。输出是由单个空格隔开的字的列表。
$(sort foo bar lose)
返回值是‘bar foo lose’。顺便提及,由于函数sort可以取掉重复的字,您就是不关心排序也可以使用它的这个特点。
这里有一个实际使用函数subst和patsubst的例子。假设一个makefile文件使用变量VPATH指定make搜寻依赖文件的一系列路径(参阅VPATH:依赖搜寻路径)。这个例子表明怎样告诉C编译器在相同路径列表中搜寻头文件。
变量VPATH的值是一列用冒号隔开的路径名,如‘src:../headers'。首先,函数subst将冒号变为空格:
$(subst :, ,$(VPATH))
这产生值src ../headers'。然后,函数patsubst为每一个路径名加入‘-|’标志,这样这些路径可以加到变量CFLAGS中,就可以自动传递给C编译器:
override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH)))
结果是在以前给定的变量CFLAGS的值后追加文本‘-Isrc -I../headers’。Override指令的作用是即使以前使用命令参数指定变量CFLAGS的值,新值也能起作用。参阅override指令

8.3文件名函数

其中几个内建的扩展函数和拆分文件名以及列举文件名相关联。下面列举的函数都能执行对文件名的特定转换。函数的参数是一系列的文件名,文件名之间用空格隔开(前导和结尾空格被忽略)。列表中的每一个文件名都采用相同的方式转换,而且结果用单个空格串联在一起。

$(dir names...)

抽取‘names’中每一个文件名的路径部分,文件名的路径部分包括从文件名的开始到最后一个斜杠(含斜杠)之前的一切字符。如果文件名中没有斜杠,路径部分是‘./’。如:
$(dir src/foo.c hacks)

产生的结果为 ‘src/ ./’

$(notdir names...)

抽取‘names’中每一个文件名中除路径部分外一切字符(真正的文件名)。如果文件名中没有斜杠,则该文件名保持不变,否则,将路径部分移走。一个文件名如果仅包含路径部分(以斜杠结束的文件名)将变为空字符串。这是非常不幸的,因为这意味着在结果中如果有这种文件名存在,两文件名之间的空格将不是由相同多的空格隔开。但现在我们并不能看到其它任何有效的代替品。例如:
$(notdir src/foo.c hacks)
产生的结果为‘foo.c hacks’。

$(suffix names...)

抽取‘names’中每一个文件名的后缀。如果文件名中(或含有斜杠,且在最后一个斜杠后)含有句点,则后缀是最后那个句点以后的所有字符,否则,后缀是空字符串。如果结果为空意味着‘names’没有带后缀文件名,如果文件中含有多个文件名,则结果列出的后缀数很可能比原文件名数目少。例如:
$(suffix src/foo.c src-1.0/bar.c hacks)
产生的结果是‘.c .c’。

$(basename names...)

抽取‘names’中每一个文件名中除后缀外一切字符。如果文件名中(或含有斜杠,且在最后一个斜杠后)含有句点,则基本名字是从开始到最后一个句点(不包含)间的所有字符。如果没有句点,基本名字是整个文件名。例如:
$(basename src/foo.c src-1.0/bar hacks)

产生的结果为‘src/foo src-1.0/bar hacks’。

$(addsuffix suffix,names...)

参数‘names’作为一系列的文件名,文件名之间用空格隔开;suffix作为一个单位。将Suffix(后缀)的值附加在每一个独立文件名的后面,完成后将文件名串联起来,它们之间用单个空格隔开。例如:
$(addsuffix .c,foo bar)

结果为‘foo.c bar.c’。

$(addprefix prefix,names...)

参数‘names’作为一系列的文件名,文件名之间用空格隔开;prefix作为一个单位。将preffix(前缀)的值附加在每一个独立文件名的前面,完成后将文件名串联起来,它们之间用单个空格隔开。例如:
$(addprefix src/,foo bar)

结果为‘src/foo src/bar’。

$(join list1,list2)

将两个参数串联起来:两个参数的第一个字串联起来形成结果的第一个字,两个参数的第二个字串联起来形成结果的第二个字,以此类推。如果一个参数比另一个参数的字多,则多余的字原封不动的拷贝到结果上。例如,$(join a b,.c .o)'产生a.c b.o'。字之间多余的空格不再保留,它们由单个空格代替。该函数可将函数dir、notdir的结果合并,产生原始给定的文件列表。

$(word n,text)

返回‘text’中的第n个字。N的合法值从1开始。如果n比‘text’中的字的数目大,则返回空值。例如:
$(word 2, foo bar baz)

返回 bar’。

$(wordlist s,e,text)

返回‘text’中的从第s个字开始到第e个字结束的一列字。S、e的合法值从1开始。如果s比‘text’中的字的数目大,则返回空值;如果e比‘text’中的字的数目大,则返回从第s个字开始到‘text’结束的所有字;如果s比e大,不返回任何值。例如:
$(wordlist 2, 3, foo bar baz)

返回`bar baz'。

$(words text)

返回‘text’中字的数目。这样‘text’中的最后一个字是‘$(word $(words text),text)’。

$(firstword names...)

参数‘names’作为一系列的文件名,文件名之间用空格隔开;返回第一个文件名,其余的忽略。例如:
$(firstword foo bar)

产生结果‘foo’。 虽然 $(firstword text)$(word 1,text)的作用相同,但第一个函数因为简单而保留下来。

$(wildcard pattern)

参数‘pattern’是一个文件名格式,典型的包含通配符(和shel中的文件名一样)。函数wildcard的结果是一列和格式匹配的且文件存在的文件名,文件名之间用一个空格隔开,参阅在文件名中使用通配符

8.4函数foreach

函数foreach和其它函数非常不同,它导致一个文本块重复使用,而且每次使用该文本块进行不同的替换;它和shell sh中的命令for及C-shell csh中的命令foreach类似。
函数foreach语法如下:
$(foreach var,list,text)
前两个参数,‘var’和‘list’,将首先扩展,注意最后一个参数‘text’此时不扩展;接着,对每一个‘list’扩展产生的字,将用来为‘var’扩展后命名的变量赋值;然后‘text’引用该变量扩展;因此它每次扩展都不相同。
结果是由空格隔开的‘text’ 在‘list’中多次扩展的字组成的新的‘list’。‘text’多次扩展的字串联起来,字与字之间由空格隔开,如此就产生了函数foreach的返回值。
这是一个简单的例子,将变量‘files’的值设置为 ‘dirs’中的所有目录下的所有文件的列表:
dirs := a b c d
files := $(foreach dir,$(dirs),$(wildcard $(dir)/*))
这里‘text’是‘$(wildcard $(dir)/*)’。第一个为变量dir发现的值是‘a’,所以产生函数foreach结果的第一个字为‘$(wildcard a/*)’;第二个重复的值是‘b’,所以产生函数foreach结果的第二个字为‘$(wildcard b/*)’;第三个重复的值是‘c’,所以产生函数foreach结果的第三个字为‘$(wildcard c/*)’;等等。该例子和下例有共同的结果:
files := $(wildcard a/* b/* c/* d/*)
如果‘text’比较复杂,您可以使用附加变量为它命名,这样可以提高程序的可读性:
find_files = $(wildcard $(dir)/*)
dirs := a b c d
files := $(foreach dir,$(dirs),$(find_files))
这里我们使用变量find_file。我们定义变量find_file时,使用了‘=’,因此该变量为递归调用型变量,这样变量find_file所包含的函数调用将在函数foreach控制下在扩展;对于简单扩展型变量将不是这样,在变量find_file定义时就调用函数wildcard。
函数foreach对变量‘var’没有长久的影响,它的值和变量特色在函数foreach调用结束后将和前面一样,其它从‘list’得到的值仅在函数foreach执行时起作用,它们是暂时的。变量‘var’在函数foreach执行期间是简单扩展型变量,如果在执行函数foreach之前变量‘var’没有定义,则函数foreach调用后也没有定义。参阅变量的两个特色
当使用复杂变量表达式产生变量名时应特别小心,因为许多奇怪的字符作为变量名是有效的,但很可能不是您所需要的,例如:
files := $(foreach Esta escrito en espanol!,b c ch,$(find_files))
如果变量find_file扩展引用名为‘Esta escrito en espanol!’变量,上例是有效的,但它极易带来错误。

8.5函数if

函数if对在函数上下文中扩展条件提供了支持(相对于GNU make makefile文件中的条件语句,例如ifeq指令,参阅条件语句的语法)。
一个函数if的调用,可以包含两个或三个参数:
$(if condition,then-part[,else-part])
第一个参数‘condition’,首先把前导、结尾空格去掉,然后扩展。如果扩展为非空字符串,则条件‘condition’为‘真’;如果扩展为空字符串,则条件‘condition’为‘假’。
如果条件‘condition’为‘真’,那么计算第二个参数‘then-part’的值,并将该值作为整个函数if的值。
如果条件‘condition’为‘假’,第三个参数如果存在,则计算第三个参数‘else-part’的值,并将该值作为整个函数if的值;如果第三个参数不存在,函数if将什么也不计算,返回空值。
注意仅能计算‘then-part’和‘else-part’二者之一,不能同时计算。这样有可能产生副作用(例如函数shell的调用)。

8.6函数call

函数call是唯一的创建新的带有参数函数的函数。您可以写一个复杂的表达是作为一个变量的值,然后使用函数call用不同的参数调用它。

函数call的语法为:

$(call variable,param,param,...)

make扩展该函数时,它将每一个参数‘param’赋值给临时变量$(1)、$(2)等;变量$(0)的值是变量‘variable’。对于参数‘param’的数量无没有最大数目限制,也没有最小数目限制,但是如果使用函数call而没有任何参数,其意义不大。

变量‘variable’在这些临时变量的上下文中被扩展为一个make变量,这样,在变量‘variable’中对变量‘$(1)’的引用决定了调用函数call时对第一个参数‘param’的使用。

注意变量‘variable’是一个变量的名称,不是对该变量的引用,所以,您不能采用‘$’和圆括号的格式书写该变量,当然,如果您需要使用非常量的文件名,您可以在文件名中使用变量引用。

如果变量名是内建函数名,则该内建函数将被调用(即使使用该名称的make变量已经存在)。函数call在给临时变量赋值以前首先扩展参数,这意味着,变量‘variable’对内建函数的调用采用特殊的规则进行扩展,象函数foreach或if,它们的扩展结果和您预期的结果可能不同。下面的一些例子能够更清楚的表达这一点。

该例子时使用宏将参数的顺序翻转:

reverse = $(2) $(1)
 
foo = $(call reverse,a,b)

这里变量foo的值是‘b a’。

下面是一个很有意思的例子:它定义了一个宏,使用该宏可以搜寻变量PATH包含的所有目录中的第一个指定类型的程序:

pathsearch = $(firstword $(wildcard $(addsufix /$(1),$(subst :, ,$(PATH)))))
 
LS := $(call pathsearch,ls)

现在变量LS的值是‘/bin/ls’或其它的类似的值。

在函数call中可以使用嵌套。每一次递归调用都可以为它自己的局部变量‘$(1)’等赋值,从而代替上一层函数call赋的值。例如:这实现了映像函数功能。

map = $(foreach a,$(2),$(call $(1),$(a)))

现在您可以映像(map)仅有一个参数的函数,如函数origin,一步得到多个值:

o = $(call map,origin,o map MAKE)

最后变量o包含诸如‘file file default’这样的值。

警告:在函数call的参数中使用空格一定要十分小心。因为在其它函数中,第二个或接下来的参数中的空格是不删除的,这有可能导致非常奇怪的结果。当您使用函数call时,去掉参数中任何多余的空格才是最安全的方法。

8.7函数origin

函数origin不想一般函数,它不对任何变量的值操作;它仅仅告诉您一些关于一个变量的信息;它特别的告诉您变量的来源。

函数origin的语法:

$(origin variable)

注意变量‘variable’是一个查询变量的名称,不是对该变量的引用所以,您不能采用‘$’和圆括号的格式书写该变量,当然,如果您需要使用非常量的文件名,您可以在文件名中使用变量引用。

函数origin的结果是一个字符串,该字符串变量是怎样定义的:

undefined'

如果变量‘variable’从没有定义。

default'

变量‘variable’是缺省定义,通常和命令CC等一起使用,参阅隐含规则使用的变量。注意如果您对一个缺省变量重新进行了定义,函数origin将返回后面的定义。

environment'

变量‘variable’作为环境变量定义,选项‘-e’没有打开(参阅选项概要

environment override'

变量‘variable’作为环境变量定义,选项‘-e’已打开(参阅选项概要)。

file'

变量‘variable’在makefile中定义。

command line'

变量‘variable’在命令行中定义。

override'

变量‘variable’在makefile中用override指令定义(参阅override指令)。

automatic'

变量‘variable’是自动变量,定义它是为了执行每个规则中的命令(参阅自动变量)。
这种信息的基本用途(其它用途是满足您的好奇心)是使您要了解变量值的依据。例如,假设您有一个名为‘foo’的makefile文件,它包含了另一个名为‘bar’的makefile文件,如果在环境变量中已经定义变量‘bletch’,您希望运行命令‘make –f bar’在makefile文件‘bar’中重新定义变量‘bletch’。但是makefile文件‘foo’在包括makefile文件‘bar’之前已经定义了变量‘bletch’,而且您也不想使用override指令定义,那么您可以在makefile文件‘foo’中使用override指令,因为override指令将会重载任何命令行中的定义,所以其定义的优先权超越以后在makefile文件‘bar’中的定义。因此makefile文件‘bar’可以包含:
ifdef bletch
ifeq "$(origin bletch)" "environment"
bletch = barf, gag, etc.
endif
endif
如果变量‘bletch’在环境中定义,这里将重新定义它。
即使在使用选项‘-e’的情况下,您也要对来自环境的变量‘bletch’重载定义,则您可以使用如下内容:
ifneq "$(findstring environment,$(origin bletch))" ""
bletch = barf, gag, etc.
endif
如果‘$(origin bletch)’返回‘environment’或‘environment override’,这里将对变量‘bletch’重新定义。参阅字符串替换和分析函数

8.8 函数shell

除了函数wildcard之外,函数shell和其它函数不同,它是make与外部环境的通讯工具。函数shell和在大多数shell中后引号(’)执行的功能一样:它用于命令的扩展。这意味着它起着调用shell命令和返回命令输出结果的参数的作用。Make仅仅处理返回结果,再返回结果替换调用点之前,make将每一个换行符或者一对回车/换行符处理为单个空格;如果返回结果最后是换行符(和回车符),make将把它们去掉。由函数shell调用的命令,一旦函数调用展开,就立即执行。在大多数情况下,当makefile文件读入时函数shell调用的命令就已执行。例外情况是在规则命令行中该函数的调用,因为这种情况下只有在命令运行时函数才能扩展,其它调用函数shell的情况和此类似。
这里有一些使用函数shell的例子:
contents := $(shell cat foo)
将含有文件foo的目录设置为变量contents的值,是用空格(而不是换行符)分离每一行。
files := $(shell echo *.c)
将‘*.c’的扩展设置为变量files的值。除非make使用非常怪异的shell,否则这条语句和‘wildcard *.c’的结果相同。

8.9 控制make的函数

这些函数控制make的运行方式。通常情况下,它们用来向用户提供makefile文件的信息或在侦测到一些类型的环境错误时中断make运行。

$(error text...)

通常‘text’是致命的错误信息。注意错误是在该函数计算时产生的,因此如果您将该函数放在命令的脚本中或递归调用型变量赋值的右边,它直到过期也不能计算。‘text’将在错误产生之前扩展,例如:
ifdef ERROR1
$(error error is $(ERROR1))
endif
如果变量ERROR01已经定义,在将makefile文件读入时产生致命的错误。或,
ERR = $(error found an error!)
 
.PHONY: err
err: ; $(ERR)
如果err目标被调用,在make运行时产生致命错误。

$(warning text...)

该函数和函数error工作的方式类似,但此时make不退出,即虽然‘text’扩展并显示结果信息,但make仍然继续执行。扩展该函数的结果是空字符串。
原创粉丝点击