GNU make v3.80 书摘

GNU make v3.80完整版中文指南下载

Chapter 1

书写规则建议的方式是:单目标,多依赖。就是说尽量要做到一个规则中只存在一个目标文件,可有多个依赖文件。尽量避免多目标,单依赖的方式。这样后期维护也会非常方便,而且Makefile会更清晰、明了。

在实际应用时,我们会把这个规则写成如下稍微复杂一些的样子。以防止出现始料未及的情况。

.PHONY : clean

clean :

-rm edit $(objects)

这两个实现有两点不同: 1. 通过“.PHONY”特殊目标将“clean”目标声明为伪目标。防止当磁盘上存在一个名为“clean”文件时,“clean”所在规则的命令无法执行(参考3.6 Makefile伪目标一节)。2. 在命令行之前使用“-”,意思是忽略命令“rm”的执行错误(参考4.4 命令的错误一节)。

Chapter 2

在一个完整的Makefile中,包含了5个东西:显式规则、隐含规则、变量的定义、指示符和注释。

注释:Makefile中“#”字符后的内容被作为是注释内容(和shell脚本一样)处理。如果此行的第一个非空字符为“#”,那么此行为注释行。注释行的结尾如果存在反斜线(\),那么下一行也被作为注释行。一般在书写Makefile时推荐将注释作为一个独立的行,而不要和Makefile的有效行放在一行中书写。当在Makefile中需要使用字符“#”时,可以使用反斜线加“#”(\#)来实现,其表示将“#”作为一字符而不是注释的开始标志

Makefile中指示符“include”书写在独立的一行,其形式如下:

include FILENAMES…

FILENAMES是shell所支持的文件名(可以使用通配符)。

指示符“include”所在的行可以一个或者多个空格(make程序在处理时将忽略这些空格)开始,切忌不能以[Tab]字符开始(如果一行以[Tab]字符开始make程序将此行作为一个命令行来处理)。指示符“include”和文件名之间、多个文件之间使用空格或者[Tab]键隔开。行尾的空白字符在处理时被忽略。使用指示符包含进来的Makefile中,如果存在变量或者函数的引用。它们将会在包含它们的Makefile中被展开。

来看一个例子,存在三个.mk文件,“$(bar)”被扩展为“bish bash”。则

include foo *.mk $(bar)

等价于

include foo a.mk b.mk c.mk bish bash

我们可使用“-include”来代替“include”,忽略由于包含文件不存在或者无法创建时的错误提示(“-”的意思是告诉make,忽略此操作的错误。make继续执行)。像下边那样:

-include FILENAMES…

使用这种方式时,当所要包含的文件不存在时不会有错误提示、make也不会退出;除此之外,和第一种方式效果相同。以下是这两种方式的比较:

使用“include FILENAMES…”,make程序处理时,如果“FILENAMES”列表中的任何一个文件不能正常读取而且不存在一个创建此文件的规则时make程序将会提示错误并退出。

使用“-include FILENAMES…”的情况是,当所包含的文件不存在或者不存在一个规则去创建它,make程序会继续执行,只有在因为makefile的目标的规则不存在时,才会提示致命错误并退出。

2.6 其他特殊变量

第一个重要的特殊的变量是“.VARIABLES”。它被展开以后是此引用点之前、makefile文件中所定义的所有全局变量列表。包括:空变量(未赋值的变量)和make的内嵌变量,但不包含目标指定的变量,目标指定变量值在特定目标的上下文有效。

Chapter 3

Makefile中对“$”有特殊的含义(表示变量或者函数的引用),如果我们的规则如果需要“$”,需要书写两个连续的(“$$”)。

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

TARGETS : NORMAL-PREREQUISITES | ORDER-ONLY-PREREQUISITES

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

1. 可以用在规则的目标、依赖中,此时make会自动将其展开;

2. 可出现在规则的命令中,其展开是在shell在执行此命令时完成。

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

如果Makefile有这样一句:“objects = *.o”。那么变量“objects”的值就是“*.o”,而不是使用空格分开的所有.o文件列表。如果需要变量“objects”代表所有的.o文件,则需要是用函数“wildcard”来实现(objects = $(wildcar *.o))。

3.4.3 函数wildcard

要用到函数“wildcard”,其用法:$(wildcard PATTERN…) ;在Makefile中,它被展开未已经存在的、空格分割的、匹配此模式的所有文件列表。

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

#sample Makefile

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

foo : $(objects)

cc -o foo $(objects)

3.5.1 一般搜索(变量VPATH

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

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

3.5.2 选择性搜索(关键字vpath

它的使用方法有三种:

1、vpath PATTERN DIRECTORIES

为符合模式“PATTERN”的文件指定搜索目录“DIRECTORIES”。多个目录使用空格或者冒号(:)分开。类似上一小节的“VPATH”

2、vpath PATTERN

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

3、vpath

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

vapth使用方法中的“PATTERN”需要包含模式字符“%”。“%”意思是匹配一个或者多个字符,例如,“%.h”表示所有以“.h”结尾的文件。

3.5.3 目录搜索的机制

make在执行时,如果通过目录搜寻得到一个过时的完整的目标文件路径名,而目标存在的目录又出现在“GPATH”变量的定义列表中,则该目标的完整路径将不废弃,目标将在该路径下被重建。

为了更清楚地描述此算法,我们使用一个例子来说明。存在一个目录“prom”,“prom”的子目录“src”下存在“sum.c”和“memcp.c”两个源文件。在“prom”目录下的Makefile部分内容如下:

LIBS = libtest.a

VPATH = src

libtest.a : sum.o memcp.o

$(AR) $(ARFLAGS) $@ $^

首先,如果在两个目录(“prom”和“src”)都不存在目标“libtest.a”,执行make时将会在当前目录下创建目标文件“libtest.a”。另外;如果“src”目录下已经存在“libtest.a”,以下两种不同的执行结果:

1) 当它的两个依赖文件“sum.c”和“memcp.c”没有被更新的情况下我们执行make,首先make程序会搜索到目录“src”下的已经存在的目标“libtest.a”。由于目标“libtest.a”的依赖文件没有发生变化,所以不会重建目标。并且目标所在的目录不会发生变化。

2) 当我们修改了文件“sum.c”或者“memcp.c”以后执行make。“libtest.a”和“sum.o”或者“memcp.o”文件将会被在当前目录下创建(目标完整路径名被废弃),而不是在“src”目录下更新这些已经存在的文件。此时在两个目录下(“prom”和“src”)同时存在文件“libtest.a”。但只有“prom/libtest.a”是最新的库文件。

当在上边的Makefile文件中使用“GPATH”指定目录时,情况就不一样了。首先看看怎么使用“GPATH”,改变后的Makefile内容如下:

LIBS = libtest.a

GPATH = src

VPATH = src

LDFLAGS += -L ./. –ltest

…….

……

同样;当两个目录都不存在目标文件“libtest.a”时,目标将会在当前目录(“prom”目录)下创建。如果“src”目录下已经存在目标文件“libtest.a”。当其依赖文件任何一个被改变以后执行make,目标“libtest.a”将会被在“src”目录下被更新(目标完整路径名不会被废弃)。

。规则命令行中的自动化变量“$^”代表所有的是的通过目录搜索得到的依赖文件的完整路径名(目录+一般文件名)列表。“$@”代表规则的目标。所以对于一个规则我们可以进行如下的描述:

foo.o : foo.c

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

我们可以使用另外一个变量来书代替“$^”,例如:

VPATH = src:../headers

foo.o : foo.c defs.h hack.h

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

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

3.5.6 库文件和搜索目录

Makefile中程序链接的静态库、共享库同样也可以有目录搜索得到。这一特性需要我们在书规则的依赖是指定一个类似“-lNAME”的依赖文件名

当规则中依赖文件列表中存在一个“-lNAME”形式的文件时。make将根据“NAME”首先搜索当前系统可提供的共享库,如果当前系统不能提供这个共享库,则搜索它的静态库

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

当规则的依赖列表中出现了“-lNAME”格式的依赖,默认搜索的文件名为“libNAME.so”和“libNAME.a”,这是由变量“.LIBPATTERNS”来指定的。“.LIBPATTERNS”的值一般是多个包含模式字符(%)的字(一个不包含空格的字符串),多个字之间使用空格分开。在规则中出现“-lNAME”格式的的依赖时,首先使用这里的“NAME”代替变量“.LIBPATTERNS”的第一个字的模式字符(%)而得到第一个库文件名,根据这个文件名在搜索目录下查找,如果能够找到、就是用这个文件,否则使用“NAME”代替第二个字的模式字符,进行同样的查找。默认情况时,“.LIBPATTERNS”的值为:“lib%.so lib%.a”。这也是默认情况下在规则存在“-lNAME”格式的依赖时,链接生成目标时使用“libNAME.so”和“libNAME.a”的原因。

变量“.LIBPATTERNS”就是告诉链接器在执行链接过程中对于出现“-LNAME”的文件如何展开。当然我们也可以将此变量制空,取消链接器对“-lNAME”格式进行展开。

3.6 Makefile伪目标

将一个目标声明为伪目标需要将它作为特殊目标.PHONY”的依赖。如下:

.PHONY : clean

 

说明:

通常在清除文件的伪目标所定义的命令中“rm”使用选项“–f”(–force)来防止在缺少删除文件时出错并退出,使“make clean”过程失败。也可以在“rm”之前加上“-”来防止“rm”错误退出,这种方式时make会提示错误信息但不会退出。为了不看到这些讨厌的信息,需要使用上述的第一种方式。

另外make存在一个内嵌隐含变量“RM”,它被定义为:“RM = rm –f”。因此在书写“clean”规则的命令行时可以使用变量“$(RM)”来代替“rm”,这样可以免出现一些不必要的麻烦!这是我们推荐的用法。

3.9 Makefile的特殊目标

.PHONY

目标“.PHONY”的所有的依赖被作为伪目标。伪目标时这样一个目标:当使用make命令行指定此目标时,这个目标所在规则定义的命令、无论目标文件是否存在都会被无条件执行。

.SUFFIXES:

特殊目标“SUFFIXES”的所有依赖指出了一系列在后缀规则中需要检查的后缀名(就是当前make需要处理的后缀)

.DEFAULT

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

.PRECIOUS

目标“.PRECIOUS”的所有依赖文件在make过程中会被特殊处理:当命令在执行过程中被中断时,make不会删除它们(可参考4.5 中断make的执行一节)。而且如果目标的依赖文件是中间过程文件,同样这些文件不会被删除。这一点目标“.PRECIOUS”和目标“.SECONDAY”实现的功能相同。参考9.4 make隐含规则链一节

另外,目标“.PRECIOUS”的依赖文件也可以是一个模式,例如“%.o”。这样可以保留有规则创建的中间过程文件。

.INTERMEDIATE

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

.SECONDARY

目标“.SECONDARY”的依赖文件被作为中间过程文件对待。但这些文件不会被自动删除(可参考9.4 make隐含规则链一节)

没有任何依赖文件的目标“.SECONDARY”的含义是:将所有的文件作为中间过程文件(不会自动删除任何文件)。

.DELETE_ON_ERROR

如果在Makefile中存在特殊目标“.DELETE_ON_ERROR”,make在执行过程中,如果规则的命令执行错误,将删除已经被修改的目标文件。参考4.4 命令执行的错误一节

.IGNORE

如果给目标“.IGNORE”指定依赖文件,则忽略创建这个文件所执行命令的错误。给此目标指定命令是没有意义的。当此目标没有依赖文件时,将忽略所有命令执行的错误。参考4.4 命令执行的错误一节

.LOW_RESOLUTION_TIME

目标“.LOW_RESOLUTION_TIME”的依赖文件被make认为是低分辨率时间戳文件。给目标“.LOW_RESOLUTION_TIME”指定命令是没有意义的。

通常文件的时间辍都是高分辨率的,make在处理依赖关系时、对规则目标-依赖文件的高分辨率的时间戳进行比较,判断目标是否过期。但是在系统中并没有提供一个修改文件高分辨率时间辍的机制(方式),因此类似“cp -p”这样的命令在根据源文件创建目的文件时,所产生的目的文件的高分辨率时间辍的细粒度部分被丢弃(来源于源文件)。可能会造成目的文件的时间戳和源文

件的相等甚至不及源文件新。处理此类命令创建的文件时,需要将命令创建的文件作为目标“.LOW_RESOLUTION_TIME”的依赖,声明这个文件是一个低分辨率时间辍的文件。例如:

.LOW_RESOLUTION_TIME: dst

dst: src

cp -p src dst

首先规则的命令“cp –p src dst”,所创建的文件“dst”在时间戳上稍稍比“src”晚(因为命令不能更新文件“dst”的细粒度时间)。因此make在判断文件依赖关系时会出现误判,将文件作为目标“.LOW_RESOLUTION_TIME”的依赖后,只要规则中目标和依赖文件的时间戳中的初始时间相等,就认为目标已经过期。这个特殊的目标主要作用是,弥补系统在没有提供修改文件高分辨率时间戳机制的情况下,某些命令在make中的一些缺陷。

对于静态库文件(文档文件)成员的更新也存在这个问题。make在创建或者更新静态库时,会自动将静态库的所有成员作为目标“.LOW_RESOLUTION_TIME”的依赖。

.SILENT

出现在目标“.SILENT”的依赖列表中的文件,make在创建这些文件时,不打印出重建此文件所执行的命令。同样,给目标“.SILENT”指定命令行是没有意义的。

没有任何依赖文件的目标“.SILENT”告诉make在执行过程中不打印任何执行的命令。现行版本make支持目标“.SILENT”的这种功能和用法是为了和旧版本的兼容。在当前版本中如果需要禁命令执行过程的打印,可以使用make的命令行参数“-s”或者“–silent”。参考8.7 make的命令行选项一节

.EXPORT_ALL_VARIABLES

此目标应该作为一个简单的没有依赖的目标,它的功能含义是将之后所有的变量传递给子make进程。参考4.6 make的递归执行一节

.NOTPARALLEL

Makefile中,如果出现目标“.NOPARALLEL”,则所有命令按照串行方式执行,即使存在make的命令行参数“-j”。但在递归调用的字make进程中,命令可以并行执行。此目标不应该有依赖文件,所有出现的依赖文件将被忽略。

所有定义的隐含规则后缀作为目标出现时,都被视为一个特殊目标,两个后缀串联起来也是如此,例如“.c.o”。这样的目标被称为后缀规则的目标,这种定义方式是已经过时的定义隐含规则的方法(目前,这种方式还被用在很多地方)。原则上,如果将其分为两个部分、并将它们加到后缀列表中,任何目标都可采用这种方式来表示。实际中,后缀通常以“.”开始,因此,以上的这些特别目标同样是以“.”开始。可参考9.7 后缀规则一节

3.11 多规则目标

extradeps=

$(objects) : $(extradeps)

它的意思是:如果我们执行“make extradeps=foo.h”那么“foo.h”将作为所有的.o文件的依赖文件。当然我们只执行“make”的话,就没有指定任何文件作为.o文件的依赖文件

3.12 静态模式

静态模式规则是这样一个规则:规则存在多个目标,并且不同的目标可以根据目标文件的名字来自动构造出依赖文件

静态模式规则的基本语法:

TARGETS …: TARGET-PATTERN: PREREQ-PATTERNS …

COMMANDS

“TAGETS”列出了此规则的一系列目标文件。

“TAGET-PATTERN”和“PREREQ-PATTERNS”说明了如何为每一个目标文件生成依赖文件。从目标模式(TAGET-PATTERN)的目标名字中抽取一部分字符串(称为“茎”)。使用“茎”替代依赖模式(PREREQ-PATTERNS)中的相应部分来产生对应目标的依赖文件。

它根据相应的.c文件来编译生成“foo.o”和“bar.o”文件:

objects = foo.o bar.o

all: $(objects)

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

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

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

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

bigoutput littleoutput : %output : text.g

generate text.g -$* > $@

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

3.13 双冒号规则

双冒号规则就是使用“::”代替普通规则的“:”得到的规则。当同一个文件作为多个规则的目标时,双冒号规则的处理和普通规则的处理过程完全不同(双冒号规则允许在多个规则中为同一个目标指定不同的重建目标的命令)。

首先需要明确的是:Makefile中,一个目标可以出现在多个规则中。但是这些规则必须是同一种规则,要么都是普通规则,要么都是双冒号规则。而不允许一个目标同时出现在两种不同的规则中。双冒号规则和普通规则的处理的不同点表现在以下几个方面:

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

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

3.14 自动产生依赖

。“GCC”支持一个“-M”的选项来实现此功能。“GCC”将自动找寻源文件中包含的头文件,并生成一个依赖关系。例如,如果“main.c”只包含了头文件“defs.h”,那么在Linxu下执行下面的命令:

gcc -M main.c

其输出是:

main.o : main.c defs.h

当不需要依赖关系中不考虑标准库头文件时,需要使用“-MM”参数

在新版本的make中,推荐的方式是为每一个源文件产生一个描述其依赖关系的makefile文件。对于一个源文件“NAME.c”,对应的这个makefile文件为“NAME.d”。“NAME.d”中描述了文件“NAME.o”所要依赖的所有头文件。采用这种方式,只有源文件在修改之后才会重新使用命令生成新的依赖关系描述文件“NAME.o”。

我们可以使用如下的模式规则来自动生成每一个.c文件对应的.d文件:

%.d: %.c

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

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

rm -f $@.$$$$

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

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

第二行;使用sed处理第二行已产生的那个临时文件并生成此规则的目标文件。这里sed完成了如下的转换过程。例如对已一个.c源文件。将编译器产生的依赖关系:

main.o : main.c defs.h

转成:

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

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

第三行;删除临时文件。

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

sources = foo.c bar.c

sinclude $(sources:.c=.d)

例子中,变量“sources”定义了当前目录下的需要编译的源文件。变量引用变换“$(sources : .c=.d)”的功能是根据需要.c文件自动产生对应的.d文件(参考5.3变量的高级用法一节),并在当前Makefile文件中包含这些.d文件。.d文件和其它的makefile文件一样,make在执行时读取并试图重建他们。其实这些.d文件也是一些可被make解析的makefile文件。

需要注意的是include指示符的书写顺序,因为在这些.d文件中已经存在规则。当一个Makefile使用指示符include这些.d文件时,应该注意它应该出现在终极目标之后,以免.d文件中的规则被是Makefile的终极规则。关于这个前面我们已经有了比较详细的讨论。

Chapter4

4.1 命令回显

如果要执行的命令行以字符“@”开始,则make在执行时这个命令就不会被回显。典型的用法是我们在使用“echo”命令输出一些信息时。如:

@echo 开始编译XXX模块……

如果使用make的命令行参数“-n”或“–just-print”,那么make执行时只显示所要执行的命令,但不会真正的去执行这些命令

而make参数“-s”或“–slient”则是禁止所有执行命令的显示,就好像所有的命令行均使用“@”开始一样。在Makefile中使用没有依赖的特殊目标“.SILENT”也可以禁止命令的回显

4.2 命令的执行

而在Makefile中书写在同一行中的多个命令属于一个完整的shell命令行,书写在独立行的一条命令是一个独立的shell命令行。所以需要注意:在一个规则的命令中,命令行“cd”改变目录不会对其后的命令的执行产生影响。就是说其后的命令执行的工作目录不会是之前使用“cd”进入的那个目录。如果要实现这个目的,就不能把“cd”和其后的命令放在两行来书写。而应该把这两条命令写在一行上,用分号分隔。这样它们才是一个完整的shell命令行。如:

foo : bar/lose

cd bar; gobble lose > ../foo

4.3 并发执行命令

GUN make可以同时执行多条命令。通常情况下,一个时刻只有一个命令在执行,下一个命令在当前命令执行完成之后才能够被执行。不过可以通过make的命令行选项“-j”或者“–job”来告诉make在同一时刻可以允许多条命令同时被执

在执行make时,如果系统运行于重负荷状态下,我们需要控制(减轻)系统在执行make时的负荷。可以使用“-l”选项告诉make限制当前运行的任务的数量(make所限制的只是它本身所需要占用的系统负载,而不能通过它去控制其它的任务所占用的系统负载)。“-l”或“–max-load”选项一般后边需要跟一个浮点数。例如:

-l 2.5

它的意思是告诉make当系统平均负荷高于2.5时,不再启动任何执行命令的子任务。不带浮点数的“-l”选项用于取消前面通“-l”给定的负荷限制。

4.4 命令执行的错误

可以使用make的命令行选项“-k”或者“–keep-going”来通知make,当出现错误时不立即退出,而是继续后续命令的执行。直到无法继续执行命令时才异常退出。例如:使用“-k”参数,在重建一个.o文件目标时出现错误,make不会立即退出。虽然make已经知道因为这个错误而无法完成终极目标的重建,但还是继续完成其它后续的依赖文件的重建。直到执行最后链接时才错误退出。

4.5 中断make的执行

make在执行命令时如果收到一个致命信号(结束make),make将会删除命令重建的规则目标文件。其依据是此目标文件的当前时间戳是否和make开始时的时间戳相同。

4.6 make的递归执行

在make的递归调用中,需要了解变量“CURDIR”,此变量代表了make当前的工作路径。如果使用“-C”选项进入一个子目录后,此变量将被重新赋值。总之,如果在Makefile中没有对此变量进行显式的赋值操作,那么它代表make的当前工作目录

在使用make的递归调用时,在Makefile中规则的命令行中应该使用变量“MAKE”来代替直接使用“make”。像上一小节的例子:

subsystem:

cd subdir && $(MAKE)

变量“MAKE”的值是“make”程序的文件名。

需要注意的是有几个特殊的命令行选项例外,分别是:“-C”、“-f”、“-o”和“-W”。这些

命令行选项不会被赋值给变量“MAKEFLAGS”。

4.6.4 -w选项

在多级make递归调用过程中,选项“-w”或者“–print-directory”可以让make在开始编译一个目录之前和完成此目录的编译之后给出相应的提示信息,方便开发人员能够跟踪make的执行过程。

Chapter5

在Makefile中变量的特征有以下几点:

1. Makefile中变量和函数的展开(除规则的命令行以外),是在make读取makefile文件时进行的,这里的变量包括了使用“=”定义和使用指示符“define”定义的。

shell中变量的引用可以是“${xx}”或者“$xx”格式。但在Makefile中多字符变量名的引用只能是“$(xx)”或者“${xx}”格式

变量的这两种不同的风格的区别在于:1. 定义方式;2. 展开时机。

第一种风格的变量就是递归方式扩展的变量。这一类型的变量的定义是通过“=”

或者使用指示符“define”(参考5.8 多行定义一节)定义的变量。

我们将它称为“直接展开”式。这种风格的变量使用“:=”来定义变量。

需要注意的是:此风格变量在定义时就完成了对所引用的变量的展开,因此它不能实现对其后定义变量的引用

5.2.4 ?=”操作符GNU make中,还有一个被称为条件赋值的赋值操作符“?=”。被称为条件赋值是因为:只有此变量在之前没有赋值的情况下才会对这个变量进行赋值。例如:

FOO ?= bar

其等价于:

ifeq ($(origin FOO), undefined)

FOO = bar

endif

含义是:如果变量“FOO”在没有定义过,就给它赋值“bar”。否则不改变它的值。

5.3 变量的高级用法

对于一个已经定义的变量,可以使用“替换引用”将其值使用指定的字符(字符串)进行替换。格式为“$(VAR:A=B)”(或者“${VAR:A=B}”),意思是,替换变量“VAR”中所有“A”字符结尾的字为“B”结尾的字。“结尾”的含义是空格之前(变量值的多个字以空格分开)。而对于变量其它部分的“A”字符不进行替换。例如:

foo := a.o b.o c.o

bar := $(foo:.o=.c)

在这个定义中,变量“bar”的值就为“a.c b.c c.c”。

另外一种引用替换的技术使用功能更强大的“patsubst”函数的所有功能。它的格式和上面“$(VAR:A=B)”的格式相类似,不过需要在这里的“A”和“B”中需要包含模式字符“%”。只是它就和“$(patsubst A,B $(VAR))”所实现功能相同。例如:

foo := a.o b.o c.o

bar := $(foo:%.o=%.c)

这个例子同样使变量“bar”的值为“a.c b.c c.c”

5.5 如何设置变量

使用“=”定义的变量称之为“递归展开”式变量;使用“:=”定义的变量称为“直接展开”式变量,“直接展开”式的变量如果其值中存在对其变量或者函数的引用,在定义时这些引用将会被进行替换展开

5.6 追加变量值

1. 如果被追加值的变量之前没有定义,那么,“+=”会自动变成“=”,此变量就被定义为一个递归展开式的变量。如果之前存在这个变量定义,那么“+=”就继承之前定义时的变量风格

2. 直接展开式变量的追加过程:变量使用“:=”定义,之后“+=”操作将会首先替换展开之前此变量的值,尔后在末尾添加需要追加的值,并使用“:=”重新给此变量赋值。

3. 递归展开式变量的追加过程:一个变量使用“=”定义,之后“+=”操作时不对之前此变量值中的任何引用进行替换展开,而是按照文本的扩展方式(之前等号右边的文本未发生变化)替换,尔后在末尾添加需要追加的值,并使用“=”给此变量重新赋值。实际的过程和上边的相类似:

variable = value

variable += more

相当于:

temp = value

variable = $(temp) more

5.7 override 指示符

变量在定义时使用了“override”,则后续对它值进行追加时,也需要使用带有“override”指示符的追加方式。否则对此变量值的追加不会生效。

5.10 目标指定变量

目标指定的变量值只在指定它的目标的上下文中有效,对于其他的目标不产生影响。就是说目标指定的变量具有“局部性”。

设置一个目标指定变量的语法为:

TARGET … : VARIABLE-ASSIGNMENT

或者:

TARGET … : override VARIABLE-ASSIGNMENT

Chapter6

1. “ifeq”表示条件语句的开始,并指定了一个比较条件(相等)。之后是用圆括号括包围的、使用逗号“,”分割的两个参数,和关键字“ifeq”用空格分开。参数中的变量引用在进行变量值比较时被展开。“ifeq”之后就是当条件满足make需要执行的,条件不满足时忽略。

2. “else”之后就是当条件不满足时的执行部分。不是所有的条件语句都需要此部分。

3. “endif”表示一个条件语句的结束,任何一个条件表达式都必须以“endif”结束

6.2.1.3 关键字“ifdef” 关键字是“ifdef”用来判断一个变量是否定义。格式为:

`ifdef VARIABLE-NAME’

如果变量“VAEIABLE_NAME”的值非空,那么表达式为真,将“TEXT-IF-TRUE”作为make要执行的一部分。否则,表达式为假

在“CONDITIONAL-DIRECTIVE”这一行上,可以以若干个空格开始,make处理时会被忽略这些空格。但不能以[Tab]字符做为开始(不然就被认为是命令)。

Chapter7

7.1 函数的调用语法GNU make函数的调用格式类似于变量的引用格式,以“$”开头表示一个引用的过程。语法格式如下:

$(FUNCTION ARGUMENTS)

或者:

${FUNCTION ARGUMENTS}

如果存在多个参数时,参数之间使用逗号“,”分割

7.2 文本处理函数以下是GNU make内嵌的文本(字符串)处理函数。

7.2.1 $(subst FROM,TO,TEXT)

函数名称:字符串替换函数—subst。

函数功能:把字串“TEXT”中的“FROM”字符替换为“TO”。

返回值:替换后的新字符串。

示例:

$(subst ee,EE,feet on the street)

替换“feet on the street”中的“ee”为“EE”,结果是新的字符串“fEEt on the strEEt”。

7.2.2 $(patsubst PATTERN,REPLACEMENT,TEXT) 函数名称:模式替换函数—patsubst。

函数功能:搜索“TEXT”中以空格分开的单词,将否符合模式“TATTERN”替换为“REPLACEMENT”。参数“PATTERN”中可以使用模式通配符“%”来代表一个单词中的若干字符。如果参数“REPLACEMENT”中也包含一个“%”,那么“REPLACEMENT”中的“%”将是“TATTERN”中的那个“%”所代表的字符串。在“TATTERN”和“REPLACEMENT”中,只有第一个“%”被作为模式字符来处理,后续的作为字符本上来处理。在两个参数中当使用第一个“%”本是字符本身时,可使用反斜杠“\”对它进行转义处理(转义处理的机制和使用静态模式的转义一致,具体可参考3.12.1 静态模式规则的语法一小节)。

返回值:替换后的新字符串。

函数说明:参数“TEXT”单词之间的多个空格在处理时被合并为一个空格,但前导和结尾空格忽略。

示例:

$(patsubst %.c,%.o,x.c.c bar.c)

它是一个简化版的“patsubst”函数在变量引用过程的实现。变量替换引用中:

$(VAR:PATTERN=REPLACEMENT)

就等于:

$(patsubst PATTERN,REPLACEMENT,$(VAR))

而另外一种更为简单的替换字符后缀的实现:

$(VAR:SUFFIX=REPLACEMENT)

它等于:

$(patsubst %SUFFIX,%REPLACEMENT,$(VAR))

7.2.3 $(strip STRINT) 函数名称:去空格函数—strip。

函数功能:去掉字串(若干单词,使用若干空字符分割)“STRINT”开头和结尾的空字符,并将其中多个连续空字符合并为一个空字符。

返回值:无前导和结尾空字符、使用单一空格分割的多单词字符串。

函数说明:空字符包括空格、[Tab]等不可显示字符。

7.2.4 $(findstring FIND,IN) 函数名称:查找字符串函数—findstring。

函数功能:搜索字串“IN”,查找“FIND”字串。

返回值:如果在“IN”之中存在“FIND”,则返回“FIND”,否则返回空。

函数说明:字串“IN”之中可以包含空格、[Tab]。搜索需要是严格的文本匹配。

7.2.5 $(filter PATTERN…,TEXT) 函数名称:过滤函数—filter。

函数功能:过滤掉字串“TEXT”中所有不符合模式“PATTERN”的单词,保留所有符合此模式的单词。可以使用多个模式。模式中一般需要包含模式字符“%”。存在多个模式时,模式表达式之间使用空格分割。

返回值:空格分割的“TEXT”字串中所有符合模式“PATTERN”的字串

7.2.6 $(filter-out PATTERN…,TEXT) 函数名称:反过滤函数—filter-out。

函数功能:和“filter”函数事项的功能相反。过滤掉字串“TEXT”中所有符合模式“PATTERN”的单词,保留所有不符合此模式的单词。可以有多个模式。存在多个模式时,模式表达式之间使用空格分割。。

返回值:空格分割的“TEXT”字串中所有不符合模式“PATTERN”的字串。

函数说明:“filter-out”函数也可以用来去除一个变量中的某些字符串,(实现和“filter”函数相反)。

7.2.7 $(sort LIST) 函数名称:排序函数—sort。

函数功能:给字串“LIST”中的单词以首字母为准进行排序(升序),并取掉重复的单词。

返回值:空格分割的没有重复单词的字串。

函数说明:两个功能,排序和去字串中的重复单词。可以单独使用其中一个功能。

7.2.8 $(word N,TEXT) 函数名称:取单词函数—word。

函数功能:取字串“TEXT”中第“N”个单词(“N”的值从1开始)。

返回值:返回字串“TEXT”中第“N”个单词。

函数说明:如果“N”值大于字串“TEXT”中单词的数目,返回空字符串。如果“N”为0,出错!

7.2.9 $(wordlist S,E,TEXT) 函数名称:取字串函数—wordlist。

函数功能:从字串“TEXT”中取出从“S”开始到“E”的单词串。“S”和“E”表示单词在字串中位置的数字。

返回值:字串“TEXT”中从第“S”到“E”(包括“E”)的单词字串。

函数说明:“S”和“E”都是从1开始的数字。

当“S”比“TEXT”中的字数大时,返回空。如果“E”大于“TEXT”字数,返回从“S”开始,到“TEXT”结束的单词串。如果“S”大于“E”,返回空

7.2.10 $(words TEXT) 函数名称:统计单词数目函数—words。

函数功能:字算字串“TEXT”中单词的数目。

返回值:“TEXT”字串中的单词数。

7.2.11 $(firstword NAMES…) 函数名称:取首单词函数—firstword。

函数功能:取字串“NAMES…”中的第一个单词。

返回值:字串“NAMES…”的第一个单词。

函数说明:“NAMES”被认为是使用空格分割的多个单词(名字)的序列。函数忽略“NAMES…”中除第一个单词以外的所有的单词。

7.3 文件名处理函数

7.3.1 $(dir NAMES…) 函数名称:取目录函数—dir。

函数功能:从文件名序列“NAMES…”中取出各个文件名目录部分。文件名的目录部分就是包含在文件名中的最后一个斜线(“/”)(包括斜线)之前的部分。

返回值:空格分割的文件名序列“NAMES…”中每一个文件的目录部分。

函数说明:如果文件名中没有斜线,认为此文件为当前目录(“./”)下的文件。

7.3.2 $(notdir NAMES…) 函数名称:取文件名函数——notdir。

函数功能:从文件名序列“NAMES…”中取出非目录部分。目录部分是指最后一个斜线(“/”)(包括斜线)之前的部分。删除所有文件名中的目录部分,只保留非目录部分。

返回值:文件名序列“NAMES…”中每一个文件的非目录部分。

函数说明:如果“NAMES…”中存在不包含斜线的文件名,则不改变这个文件名。以反斜线结尾的文件名,是用空串代替,因此当“NAMES…”中存在多个这样的文件名时,返回结果中分割各个文件名的空格数目将不确定!这是此函数的一个缺陷。

7.3.3 $(suffix NAMES…) 函数名称:取后缀函数—suffix。

函数功能:从文件名序列“NAMES…”中取出各个文件名的后缀。后缀是文件名中最后一个以点“.”开始的(包含点号)部分,如果文件名中不包含一个点号,则为空。

返回值:以空格分割的文件名序列“NAMES…”中每一个文件的后缀序列。

函数说明:“NAMES…”是多个文件名时,返回值是多个以空格分割的单词序列。如果文件名没有后缀部分,则返回空。

7.3.4 $(basename NAMES…) 函数名称:取前缀函数—basename。

函数功能:从文件名序列“NAMES…”中取出各个文件名的前缀部分(点号之后的部分)。前

缀部分指的是文件名中最后一个点号之前的部分。

返回值:空格分割的文件名序列“NAMES…”中各个文件的前缀序列。如果文件没有前缀,则返回空字串。

函数说明:如果“NAMES…”中包含没有后缀的文件名,此文件名不改变。如果一个文件名中存在多个点号,则返回值为此文件名的最后一个点号之前的文件名部分。

7.3.5 $(addsuffix SUFFIX,NAMES…) 函数名称:加后缀函数—addsuffix。

函数功能:为“NAMES…”中的每一个文件名添加后缀“SUFFIX”。参数“NAMES…”为空格分割的文件名序列,将“SUFFIX”追加到此序列的每一个文件名的末尾。

返回值:以单空格分割的添加了后缀“SUFFIX”的文件名序列。

7.3.6 $(addprefix PREFIX,NAMES…) 函数名称:加前缀函数—addprefix。

函数功能:为“NAMES…”中的每一个文件名添加前缀“PREFIX”。参数“NAMES…”是空格分割的文件名序列,将“SUFFIX”添加到此序列的每一个文件名之前。

返回值:以单空格分割的添加了前缀“PREFIX”的文件名序列。

7.3.7 $(join LIST1,LIST2) 函数名称:单词连接函数——join。

函数功能:将字串“LIST1”和字串“LIST2”各单词进行对应连接。就是将“LIST2”中的第一个单词追加“LIST1”第一个单词字后合并为一个单词;将“LIST2”中的第二个单词追加到“LIST1”的第一个单词之后并合并为一个单词,……依次列推。

返回值:单空格分割的合并后的字(文件名)序列。

函数说明:如果“LIST1”和“LIST2”中的字数目不一致时,两者中多余部分将被作为返回序列的一部分。

示例1:

$(join a b , .c .o)

返回值为:“a.c b.o”。

示例2:

$(join a b c , .c .o)

返回值为:“a.c b.o c”。

7.3.8 $(wildcard PATTERN) 函数名称:获取匹配模式文件名函数—wildcard

函数功能:列出当前目录下所有符合模式“PATTERN”格式的文件名。

返回值:空格分割的、存在当前目录下的所有符合模式“PATTERN”的文件名。

函数说明:“PATTERN”使用shell可识别的通配符,包括“?”(单字符)、“*”(多字符)等。可参考3.4 文件名中使用通配符一节。

示例:

$(wildcard *.c)

返回值为当前目录下所有.c源文件列表。

7.4 foreach 函数函数“foreach”不同于其它函数。它是一个循环函数。类似于Linux的shell中的循环(for语句)。

“foreach”函数的语法:

$(foreach VAR,LIST,TEXT)

函数功能:这个函数的工作过程是这样的:如果必要(存在变量或者函数的引用),首先展开变量“VAR”和“LIST”;而表达式“TEXT”中的变量引用不被展开。执行时把“LIST”中使用空格分割的单词依次取出赋值给变量“VAR”,然后执行“TEXT”表达式。重复直到“LIST”的最后一个单词(为空时结束)。“TEXT”中的变量或者函数引用在执行时才被展开,因此如果在“TEXT”中存在对“VAR”的引用,那么“VAR”的值在每一次展开式将会到的不同的值。

返回值:空格分割的多次表达式“TEXT”的计算的结果。

7.5 if 函数

函数语法:

$(if CONDITION,THEN-PART[,ELSE-PART])

函数功能:第一个参数“CONDITION”,在函数执行时忽略其前导和结尾空字符并展开。“CONDITION”的展开结果非空,则条件为真,就将第二个参数“THEN_PATR”作为函数的计算表达式,函数的返回值就是第二表达式的计算结果;“CONDITION”的展开结果为空,将第三个参数“ELSE-PART”作为函数的表达式,返回结果为第三个表达式的计算结果。

返回值:根据条件决定函数的返回值是第一个或者第二个参数表达式的计算结果。当不存在第三个参数“ELSE-PART”,并且“CONDITION”展开为空,函数返回空。

函数说明:函数的条件表达式“CONDITION”决定了,函数的返回值只能是“THEN-PART”或者“ELSE-PART”两个之一的计算结果。

函数示例:

SUBDIR += $(if $(SRC_DIR) $(SRC_DIR),/home/src)

7.6 call函数“call”函数是唯一一个可以创建定制参数化的函数的引用函数。我们可以将一个变量定义为一个复杂的表达式,用“call”函数根据不同的参数对它进行展开来获得不同的结果。

函数语法:

$(call VARIABLE,PARAM,PARAM,…)

函数功能:在执行时,将它的参数“PARAM”依次赋值给临时变量“$(1)”、“$(2)”(这些临时变量定义在“VARIABLE”的值中,参考下边的例子)…… call函数对参数的数目没有限制,也可以没有参数值,没有参数值的“call”没有任何实际存在的意义。执行时变量“VARIABLE”被展开为在函数上下文有效的临时变量,变量定义中的“$(1)”作为第一个参数,并将函数参数值中的第一个参数赋值给它;变量中的“$(2)”一样被赋值为函数的第二个参数值;依此类推(变量$(0)代表变量“VARIABLE”本身)。之后对变量“VARIABLE” 表达式的计算值。

返回值:参数值“PARAM”依次替换“$(1)”、“$(2)”…… 之后变量“VARIABLE”定义的表达式的计算值。

函数说明:1. 函数中“VARIBLE”是一个变量名,而不是对变量的引用。因此,通常“call”函数中的“VARIABLE”中不包含“$”(当然,除了此变量名是一个计算的变量名)。2. 当变量“VARIBLE”是一个make内嵌的函数名时(如“if”、“foreach”、“strip”等),对“PARAM”参数的使用需要注意,因为不合适或者不正确的参数将会导致函数的返回值难以预料。3. 函数中多个“PARAM”之间使用逗号分割。4. 变量“VARIABLE”在定义时不能定义为直接展开式!只能定义为递归展开式。

函数示例: 首先,来看一个简单的例子。

示例1:

reverse = $(2) $(1)

foo = $(call reverse,a,b)

变量“foo”的值为“ba”。这里变量“reverse”中的参数定义顺序可以根据需要来调整,并不是需要按照“$(1)”、“$(2)”、“$(3)”…… 这样的顺序来定义。

看一个稍微复杂一些的例子。我们定义了一个宏“pathsearch”来在“PATH”路径中搜索第一个指定的程序。

示例2:

pathsearch = $(firstword $(wildcard $(addsuffix /$(1),$(subst :, ,$(PATH)))))

LS := $(call pathsearch,ls)

变量“LS”的结果为“/bin/sh”。执行过程:函数“subst”将环境变量“PATH”转换为空格分割的搜索路径列表;“addsuffix”构造出可能的可执行程序“$(1)”(这里是“ls”)带路径的完整文件名(如:“/bin/$(1)”),之后使用函数“wildcard”匹配,最后“firstword”函数取第一个文件名。

函数“call”以可以套嵌使用。每一层“call”函数的调用都为它自己的局部变量“$(1)”等赋值,覆盖上一层函数为它所赋的值。

示例3:

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

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

那么变量“o”的值就为“file file default”。我们这里使用了origin函数。我们分析函数的执行过程:首先,“o=$(call map,origin, o map MAKE)”这个函数调用使用了变量“map”所定义的表达式;使用内嵌函数名“origin”作为它的第一个参数值,使用Makefile中的变量“o map MAKE”作为他的第二个参数值。当使用“call”函数每一的展开后等价于“$(foreach a,o map MAKE,$(origin $(a)))”。

注意:和其它函数一样,“call”函数会保留出现在其参数值列表中的空字符。因此在使用参数值时对空格处理要格外小心。如果参数中存在多余的空格,函数可能会返回一个莫名奇妙的值。为了安全,在变量作为“call”函数参数值之前,应去掉其值中的多余空格(可以使用strip函数)。

7.7 value函数函数“value”提供了一种在不对变量进行展开的情况下获取其值的方式。注意:并不是说函数会取消之前已经执行过的替换扩展。比如:我们定义了一个直接展开式的变量,此变量在定义过程中对其它变量的引用进行替换展开得到自身的值。我们在使用“value”函数取它的值时,得到的是不包含任何引用的实际值。而不是将定义过程中的替换展开动作取消后包含引用的定义值。就是说它不能取消此变量中已经发生了的替换展开动作。

函数语法:

$(value VARIABLE)

函数功能:不对变量“VARIBLE”进行任何展开操作,直接返回变量“VARIBALE”代表的值。这里“VARIABLE”是一个变量名,一般不包含“$”(当然,除了计算的变量名),

返回值:变量“VARIBALE”所定义文本值(不展开其中的变量或者函数应用)。

函数说明:

示例:

# sample Makefile

FOO = $PATH

all:

@echo $(FOO)

@echo $(value FOO)

执行make时,我们可以看到的结果是:第一行为:“ATH”。这是因为变量“FOO”定义为“$PATH”,所以展开为“ATH”(“$P”为空,参考 5.1 变量的引用一节)。

第二行才是我们需要显示的系统环境变量“PATH”的值(value函数得到变量“FOO”的值为“$PATH”)。

7.8 eval函数

函数功能:函数“eval”是一个比较特殊的函数。使用它我们可以在我们的Makefile中构造一个可变的规则结构关系(依赖关系链),其中可以使用其它变量和函数。函数“eval”对它的参数进行展开,展开的结果作为Makefile的一部分,make可以对展开内容进行语法解析。展开的结果可以包含一个新变量、目标、隐含规则或者是明确规则等。也就是说此函数的功能主要是:根据其参数的关系、结构,对它们进行替换展开。

返回值:函数“eval”的返回值时空,也可以说没有返回值。

函数说明:“eval”函数执行时会对它的参数进行两次展开。第一次展开过程发是由函数本身完成的,第二次是函数展开后的结果被作为Makefile内容时由make解析时展开的。明确这一点对于使用“eval”函数非常重要。在理解了函数“eval”二次展开的过程后。实际使用时,当函数的展开结果中存在引用(格式为:$(x))时,那么在函数的参数中应该使用“$$”来代替“$”(参考5.1 变量的引用一节)。因为这一点,所以通常它的参数中会使用函数value来取一个变量的文本值。

我们看一个例子。例子看起来似乎非常复杂,因为它综合了其它的一些概念和函数。不过我们可以考虑两点:其一,通常实际一个模板的定义可能比例子中的更为复杂;其二,我们可以实现一个复杂通用的模板,在我们的所有Makefile中包含它,以可作到一劳永逸。相信这一点可能是大多数程序员所推崇的。

示例:

# sample Makefile

PROGRAMS = server client

server_OBJS = server.o server_priv.o server_access.o

server_LIBS = priv protocol

client_OBJS = client.o client_api.o client_mem.o

client_LIBS = protocol

# Everything after this is generic

.PHONY: all

all: $(PROGRAMS)

define PROGRAM_template

$(1): $$($(1)_OBJ) $$($(1)_LIBS:%=-l%)

ALL_OBJS += $$($(1)_OBJS)

endef

$(foreach prog,$(PROGRAMS),$(eval $(call PROGRAM_template,$(prog))))

$(PROGRAMS):

$(LINK.o) $^ $(LDLIBS) -o $@

clean:

rm -f $(ALL_OBJS) $(PROGRAMS)

我们来看一下这个例子:它实现的功能是完成“PROGRAMS”的编译链接。例子中“$(LINK.o)”为“$(CC) $(LDFLAGS)”,意思是对所有的.o文件和指定的库文件进行链接。可参考9.2 make隐含规则一览一节

$(foreach prog,$(PROGRAM),$(eval $(call PROGRAM_template,$(prog))))”展开为:

server : $(server_OBJS) –l$(server_LIBS)

client : $(client_OBJS) –l$(client_LIBS)

7.9 origin函数函数“origin”和其他函数不同,函数“origin”的动作不是操作变量(它的参数)。它只是获取和此变量(参数)相关的信息,告诉我们这个变量的出处(定义方式)。

函数语法:

$(origin VARIABLE)

函数功能:函数“origin”查询参数“VARIABLE”(通常是一个变量名)的出处。

函数说明:“VARIABLE”是一个变量名而不是一个变量的引用。因此通常它不包含“$”(当然,计算的变量名例外)。

返回值:返回“VARIABLE”的定义方式。用字符串表示。

函数的返回情况有以下几种:

1. undefined 变量“VARIABLE”没有被定义。

2. default

变量“VARIABLE”是一个默认定义(内嵌变量)。如“CC”、“MAKE”、“RM”等变量(参考9.3 隐含变量一节)。如果在Makefile中重新定义这些变量,函数返回值将相应发生变化。

3. environment

变量“VARIABLE”是一个系统环境变量,并且make没有使用命令行选项“-e”(Makefile中不存在同名的变量定义,此变量没有被替代)。参考8.7 make的命令行选项一节

4. environment override

变量“VARIABLE”是一个系统环境变量,并且make使用了命令行选项“-e”。Makefile

中存在一个同名的变量定义,使用“make -e”时环境变量值替代了文件中的变量定义。

5. file 变量“VARIABLE”在某一个makefile文件中定义。

6. command line

变量“VARIABLE”在命令行中定义。

7. override

变量“VARIABLE”在makefile文件中定义并使用“override”指示符声明。

8. automatic

变量“VARIABLE”是自动化变量。参考9.5.3 自动化变量一节

函数“origin”返回的关于变量的信息对我们书写Makefile是相当有用的,可以使我们在使用一个变量之前对它的值的合法性进行判断。假设在Makefile其包了另外一个名为bar.mk的makefile文件。我们需要在bar.mk中定义变量“bletch”(无论它是否是一个环境变量),保证“make –f bar.mk”能够正确执行。另外一种情况,当Makefile包含bar.mk,在Makefile包含bar.mk之前有同样的变量定义,但是我们不希望覆盖bar.mk中的“bletch”的定义。一种方式是:我们在bar.mk中使用指示符“override”声明这个变量。但是它所存在的问题时,此变量不能被任何方式定义的同名变量覆盖,包括命令行定义。另外一种比较灵活的实现就是在bar.mk中使用“origin”函数,如下:

ifdef bletch

ifeq “$(origin bletch)” “environment”

bletch = barf, gag, etc.

endif

endif

这里,如果存在环境变量“bletch”,则对它进行重定义。

ifneq “$(findstring environment,$(origin bletch))” “”

bletch = barf, gag, etc.

endif

这个例子实现了:即使环境变量中已经存在变量“bletch”,无论是否使用“make -e”来执行Makefile,变量“bletch”的值都是“barf,gag,etc”(在Makefile中所定义的)。环境变量不能替代文件中的定义。

如果“$(origin bletch)”返回“environment”或“environment override”,都将对变量“bletch”重新定义。关于函数“firststring”可参考7.2 文本处理函数一节

7.10 shell函数

shell函数不同于除wildcard函数之外的其它函数。make可以使用它来和外部通信。

函数功能:函数“shell”所实现的功能和shell中的引用(“)相同。实现了命令的扩展。意味着需要一个shell 命令作为它的参数,而返回的结果是此命令在shell中的执行结果。make仅仅对它的回返结果进行处理;make将函数的返回结果中的所有换行符(“\n”)或者一对“\n\r”替换为单空格;并去掉末尾的回车符号(“\n”)或者“\n\r”。函数展开式时,它所调用的命令(它的参数)得到执行。(可参考2.9 make如何解析makefile 一节)。除了对它的引用出现在规则的命令行中和递归的变量定义引用以外,其它决大多数情况下,make在读取Makefile时函数shell就被扩展。

返回值:函数“shell”的参数在shell中的执行结果。

函数说明:函数本身的返回值是其参数的执行结果,没有进行任何处理。对结果的处理是由make进行的。当对函数的引用出现在规则的命令行中,命令行在执行时函数引用才被展开。展开过程函数参数的执行时在另外一个shell进程中完成的,因此对于出现在规则命令行的多级“shell”函数引用需要谨慎处理,否则会影响效率(每一级的“shell”函数的参数都会有各自的shell进程)。示例1:

contents := $(shell cat foo)

将变量“contents”赋值为文件“foo”的内容,文件中的行在变量中使用空格

7.11.1 $(error TEXT…)

函数功能:产生致命错误,并提示“TEXT…”信息给用户,之后退出make的执行。需要说明的是:“error”函数是在函数展开式(函数被调用时)才提示信息并结束make进程。因此如果函数出现在命令中或者一个递归的变量定义中时,在读取Makefile时不会出现错误。而只有包含“error”函数引用的命令被执行,或者定义中引用此函数的递归变量被展开时,才会提示致命信息“TEXT…”同时make退出执行。

返回值:空字符

函数说明:“error”函数一般不出现在直接展开式的变量定义中,否则在make读取Makefile时将会提示致命错误。

7.11.2 $(warning TEXT…)

函数功能:函数“warning”类似于函数“error”,区别在于它不会导致致命错误(make不退出),而只是提示“TEXT…”,make的执行过程继续。

返回值:空字符

函数说明:用法和“error”类似,展开过程相同。

Chapter8

make的退出状态有三种:

0 — 状态为0时,表示执行成功。

2 — 执行过程出现错误,同时会提示错误信息。

1 — 在执行make时使用了“-q”参数,而且在当前存在过时的目标文件。

all 作为Makefile的顶层目标,一般此目标作为默认的终极目标。

clean

这个伪目标定义了一组命令,这些命令的功能是删除所有由make创建的文件。

mostlyclean

和“clean”伪目标功能相似。区别在于它所定义的删除命令不会全部删除由make生成的文件。比如说不需要删除某些库文件。

distclean

realclean

clobber

同样类似于伪目标,只是它们所定义的删除命令所删除的文件更多。可以包含非make创建的文件。例如:编译之前系统的配置文件、链接文件等。

install 将make成功创建的可执行文件拷贝到shell 环境变量“PATH”指定的某个目录。典型的,应用可执行文件被拷贝到目录“/usr/local/bin”,库文件拷贝到目录“/usr/local/lib”目录下。

print

打印出所有被更改的源文件列表。

tar

创建一个tar文件。

shar 创建一个源代码的shell文档(shar文件)。

dist

为源文件创建发布的压缩包,可以使各种压缩方式的发布包。

TAGS

更新一个工程的“tags”列表。

check

test

对Makefile最后生成的文件进行检查。

8.6 使用make进行编译测试

需要使用make的“-k”或者“–keep-going”命令行选项。这个参数的功能是告诉make当出现错误时继续执行,直到最后出现致命错误(无法重建终极目标)才返回非0并退出。例如:当编译一个.o目标文件时出现错误,如果使用“make -k”执行,make将不会在检测到这个错误时退出(虽然已经知道终极目标是不可能会重建成功的);只是给出一个

错误消息,make将继续重建其它需要重建的目标文件;直到最后出现致命错误才退出。在没有使用“-k”或者“—keep-going”时,make在检测到错误时会立刻退出。

Chapter9

9.3.1 代表命令的变量AR

函数库打包程序,可创建静态库.a文档。默认是“ar”。

AS

汇编程序。默认是“as”。

CC

C编译程序。默认是“cc”。

CXX

C++编译程序。默认是“g++”。

CO

从 RCS中提取文件的程序。默认是“co”。

CPP

C程序的预处理器(输出是标准输出设备)。默认是“$(CC) –E”。

FC

编译器和预处理Fortran 和 Ratfor 源文件的编译器。默认是“f77”。

GET

从SCCS中提取文件程序。默认是“get”。

LEX

将 Lex 语言转变为 C 或 Ratfo 的程序。默认是“lex”。

PC

Pascal语言编译器。默认是“pc”。

YACC

Yacc文法分析器(针对于C程序)。默认命令是“yacc”。

YACCR

Yacc文法分析器(针对于Ratfor程序)。默认是“yacc –r”。

MAKEINFO

转换Texinfo源文件(.texi)到Info文件程序。默认是“makeinfo”。

TEX

从TeX源文件创建TeX DVI文件的程序。默认是“tex”。

TEXI2DVI

从Texinfo源文件创建TeX DVI 文件的程序。默认是“texi2dvi”。

WEAVE

转换Web到TeX的程序。默认是“weave”。

CWEAVE

转换C Web 到 TeX的程序。默认是“cweave”。

TANGLE

转换Web到Pascal语言的程序。默认是“tangle”。

CTANGLE

转换C Web 到 C。默认是“ctangle”。

RM

删除命令。默认是“rm –f”。

9.3.2 命令参数的变量下边的是代表命令执行参数的变量。如果给出默认值则默认值为空。

ARFLAGS

执行“AR”命令的命令行参数。默认值是“rv”。

ASFLAGS

执行汇编语器“AS”的命令行参数(明确指定“.s”或“.S”文件时)。

CFLAGS

执行“CC”编译器的命令行参数(编译.c源文件的选项)。

CXXFLAGS

执行“g++”编译器的命令行参数(编译.cc源文件的选项)。

COFLAGS

执行“co”的命令行参数(在RCS中提取文件的选项)。

CPPFLAGS

执行C预处理器“cc -E”的命令行参数(C 和 Fortran 编译器会用到)。

FFLAGS

Fortran语言编译器“f77”执行的命令行参数(编译Fortran源文件的选项)。

GFLAGS

SCCS “get”程序参数。

LDFLAGS

链接器参数。(如:“ld”)

LFLAGS

Lex文法分析器参数。

PFLAGS

Pascal语言编译器参数。

RFLAGS

Ratfor 程序的Fortran 编译器参数。

YFLAGS

Yacc文法分析器参数。

9.5.3 自动化变量

在模式规则中,规则的目标和依赖文件名代表了一类文件名。命令是对所有这一类文件重建过程的描述,显然,在命令中不能指定特定的文件名,否则模式规则将没有了意义。那么在模式规则的命令行中该如何表示文件,将成我们这一小节的讨论重点。make中使用了“自动环变量”来实现这个目的,自动化变量的取值是根据具体的规则决定的,就是说对不同的规则其所代表的文件名不同。

前边我们也看到了很多例子中使用到了自动化变量。下面对所有的自动化变量进行说明:

$@

代表规则中的目标文件名。如果目标是一个文档(Linux中,一般称.a文件为文档),那么它代表这个文档的文件名。在多目标的模式规则中,它代表的是哪个触发规则被执行的目标文件名。

$%

规则的目标文件是一个静态库文件时,代表静态库的一个成员名。例如,规则的目标是“foo.a(bar.o)”,那么,“$%”的值就为“bar.o”,“$@”的值为“foo.a”。如果目标不是函数库文件,其值为空。

$<

规则的第一个依赖文件名。如果是隐含规则,则它代表通过目标指定的第一个依赖文件。

$?

所有比目标文件更新的依赖文件列表,空格分割。如果目标是静态库文件名,代表的是库成员(.o文件)的更新情况。

$^

规则的所有依赖文件列表,使用空格分隔。如果目标是静态库文件名,它所代表的只能是所有库成员(.o文件)名。一个文件可重复的出现在目标的依赖中,变量“$^”只记录它的一次引用情况。就是说变量“$^”会去掉重复的依赖文件。

$+

类似“$^”,但是它保留了依赖文件中重复出现的文件。主要用在程序链接时,库的交叉引用场合。

$*

在模式规则和静态模式规则中,代表“茎”。“茎”是目标模式中“%”所代表的部分(当文件名中存在目录时,“茎”也包含目录(斜杠之前)部分,可参考9.5.4 模式的匹配一小节)。例如:文件“dir/a.foo.b”,当目