第一章 makefile概述
Make 可自动决定一个大程序中哪些文件需要重新编译,并发布重新编译它们的命令。Make是一种帮助大型软件工程的编译工作实现自动化的编程语言。正确地使用Make可以大大减少因编译程序而花费的时间,因为它可以消除不必要的再编译。Make的基本设计思想是如果目标文件是在最近一次对源文件的修改之后编译的,它就是“新的”,不需要重新编译;如果最近一次对源文件的修改之后没有及时更新目标文件,那么该目标文件就是“旧的”,需要重新编译。make编译系统依据的信息来源于称为makefile文件的数据库
1.1 makefile的由来和发展
Make 程序最初设计是为了维护C程序文件防止不必要的重新编译。在使用命令行编译器的时候,修改了一个工程中的头文件,如何确保包含这个头文件的所有文件都得到编译?现在实际的版本生成是使用批处理程序,编译那些文件依赖于程序的维护者,在模块之间相互引用头文件的情况下,要将所有需要重新编译的文件找出来是一件痛苦的事情;在找到这些文件之后,修改批处理进行编译。实际上这些工作可以让make程序来自动完成,make工具对于维护一些具有相互依赖关系的文件特别有用,它对文件和命令的联系(在文件改变时调用来更新其它文件的程序)提供一套编码方法。Make工具的基本概念类似于Proglog语言,你告诉make需要做什么,提供一些规则,make来完成剩下的工作。
1.2 makefile文件介绍
Make程序需要一个所谓的Makefile文件来告诉它干什么。该文件描述程序中各个文件之间的相互关系,并且提供每一个文件的更新命令。在一个程序中,可执行程序文件的更新依靠OBJ文件,而OBJ文件是由源文件编译得来的,一旦合适的Makefile文件存在,每次更改一些源文件,在shell命令下简单的键入:
make
就能执行所有的必要的重新编译任务。Make程序根据Makefile文件中的数据和每个文件更改的时间戳决定哪些文件需要更新。对于这些需要更新的文件,Make基于Makefile文件发布命令进行更新,进行更新的方式由提供的命令行参数控制。。
1.3 makefile的特点
Make并不是仅仅能够处理C语言程序,它可以处理那些编译器能够在Shell命令下运行的的各种语言的程序。事实上,GNU Make不仅仅限于程序,它可以适用于任何如果一些文件变化导致另外一些文件必须更新的任务。
Makefile可以手动编写,也可以自动生成,如采用GNU Autoconf和Automake
我们这里说的Makefile 是指gnu makefile。GNU工程开始於一九八四年,旨在发展一个「类-Unix」且为 自由软件的完整操作系统: GNU 系统。各种使用 Linux 作为内核的 GNU 操作系统正被广泛地使用著;虽然这些系统通常被称作为“Linux”,但是它们应该更精确地被称为 GNU/Linux系统 。gnu系统使用的编译器为gcc(GNU Compiler Collection)编译器,vxworks操作系统使用的也是gnu makefile和gcc编译器,但是psos操作系统使用的就是diab 的编译器,和相应的makefile。
虽然makefile也可以用在除了编译以外的其他工作,例如代码走查等,但是makefile的真正用途是大型软件工程的编译工作。在大型的项目中,如果将原代码全部重新编译一边的话,意味着很长的时间,可能是几个小时,如果只编译改动相关的文件,编译时间将大为缩短。编译器和连接器将根据makefile来连接和编译相应的目标,在大型软件中软件将采用子系统和模块化的开发方法,可以单独编译没一个模块,这将极大的方便维护和定位问题。
第二章 规则
2.1 规则的语法
通常一条规则形式如下:
目标(target)…: 依赖(prerequiries)…
命令(command)
…
或
目标(target)…: 依赖(prerequiries)…;命令(command)
命令(command)
…
目标(target)通常是要产生的文件的名称,也可是一个执行的动作名称,中间由空格隔开。通配符可以在文件名中使用,一般情况下一条规则只有一个目标,但一条规则也可以有多个目标
依赖是用来输入从而产生目标的文件,一个目标经常有几个依赖。依赖也由文件名构成,文件名之间由空格隔开,通配符和档案成员也允许在依赖中出现。
命令是Make执行的动作,一个规则可以含有几个命令,每个命令占一行。命令行以Tab字符开始,第一个命令可以和依赖在一行,命令和依赖之间用分号隔开,也可以在依赖下一行,以Tab字符为行的开始。这两种方法的效果一样
您可以把一长行在中间插入‘\’使其分为两行,也就是说,一行的尾部是’\’的话,表示下一行是本行的继续行。但这并不是必须的,make没有对makefile文件中行的长度进行限制。
规则一般是用于解释怎样和何时重建特定文件的,这些特定文件是这个详尽规则的目标。Make需首先调用命令对依赖进行处理,进而才能创建或更新目标。当然,一个规则也可以是用于解释怎样和何时执行一个动作。
一条规则可以告诉make两件事情:何时目标已经过时,以及怎样在必要时更新它们。
判断目标过时的准则和依赖关系密切,一个目标如果不存在或它比其中一个依赖的修改时间早,则该目标已经过时。该思想来源于目标是根据依赖的信息计算得来的,因此一旦任何一个依赖发生变化,目标文件也就不再有效。目标的更新方式由命令决定。命令由shell解释执行,。但是,指定命令更新目标的规则并不都需要依赖。
2.2 具体规则
具体规则:用于阐述什么时间或怎样重新生成称为规则目标的一个或多个文件的。它列举了目标所依靠的文件,这些文件称为该目标的依赖。具体规则可能同时提供了创建或更新该目标的命令。
2.2.1 包含通配符的规则
Make中的通配符和Bourne shell中的通配符一样是 ‘*’、‘?’和‘[…]’。如果通配符前面是反斜杠‘\’,则该通配符失去通配能力,如‘foo\*bar’表示一个特定的文件其名字由‘foo’、‘*’和‘bar’构成。
字符‘~’在文件名的前面也有特殊的含义。如果字符‘~’单独或后面跟一个斜杠‘/’,则代表您的home目录。如‘~/bin’扩展为‘/home/bin’。如果字符‘~’后面跟一个字,它扩展为home目录下以该字为名字的目录,如‘~John/bin’表示‘home/John/bin’。在一些操作系统(如ms-dos,ms-windows)中不存在home目录,可以通过设置环境变量home来模拟。
在目标、依赖和命令中的通配符自动扩展。在其它上下文中,通配符只有在您明确表明调用通配符函数时才扩展。
下面是两个使用通配符的例子。
通配符可以用在规则的命令中,此时通配符由shell扩展。例如,下面的规则删除所有OBJ文件:
clean:
rm –f *.o
通配符在规则的依赖中也很有用。在下面的makefile规则中,‘make print’将打印所有从上次您打印以后又有改动的‘.c’文件:
print: *.c
lpr -p $?
touch print
本规则使用‘ptint’作为一个空目标文件;自动变量‘$?’用来打印那些已经修改的文件。
其中第一个例子,非常具有实用价值。
在规则中可以直接使用通配符,但是不能用在定义变量时使用通配符,再在规则中使用变量的方法。实际上在定义变量时一般不会采用通配符,在定义变量时要使用通配符,就要使用函数wildcard。
这是因为当您定义一个变量时通配符不会扩展,如果您这样写:
objects = *.o
变量objects的值实际就是字符串‘*.o’。然而,如果您在一个目标、依赖和命令中使用变量objects的值,通配符将在那时扩展。
下面这个例子会描述这种用法的问题,实际上该例子不能完成您所希望完成的任务。假设可执行文件‘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风格的前斜杠。
2.2.2 没有命令和依赖的规则
如果一个规则没有依赖、也没有命令,而且这个规则的目标也不是一个存在的文件,则make认为只要该规则运行,该目标就已被更新。这意味着,所有以这种规则的目标为依赖的目标,它们的命令将总被执行。这里举一个例子:
clean: FORCE
rm $(objects)
FORCE:
这里的目标‘FORCR’满足上面的特殊条件,所以以其为依赖的目标‘clean’将总强制它的命令执行。关于‘FORCR’的名字没有特别的要求,但‘FORCR’是习惯使用的名字。
也许您已经明白,使用‘FORCR’的方法和使用假想目标(.PHONY: clean)的结果一样,但使用假想目标更具体更灵活有效,由于一些别的版本的make不支持假想目标,所以‘FORCR’出现在许多makefile文件中。
在实际应用中这种规则十分常用,但可能不叫‘FORCE’这个名字,包括在支持假想目标的make 版本中。
2.2.3 具有多个目标的规则
具有多个目标的规则等同于写多条规则,这些规则除了目标不同之外,其余部分完全相同。相同的命令应用于所有目标,但命令执行的结果可能有所差异,因此您可以在命令中使用‘$@’分配不同的实际目标名称。这条规则同样意味着所有的目标有相同的依赖。
在以下两种情况下具有多个目标的规则相当有用:
? 您仅仅需要依赖,但不需要任何命令。例如:
kbd.o command.o files.o: command.h
为三个提及的目标文件给出附加的共同依赖。
? 所有的目标使用相同的命令。但命令的执行结果未必完全相同,因为自动变量‘$@’可以在重建时指定目标(参阅自动变量)。例如:
bigoutput littleoutput : text.g
generate text.g -$(subst output,,$@) > $@
等同于:
bigoutput : text.g
generate text.g -big > bigoutput
littleoutput : text.g
generate text.g -little > littleoutput
如果您喜欢根据目标变换依赖,象使用变量‘$@’变换命令一样。您不必使用具有多个目标的规则,您可以使用静态格式规则。
2.2.4 静态格式规则
静态格式规则是指定多个目标并能够根据每个目标名构造对应的依赖名的规则。静态格式规则在用于多个目标时比平常的规则更常用,因为目标可以不必有完全相同的依赖;也就是说,这些目标的依赖必须类似,但不必完全相同。
2.2.4.1 静态格式规则的语法
静态格式规则的语法格式:
targets ...: target-pattern: dep-patterns ...
commands
...
目标列表(targets)指明该规则应用的目标。目标可以含有通配符,具体使用和平常的目标规则基本一样。
目标格式(target-pattern)和依赖格式(dep-patterns)来表明目标的依赖关系如何计算:匹配目标格式(target-pattern)的目标从名字中抽出一部分,叫做词根,词根被替换到依赖格式(dep-patterns)来形成依赖文件名。
每一个格式通常包含字符‘%’。目标格式匹配目标时,‘%’可以匹配目标名中的任何字符串;这部分匹配的字符串称为词根;剩下的部分必须完全相同。如目标‘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) $< -o $@
这里‘$<’是自动变量,控制依赖的名称,‘$@’也是自动变量,掌握目标的名称。
每一个指定目标必须和目标格式匹配,如果不符则产生警告。如果您有一列文件,仅有其中的一部分和目标格式匹配,您可以使用filter函数把不符合的文件移走:
files = foo.elc bar.o lose.o
$(filter %.o,$(files)): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
emacs -f batch-byte-compile $<
在这个例子中,‘$(filter %.o,$(files))'的结果是‘bar.o lose.o',第一个静态格式规则是将相应的C语言源文件编译为OBJ文件,‘$(filter %.elc,$(files))' 的结果是‘foo.elc'。它依赖的文件是‘foo.el’
另一个例子是阐明怎样在静态格式规则中使用‘$*’:
bigoutput littleoutput : %output : text.g
generate text.g -$* > $@
当命令generate执行时,$*扩展为词根,即‘big’或‘little’二者之一。
2.2.5 双冒号规则
双冒号规则是在目标名后使用‘::’代替‘:’的规则。当同一个目标在一条以上的规则中出现时,双冒号规则和平常的规则处理有所差异。
当一目标在多条规则中出现时,所有的规则必须是同一类型:要么都是双冒号规则,要么都是普通规则。如果他们都是双冒号规则,则它们之间都是相互独立的。如果目标比一个双冒号规则的依赖‘旧’,则该双冒号规则的命令将执行。这可导致具有同一目标双冒号规则全部或部分执行。
双冒号规则实际就是将具有相同目标的多条规则相互分离,每一条双冒号规则都独立的运行,就像这些规则的目标不同一样。
对于一个目标的双冒号规则按照它们在makefile文件中出现的顺序执行。然而双冒号规则真正有意义的场合是双冒号规则和执行顺序无关的场合。
双冒号规则有点模糊难以理解,它仅仅提供了一种在特定情况下根据引起更新的依赖文件不同,而采用不同方式更新目标的机制。实际应用双冒号规则的情况非常罕见。
每一个双冒号规则都应该指定命令,如果没有指定命令,则会使用隐含规则。
双冒号规则用的不多。
2.3 隐含规则
隐含规则:用于阐述什么时间或怎样重新生成同一文件名的一系列文件的。它描述的目标是根据和它名字相同的文件进行创建或更新的,同时提供了创建或更新该目标的命令。
重新创建目标文件的一些标准方法是经常使用的。例如,一个传统的创建OBJ文件的方法是使用C编译器,如cc,编译C语言源程序。
隐含规则能够告诉make怎样使用习惯的技术完成任务,这样,当您使用它们时,您就不必详细指定它们。例如,有一条编译C语言源程序的隐含规则,文件名决定运行哪些隐含规则;另如,编译C语言程序一般是使用‘.c’文件,产生‘.o’文件。因此, make据此和文件名的后缀就可以决定使用编译C语言源程序的隐含规则。一系列的隐含规则可按顺序应用;例如,make可以从一个‘.y’文件,借助‘.c’文件,重建一个‘.o’文件。内建隐含规则的命令需要使用变量,通过改变这些变量的值,您就可以改变隐含规则的工作方式。例如,变量CFLAGS通过传递C文件的隐含规则控制传递给C编译器的标志。通过编写格式规则,您可以创建您自己的隐含规则。。
后缀规则定义隐含规则有许多局限性。格式规则一般比较通用和清楚,保留后缀规则是为了保证make的兼容性。
允许make对一个目标文件寻找习惯的更新方法,您不需要指定任何命令。可以编写没有命令行的规则或根本不编写任何规则。这样,make将根据存在的源文件的类型或要生成的文件类型决定使用何种隐含规则。
例如,假设makefile文件是下面的格式:
foo : foo.o bar.o
cc -o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)
因为您提及了文件‘foo.o’,但是您没有给出它的规则,make将自动寻找一条隐含规则,该规则能够告诉make怎样更新该文件。无论文件‘foo.o’存在与否,make都会这样执行。
如果能够找到一条隐含规则,则它就能够对命令和一个或多个依赖(源文件)提供支持。如果您要指定附加的依赖,例如头文件,但隐含规则不能支持,您需要为目标‘foo.o’写一条不带命令行的规则。
每一条隐含规则都有目标格式和依赖格式;也许多条隐含规则有相同的目标格式。例如,有数不清的规则产生‘.o’文件:使用C编译器编译‘.C’文件;使用Pascal编译器编译‘.p’文件;等等。实际应用的规则是那些依赖存在或可以创建的规则。所以,如果您有一个‘.C’文件,make将运行C编译器;如果您有一个‘.p’文件,make将运行Pascal编译器;等等。
当然,您编写一个makefile文件时,您知道您要make使用哪一条隐含规则,以及您知道make将选择哪一条规则,因为您知道那个依赖文件是假设存在的。
首先,我们说一条隐含规则可以应用,该规则的依赖必须‘存在或可以创建’。一个文件在makefile中作为目标或依赖在具体的规则中,或者该文件可以经过一条隐含规则的递归调用后能够创建则该文件‘可以创建’。如果一条隐含规则的依赖是另一条隐含规则的结果,我们说产生了‘链’。
总体上说,make为每一个目标搜寻隐含规则,为没有命令行的双冒号规则搜寻隐含规则。仅作为依赖被提及的文件,将被认为是一个目标,如果该目标的规则没有指定任何内容, make将为它搜寻隐含规则。
注意,任何具体的依赖都不影响对隐含规则的搜寻。例如,认为这是一条具体的规则:
foo.o: foo.p
文件foo.p不是必要条件,这意味着make按照隐含规则可以从一个Pascal源程序(‘.p’文件)创建OBJ文件,‘.o’文件根据‘.p’文件进行更新。例如,如果文件foo.c也存在,按照隐含规则则是从文件foo.c重建foo.o,这是因为C编译规则在预定义的隐含规则列表中比Pascal规则靠前,
如果您不希望使用隐含规则创建一个没有命令行的目标,您可以通过添加分号为该目标指定空命令。
2.3.1 预定义的隐含规则
这里列举了预定义的隐含规则。这些隐含规则是经常应用的,当然如果您在makefile文件中重载或删除后。选项‘-r’或‘--no-builtin-rules’将删除所有预定义的隐含规则。
并不是所有的隐含规则都是预定义的,在make中很多预定义的隐含规则是后缀规则的扩展,因此,那些是预定义的隐含规则和后缀规则的列表相关(特殊目标.SUFFIXES的依赖列表)。缺省的后缀列表为:.out, .a, .ln, .o, .c, .cc, .C, .p, .f, .F, .r, .y, .l, .s, .S, .mod, .sym, .def, .h, .info, .dvi, .tex, .texinfo, .texi, .txinfo, .w, .ch, .web, .sh, .elc, .el。所有下面描述的隐含规则,如果它们的依赖中有一个出现在这个后缀列表中,则是后缀规则。如果您更改这个后缀列表,则只有那些由一个或两个出现在您指定的列表中的后缀命名的预定义后缀规则起作用;那些后缀没有出现在列表中的规则被禁止。
Compiling C programs(编译C程序)
‘n.o' 自动由‘n.c' 使用命令 ‘$(CC) -c $(CPPFLAGS) $(CFLAGS)'生成 。
Compiling C++ programs (编译C++程序)
‘n.o'自动由‘n.cc' 或‘n.C'使用命令‘$(CXX) -c $(CPPFLAGS) $(CXXFLAGS)’生成。 我们鼓励您对C++源文件使用后缀‘.cc' 代替后缀‘.C’。
Compiling Pascal programs (编译Pascal程序)
‘n.o'自动由‘n.p'使用命令‘$(PC) -c $(PFLAGS)'生成。
Compiling Fortran and Ratfor programs (编译Fortran 和 Ratfor程序)
‘n.o'自动由‘n.r', ‘n.F'或‘n.f' 运行Fortran编译器生成。使用的精确命令如下:
`.f'
`$(FC) -c $(FFLAGS)'.
`.F'
`$(FC) -c $(FFLAGS) $(CPPFLAGS)'.
`.r'
`$(FC) -c $(FFLAGS) $(RFLAGS)'.
Preprocessing Fortran and Ratfor programs (预处理Fortran 和 Ratfor程序)
‘n.f' 自动从 ‘n.r'或‘n.F'得到。该规则仅仅是与处理器把一个Ratfor 程序或能够预处理的 Fortran 程序转变为标准的 Fortran 程序。使用的精确命令如下:
`.F'
`$(FC) -F $(CPPFLAGS) $(FFLAGS)'.
`.r'
`$(FC) -F $(FFLAGS) $(RFLAGS)'.
Compiling Modula-2 programs(编译Modula-2程序)
‘n.sym'自动由‘n.def'使用命令‘$(M2C) $(M2FLAGS) $(DEFFLAGS)'生成。 ‘n.o' 从‘n.mod'生成;命令为:‘$(M2C) $(M2FLAGS) $(MODFLAGS)'。
Assembling and preprocessing assembler programs (汇编以及预处理汇编程序)
‘n.o'自‘n.S'运行C编译器,cpp,生成。命令为:‘$(CPP) $(CPPFLAGS)'。
Linking a single object file (连接一个简单的OBJ文件)
‘n' 自动由‘n.o' 运行C编译器中的连接程序 linker (通常称为 ld)生成。命令为: ‘$(CC) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS)'。该规则对仅有一个源程序的简单程序或对同时含有多个OBJ文件(可能来自于不同的源文件)的程序都能正常工作。如果同时含有多个OBJ文件,则其中必有一个OBJ文件的名字和可执行文件名匹配。例如:
x: y.o z.o
当‘x.c', ‘y.c' 和‘z.c' 都存在时则执行:
cc -c x.c -o x.o
cc -c y.c -o y.o
cc -c z.c -o z.o
cc x.o y.o z.o -o x
rm -f x.o
rm -f y.o
rm -f z.o
对于更复杂的情况,例如没有一个OBJ文件的名字和可执行文件名匹配,您必须为连接写一条具体的命令。
每一种能自动生成‘.o’的文件,可以在没有‘-c’选项的情况下使用编译器(‘$(CC)',‘$(FC)'或‘$(PC)'; C编译器‘$(CC)'也适用于汇编程序)自动连接。当然也可以使用OBJ文件作为中间文件,但编译、连接一步完成速度将快很多。
Yacc for C programs (由Yacc生成C程序)
‘n.c'自动由‘n.y'使用命令‘$(YACC) $(YFLAGS)'运行 Yacc生成。
Lex for C programs (由Lex生成C程序)
‘n.c'自动由‘n.l' 运行 Lex生成。命令为:‘$(LEX) $(LFLAGS)'。
Lex for Ratfor programs (由Lex生成Rator程序)
‘n.r'自动由‘n.l' 运行 Lex生成。命令为:‘$(LEX) $(LFLAGS)'。 对于所有的Lex文件,无论它们产生C代码或Ratfor 代码,都使用相同的后缀‘.l’进行转换,在特定场合下,使用make自动确定您使用哪种语言是不可能的。如果make使用‘.l’文件重建一个OBJ文件,它必须猜想使用哪种编译器。它很可能猜想使用的是 C 编译器, 因为C 编译器更加普遍。如果您使用 Ratfor语言, 请确保在makefile文件中提及了‘n.r',使make知道您的选择。否则,如果您专用Ratfor语言,不使用任何C 文件, 请在隐含规则后缀列表中将‘.c’剔除:
.SUFFIXES:
.SUFFIXES: .o .r .f .l ...
Making Lint Libraries from C, Yacc, or Lex programs(由C, Yacc, 或 Lex程序创建Lint库)
‘n.ln' 可以从‘n.c' 运行lint产生。命令为:‘ $(LINT) $(LINTFLAGS) $(CPPFLAGS) –i’。用于C程序的命令和用于‘n.y'或‘n.l'程序相同。
TeX and Web(TeX 和 Web)
‘n.dvi'可以从‘n.tex' 使用命令‘$(TEX)'得到。‘n.tex'可以从‘n.web'使用命令‘$(WEAVE)'得到;或者从‘n.w' (和‘n.ch',如果‘n.ch'存在或可以建造) 使用命令‘$(CWEAVE)'。‘n.p' 可以从‘n.web'使用命令‘$(TANGLE)'产生。‘n.c' 可以从‘n.w' (和‘n.ch',如果‘n.ch'存在或可以建造) 使用命令‘$(CTANGLE)'得到。
Texinfo and Info(Texinfo和Info)
‘n.dvi'可以从‘n.texinfo',‘n.texi', 或‘n.txinfo', 使用命令‘$(TEXI2DVI) $(TEXI2DVI_FLAGS)'得到。‘n.info'可以从‘n.texinfo',‘n.texi', 或‘n.txinfo', 使用命令‘$(MAKEINFO) $(MAKEINFO_FLAGS)'创建。
RCS
文件‘n'必要时可以从名为‘n,v'或‘RCS/n,v'的RCS文件中提取。具体命令是:‘$(CO) $(COFLAGS)'。文件‘n'如果已经存在,它不能从RCS文件中提取,即使RCS文件比它新。用于RCS的规则是最终的规则,所以RCS不能够从任何源文件产生,它们必须存在。
SCCS
文件‘n'必要时可以从名为‘s.n'或‘SCCS/s.n'的SCCS文件中提取。具体命令是:‘$(GET) $(GFLAGS)'。用于SCCS的规则是最终的规则,所以SCCS不能够从任何源文件产生,它们必须存在。SCCS的优点是,文件 ‘n' 可以从文件 ‘n.sh'拷贝并生成可执行文件(任何人都可以)。这用于shell的脚本,该脚本在SCCS内部检查。因为RCS 允许保持文件的可执行性,所以您没有必要将该特点用于RCS文件。我们推荐您避免使用SCCS,RCS不但使用广泛,而且是免费的软件。选择自由软件代替相当的(或低劣的)收费软件,是您对自由软件的支持。
通常情况下,您要仅仅改变上表中的变量,需要参阅下面的文档。
内建隐含规则的命令实际使用诸如COMPILE.c, LINK.p, 和 PREPROCESS.S等等变量,它们的值包含以上列出的命令。Make按照惯例进行处理,如,编译‘.x’源文件的规则使用变量‘COMPILE.x’;从‘.x’源文件生成可执行文件使用变量‘LINK.x’;预处理‘.x’源文件使用变量‘PREPROCESS.x’。
任何产生OBJ文件的规则都使用变量‘OUTPUT_OPTION’;make依据编译时间选项定义该变量的值是‘-o $@’或空值。当源文件分布在不同的目录中,您应该使用‘-o’选项保证输出到正确的文件中;当使用变量VPATH时同样。一些系统的编译器不接受针对OBJ文件的‘-o’开关;如果您在这样的系统上运行,并使用了变量VPATH,一些文件的编译输出可能会放到错误的地方。解决办法是将变量OUTPUT_OPTION值设为:‘; mv $*.o $@’。
2.3.2 隐含规则链
有时生成一个文件需要使用多个隐含规则组成的序列。例如,从文件‘n.y’生成文件‘n.o’,首先运行隐含规则Yacc,其次运行规则cc。这样的隐含规则序列称为隐含规则链。
如果文件‘n.c’存在或在makefile文件中提及,则不需要任何特定搜寻:make首先发现通过C编译器编译‘n.c’可生成该OBJ文件,随后,考虑生成‘n.c’时,则使用运行Yacc的规则。这样可最终更新‘n.c’和‘n.o’。
即使在文件‘n.c’不存在或在makefile文件中没有提及的情况下,make也能想象出在文件‘n.y’和‘n.o’缺少连接!这种情况下,‘n.c’称为中间文件。一旦make决定使用中间文件,它将把中间文件输入数据库,好像中间文件在makefile文件中提及一样;按照隐含规则的描述创建中间文件。
中间文件和其它文件一样使用自己的规则重建,但是中间文件和其它文件相比有两种不同的处理方式。
第一个不同的处理方式是如果中间文件不存在,make的行为不同:平常的文件b如果不存在,make认为一个目标依靠文件b,它总是创建文件b,然后根据文件b更新目标;但是文件b若是中间文件,make很可能不管它而进行别的工作,即不创建文件b,也不更新最终目标。只有在文件b的依赖比最终目标‘新’时或有其它原因时,才更新最终目标。
第二个不同点是make在更新目标创建文件b后,如果文件b不再需要,make将把它删除。所以一个中间文件在make运行之前和make运行之后都不存在。Make向您报告删除时打印一条‘rm –f’命令,表明有文件被删除。
通常情况下,任何在makefile文件中提及的目标和依赖都不是中间文件。但是,您可以特别指定一些文件为中间文件,其方法为:将要指定为中间文件的文件作为特殊目标 .INTERMEDIATE的依赖。这种方法即使对采用别的方法具体提及的文件也能生效。
您通过将文件标志为secondary文件可以阻止自动删除中间文件。这时,您将您需要保留的中间文件指定为特殊目标 .SECONDARY的依赖即可。对于secondary文件,make不会因为它不存在而去创建它,也不会自动删除它。secondary文件必须也是中间文件。
您可以列举一个隐含规则的目标格式(例如%.o)作为特殊目标 .PRECIOUS的依赖,这样您就可以保留那些由隐含规则创建的文件名匹配该格式的中间文件。
一个隐含规则链可以包含两个隐含规则。例如,从‘RCS/foo.y,v’创建文件‘foo’需要运行RCS、Yacc和cc,文件foo.y和foo.c是中间文件,在运行结束后将被删掉。
没有一条隐含规则可以在隐含规则链中出现两次以上(含两次)。这意味着,make不会运行两次linker从文件‘foo.o.o’创建文件foo。这还可以阻止make在搜寻一个隐含规则链时无限循环。
有些隐含规则可以被优化成特殊的隐含规则。例如,从文件foo.c创建文件foo可以被拥有编译和连接的规则链控制,它使用foo.o作为中间文件。但是对于这种情况存在一条特别的规则,使用简单的命令cc可以同时编译和连接。因为优化规则在规则表中的前面,所以优化规则和一步一步的规则链相比,优先使用优化规则。
2.3.3 格式规则
您可以通过编写格式规则定义隐含规则。该规则看起来和普通规则类似,不同之处在于格式规则的目标中包含字符‘%’(只有一个)。目标是匹配文件名的格式;字符‘%’可以匹配任何非空的字符串,而其它字符仅仅和它们自己相匹配。依赖用‘%’表示它们的名字和目标名关联。
格式‘%.o : %.c’是说将任何‘词根.c’文件编译为‘词根.o’文件。
在格式规则中使用的‘%’扩展是在所有变量和函数扩展以后进行的,它们是在makefile文件读入时完成的。
2.3.3.1 格式规则简介
格式规则是在目标中包含字符‘%’(只有一个)的规则,其它方面看起来和普通规则相同。目标是可以匹配文件名的格式,字符‘%’可以匹配任何非空的字符串,而其它字符仅仅和它们自己相匹配。
例如‘%.c’匹配任何以‘.c’结尾的文件名;‘s.%.c’匹配以‘s.’开始并且以‘.c’结尾的文件名,该文件名至少包含5个字符(因为‘%’至少匹配一个字符)。匹配‘%’的子字符串称为stem(词根)。依赖中使用‘%’表示它们的名字中含有和目标名相同的stem。要使用格式规则,文件名必须匹配目标的格式,而且符合依赖格式的文件必须存在或可以创建。下面规则:
%.o : %.c ; command...
表明要创建文件‘n.o’,使用‘n.c’作为它的依赖,而且文件‘n.c’ 必须存在或可以创建。
在格式规则中,依赖有时不含有‘%’。这表明采用该格式规则创建的所有文件都是采用相同的依赖。这种固定依赖的格式规则在有些场合十分有用。
格式规则的依赖不必都包含字符‘%’,这样的规则是一个有力的常规通配符,它为任何匹配该目标格式规则的文件提供创建方法。
格式规则可以有多个目标,不象正常的规则,这种规则不能扮演具有相同依赖和命令的多条不同规则。如果一格式规则具有多个目标,make知道规则的命令对于所有目标来说都是可靠的,这些命令只有在创建该目标时才执行。当为匹配一目标搜寻格式规则时,规则的目标格式和规则要匹配的目标不同是十分罕见的,所以make仅仅担心目前对文件给出命令和依赖是否有问题。注意该文件的命令一旦执行,所有目标的时间戳都会更新。
格式规则在makefile文件中的次序很重要,因为这也是考虑它们的次序。对于多个都能使用的规则,使用最先出现的规则。您亲自编写的规则比内建的规则优先。注意依赖存在或被提及的规则优先于依赖需要经过隐含规则链生成的规则。
2.3.3.2 预定义的格式规则
这里有一些实际在make中预定义的格式规则例子,第一个,编译‘.c’文件生成‘.o’文件的规则:
%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
定义了一条编译‘x.c’文件生成‘x.o’文件的规则,命令使用自动变量‘$@’和‘$<’ 替换任何情况使用该规则的目标文件和源文件。
第二个内建的例子:
% :: RCS/%,v
$(CO) $(COFLAGS) $<
定义了在子目录‘RCS’中根据相应文件‘x.v’生成文件‘x’的规则。因为目标是‘%’,只要相对应的依赖文件存在,该规则可以应用于任何文件。双冒号表示该规则是最终规则,它意味着依赖不能是中间文件。
下面的格式规则有两个目标:
%.tab.c %.tab.h: %.y
bison -d $<
这告诉make执行命令‘bison -d x.y’将创建‘x.tab.c’和‘x.tab.h’。如果文件foo依靠文件‘parse.tab.o’和‘scan.o’,而文件‘scan.o’又依靠文件‘parse.tab.h’,当‘parse.y’发生变化,命令‘bison -d parse.y’执行一次。‘parse.tab.o’和‘scan.o’的依赖也随之更新。(假设文件‘parse.tab.o’由文件‘parse.tab.c’编译生成,文件‘scan.o’由文件‘scan.c’生成,当连接‘parse.tab.o’、‘scan.o’和其它依赖生成文件foo时,上述规则能够很好执行。)
2.3.3.3 格式匹配
目标格式是由前缀、后缀和它们之间的通配符%组成,它们中的任一个或两个都可以是空值。格式匹配一个文件名只有该文件名是以前缀开始,后缀结束,而且两者不重叠的条件下,才算匹配。前缀、后缀之间的文本成为径(stem)。当格式‘%.o’匹配文件名‘test.o’时,径(stem)是‘test’。格式规则中的依赖将径(stem)替换字符%,从而得出文件名。对于上例中,如果一个依赖为‘%.c’,则可扩展为‘test.c’。
当目标格式中不包含斜杠(经常是这样),则文件名中的路径名首先被去除,然后,将其和格式中的前缀和后缀相比较。在比较之后,以斜杠结尾的路径名,将会加在根据格式规则的依赖规则产生的依赖前面。只有在寻找隐含规则时路径名才被忽略,在应用时路径名绝不能忽略。例如,‘e%t’和文件名‘src/eat’匹配,stem(词根)是‘src/a’。当依赖转化为文件名时,stem中的路径名将加在前面,stem(词根)的其余部分替换‘%’。使用stem(径) ‘src/a’和依赖格式规则‘c%r’匹配得到文件名‘src/car’
2.3.3.4 静态格式规则和隐含规则
静态格式规则和定义为格式规则的隐含规则有很多相同的地方。双方都有对目标的格式和构造依赖名称的格式,差异是make使用它们的时机不同。
隐含规则可以应用于任何于它匹配的目标,但它仅仅是在目标没有具体规则指定命令以及依赖可以被搜寻到的情况下应用。如果有多条隐含规则适合,仅有执行其中一条规则,选择依据隐含规则的定义次序。
相反,静态格式规则用于在规则中指明的目标。它不能应用于其它任何目标,并且它的使用方式对于各个目标是固定不变的。如果使用两个带有命令的规则发生冲突,则是错误。
静态格式规则因为如下原因可能比隐含规则更好:
? 对一些文件名不能按句法分类的但可以给出列表的文件,使用静态格式规则可以重载隐含规则链。
? 如果不能精确确定使用的路径,您不能确定一些无关紧要的文件是否导致make使用错误的隐含规则(因为隐含规则的选择根据其定义次序)。使用静态格式规则则没有这些不确定因素:每一条规则都精确的用于指定的目标上。
2.3.4 万用规则
一个格式规则的目标仅仅包含‘%’,它可以匹配任何文件名,我们称这些规则为万用规则。它们非常有用,但是make使用它们的耗时也很多,因为make必须为作为目标和作为依赖列出的每一个文件都考虑这样的规则。
假设makefile文件提及了文件foo.c。为了创建该目标,make将考虑是通过连接一个OBJ文件‘foo.c.o’创建,或是通过使用一步的C编译连接程序从文件foo.c.c创建,或是编译连接Pascal程序foo.c.p创建,以及其它的可能性等。
我们知道make考虑的这些可能性是很可笑的,因为foo.c就是一个C语言源程序,不是一个可执行程序。如果make考虑这些可能性,它将因为这些文件诸如foo.c.o和foo.c.p等都不存在最终拒绝它们。但是这些可能性太多,所以导致make的运行速度极慢。
为了加快速度,我们为make考虑匹配万用规则的方式设置了限制。有两种不同类型的可以应用的限制,在您每次定义一个万用规则时,您必须为您定义的规则在这两种类型中选择一种。
一种选择是标志该万用规则是最终规则。即在定义时使用双冒号定义。一个规则为最终规则时,只有在它的依赖存在时才能应用,即使依赖可以由隐含规则创建也不行。换句话说,在最终规则中没有进一步的链。
例如,从RCS和SCCS文件中抽取原文件的内建的隐含规则是最终规则,则如果文件‘foo.c,v' 不存在,make绝不会试图从一个中间文件‘foo.c,v.o’或‘RCS/SCCS/s.foo.c,v’创建它。 RCS 和 SCCS 文件一般都是最终源文件,它不能从其它任何文件重新创建,所以,make可以记录时间戳,但不寻找重建它们的方式。
如果您不将万用规则标志为最终规则,那么它就是非最终规则。一个非最终万用规则不能用于特殊类型的文件。只有其它规则(非万用规则)的目标才能匹配特殊类型的文件名。
例如,文件名‘foo.c' 和格式规则 `%.c : %.y' (该规则运行Yacc)的目标匹配。无论该规则是否实际使用(如果碰巧存在文件‘foo.y’,该规则将运行),和目标匹配的事实就能足够阻止任何非最终万用规则在文件foo.c上使用。这样,make 考虑就不试图从文件‘foo.c.o',‘foo.c.c', ‘foo.c.p'等创建可执行的‘foo.c'。
内建的特殊伪格式规则是用来认定一些特定的文件名,处理这些文件名的文件时不能使用非最终万用规则。这些伪格式规则没有依赖和命令,它们用于其它目的时被忽略。例如,内建的隐含规则:
%.p :
存在可以保证Pascal源程序如‘foo.p' 匹配特定的目标格式,从而阻止浪费时间寻找‘foo.p.o' 或‘foo.p.c'。
在后缀规则中,为后缀列表中的每一个有效后缀都创建了伪格式规则,如‘%.p' 。
2.3.5 最后求助规则和缺省规则
您通过编写不含依赖的最终万用格式规则,您可以定义最后求助的缺省规则。这和其它格式规则基本一样,特别之处在于它可以匹配任何目标。因此,这样的规则的命令可用于所有没有自己的命令的目标和依赖,以及用于那些没有其它隐含规则可以应用的目标和依赖。
例如,在测试makefile时,您可能不关心源文件是否含有真实数据,仅仅关心它们是否存在。那么,您可以这样做:
%::
touch $@
这导致所有必需的源文件(作为依赖)都自动创建。
您可以通过另一种办法实现上述目标,您可以为没有规则的目标以及那些没有具体指定命令的目标定义命令。要完成上述任务,您需要为特殊目标.DEFAULT 编写规则。这些规则的命令可以用在所有在具体规则中没有作为目标出现的依赖以及不能使用隐含规则的依赖。自然,如果您不编写则没有特殊目标.DEFAULT 的规则。
如果您使用特殊目标.DEFAULT 而不带任何规则和命令:
.DEFAULT:
则以前为目标.DEFAULT定义的命令被清除。如此make的行为和您从来没有定义目标.DEFAULT一样。
如果您不需要一个目标从万用规则和目标.DEFAULT 中得到命令,也不想为该目标执行任何命令,您可以在定义时使用空命令。
您可以使用最后求助规则重载另外一个makefile文件的一部分内容。
2.3.6 过时的后缀规则
后缀规则是定义隐含规则的过时方法。后缀规则因为格式规则更为普遍和简洁而被废弃。它们在GNU make中得到支持是为了和早期的makefile文件兼容。它们分为单后缀和双后缀规则。
双后缀规则被一对后缀定义:目标后缀和源文件后缀。它可以匹配任何文件名以目标后缀结尾的文件。相应的隐含依赖通过在文件名中将目标后缀替换为源文件后缀得到。一个目标和源文件后缀分别为‘.o’和‘.c’双后缀规则相当于格式规则`%.o : %.c'。
单后缀规则被单后缀定义,该后缀是源文件的后缀。它匹配任何文件名,其相应的依赖名是将文件名添加源文件后缀得到。源文件后缀为‘.c’的单后缀规则相当于格式规则‘% : %.c’。
通过比较规则目标和定义的已知后缀列表识别后缀规则。当make见到一个目标后缀是已知后缀的规则时,该规则被认为是一个单后缀规则。当make见到一个目标后缀包含两个已知后缀的规则时,该规则被认为是一个双后缀规则。
例如,‘.o’和‘.c’都是缺省列表中的已知后缀。所以,如果您定义一个规则,其目标是‘.c.o’,则make认为是一个双后缀规则,源文件后缀是‘.c’,目标后缀是‘.o’。 这里有一个采用过时的方法定义编译C语言程序的规则:
.c.o:
$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<
后缀规则不能有任何属于它们自己的依赖。如果它们有依赖,它们将不是作为后缀规则使用,而是以令人啼笑皆非的方式处理正常的文件。例如,规则:
.c.o: foo.h
$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<
告诉从依赖foo.h生成文件名为‘.c.o’的文件,并不是象格式规则:
%.o: %.c foo.h
$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<
告诉从 ‘.c' 文件生成‘.o'的方法:创建所有‘.o' 文件使用该格式规则,而且同时使用依赖文件‘foo.h'。
没有命令的后缀规则也没有意义。它们并不象没有命令的格式规则那样移去以前的规则。他们仅仅简单的在数据库中加入后缀或双后缀作为一个目标。
已知的后缀是特殊目标‘.SUFFIXES’简单的依赖名。通过为特殊目标‘.SUFFIXES’编写规则加入更多的依赖,您可以添加您自己的已知后缀。例如:
.SUFFIXES: .hack .win
把‘.hack' 和‘.win'添加到了后缀列表中。
如果您希望排除缺省的已知后缀而不是仅仅的添加后缀,那么您可以为特殊目标‘.SUFFIXES’编写没有依赖的规则。通过这种方式,可以完全排除特殊目标‘.SUFFIXES’存在的依赖。接着您可以编写另外一个规则添加您要添加的后缀。例如,
.SUFFIXES: # 删除缺省后缀
.SUFFIXES: .c .o .h # 定义自己的后缀列表
标志‘-r'或‘--no-builtin-rules'也能把缺省的后缀列表清空。
变量SUFFIXES在make读入任何makefile文件之前定义缺省的后缀列表。您可以使用特殊目标‘.SUFFIXES’改变后缀列表,但这不能改变变量SUFFIXES的值。
2.3.7 删除隐含规则
通过定义新的具有相同目标和依赖但不同命令的规则,您可以重载内建的隐含规则(或重载您自己定义的规则)。一旦定义新的规则,内建的规则就被代替。 新规则在隐含规则次序表中的位置由您编写规则的地方决定。
通过定义新的具有相同目标和依赖但不含命令的规则,您可以删除内建的隐含规则。例如,下面的定义规则将删除运行汇编编译器的隐含规则:
%.o : %.s
2.3.8 隐含规则搜寻算法
这里是make为一个目标‘t’搜寻隐含规则的过程。这个过程用于任何没有命令的双冒号规则,用于任何不含命令的普通规则的目标,以及用于任何不是其它规则目标的依赖。这个过程也能用于来自隐含规则的依赖递归调用该过程搜寻规则链。
在本算法中不提及任何后缀规则,因为后缀规则在makefile文件读入时转化为了格式规则。
1、 在‘t’中分离出路径部分,称为‘d’,剩下部分称为‘n’。例如如果‘t’是‘src/foo.o’,那么‘d’是‘src/’;‘n’是‘foo.o’。
2、 建立所有目标名匹配‘t’和‘n’的格式规则列表。如果目标格式中含有斜杠,则匹配‘t’,否则,匹配‘n’。
3、 如果列表中有一个规则不是万用规则,则从列表中删除所有非最终万用规则。
4、 将没有命令的规则也从列表中移走。
5、 对每个列表中的格式规则:
1、 寻找stem‘s’,也就是和目标格式中%匹配的‘t’或‘n’部分。
2、 使用stem‘s’计算依赖名。如果目标格式不包含斜杠,则将‘d’添加在每个依赖的前面。
3、 测试所有的依赖是否存在或能够创建。(如果任何文件在makefile中作为目标或依赖被提及,则我们说它应该存在。)如果所有依赖存在或能够创建,或没有依赖,则可使用该规则。
6、 如果到现在还没有发现能使用的规则,进一步试。对每一个列表中的规则:
1、 如果规则是最终规则,则忽略它,继续下一条规则。
2、 象上述一样计算依赖名。
3、 测试所有的依赖是否存在或能够创建。
4、 对于不存在的依赖,按照该算法递归调用查找是否能够采用隐含规则创建。
5、 如果所有依赖存在或能使用隐含规则创建,则应用该规则。
7、 如果没有隐含规则,则如有用于目标‘.DEFAULT’规则,则应用该规则。在这种情况下,将目标‘.DEFAULT’的命令给与‘t’。
一旦找到可以应用的规则,对每一个匹配的目标格式(无论是‘t’或‘n’)使用stem‘s’替换%,将得到的文件名储存起来直到执行命令更新目标文件‘t’。在这些命令执行以后,把每一个储存的文件名放入数据库,并且标志已经更新,其时间戳和目标文件‘t’一样。
如果格式规则的命令执行时,自动变量将设置为相应的目标和依赖。
2.4 目标和依赖
2.4.1 假想目标
假想目标并不是一个真正的文件名,它仅仅是您制定的一个具体规则所执行的一些命令的名称。使用假想目标有两个原因:避免和具有相同名称的文件冲突和改善性能。
如果您写一个其命令不创建目标文件的规则,一旦由于重建而提及该目标,则该规则的命令就会执行。这里有一个例子:
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文件的第一个规则的目标,所有假象目标被定义为all,为第一个规则的目标,其依赖为所有的程序。如下例:
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
2.4.2使用空目标文件记录事件
空目标是一个假想目标变量,它用来控制一些命令的执行,这些命令可用来完成一些经常需要的具体任务。但又不象真正的假想目标,它的目标文件可以实际存在,但文件的内容与此无关,通常情况下,这些文件没有内容。
空目标文件的用途是用来记录规则的命令最后一次执行的时间,也是空目标文件最后更改的时间。它之所以能够这样执行是因为规则的命令中有一条用于更新目标文件的‘touch’命令。另外,空目标文件应有一些依赖(否则空目标文件没有存在的意义)。如果空目标比它的依赖旧,当您命令重建空目标文件时,有关的命令才会执行。下面有一个例子:
print: foo.c bar.c
lpr -p $?
touch print
按照这个规则,如果任何一个源文件从上次执行‘make print'以来发生变化,键入‘make print'则执行lpr命令。打印那些发生变化的文件。
2.4.3内建的特殊目标名
一些名字作为目标使用则含有特殊的意义:
? .PHONY
特殊目标.PHONY的依赖是假想目标。假想目标是这样一些目标,make无条件的执行它的命令,和目录下是否存在该文件以及它最后一次更新的时间没有关系。
? .SUFFIXES
特殊目标.SUFFIXES的依赖是一列用于后缀规则检查的后缀。
? .DEFAULT
.DEFAULT指定一些命令,这些命令用于那些没有找到规则(具体规则或隐含规则)的目标。如果.DEFAULT指定了一些命令,则所有提及到的文件只能作为依赖,而不能作为任何规则的目标;这些指定的命令也只按照他们自己的方式执行。
? .PRECIOUS
特殊目标.PRECIOUS的依赖将按照下面给定的特殊方式进行处理:如果在执行这些目标的命令的过程中,make被关闭或中断,这些目标不能被删除;如果目标是中间文件,即使它已经没有任何用途也不能被删除,具体情况和该目标正常完成一样;该目标的其它功能和特殊目标.SECONDARY的功能重叠。如果规则的目标格式与依赖的文件名匹配,您可以使用隐含规则的格式(如‘%.O’)列举目标作为特殊目标.PRECIOUS的依赖文件来保存由这些规则创建的中间文件。
? .INTERMEDIATE
特殊目标.INTERMEDIATE的依赖被处理为中间文件。.INTERMEDIATE如果没有依赖文件,它将不会发生作用。
? .SECONDARY
特殊目标.SECONDARY的依赖被处理为中间文件,但它们永远不能自动删除。.SECONDARY如果没有依赖文件,则所有的makefile文件中的目标都将被处理为中间文件。
? .DELETE_ON_ERROR
如果在makefile文件的某处.DELETE_ON_ERROR作为一个目标被提及,则如果该规则发生变化或它的命令没有正确完成而退出,make将会删除该规则的目标,具体行为和它受到了删除信号一样。
? .IGNORE
如果您特别为目标.IGNORE指明依赖,则MAKE将会忽略处理这些依赖文件时执行命令产生的错误。如果.IGNORE作为一个没有依赖的目标提出来,MAKE将忽略处理所有文件时产生的错误。.IGNORE命令并没有特别的含义,.IGNORE的用途仅是为了和早期版本的兼容。因为.IGNORE影响所有的命令,所以它的用途不大;我们推荐您使用其它方法来忽略特定命令产生的错误。
? .SILENT
如果您特别为.SILENT指明依赖,则在执行之前MAKE将不会回显重新构造文件的命令。如果.SILENT作为一个没有依赖的目标提出来,任何命令在执行之前都不会打印。.SILENT并没有特别的含义,其用途仅是为了和早期版本的兼容。我们推荐您使用其它方法来处理那些不打印的命令。
? .EXPORT_ALL_VARIABLES
如该特殊目标简单的作为一个目标被提及,MAKE将缺省地把所有变量都传递到子进程中。
? .NOTPARALLEL
如果.NOTPARALLEL作为一个目标提及,即使给出‘-j’选项,make也不使用并行执行。但递归调用的make命令仍可并行执行(在调用的makefile文件中包含.NOTPARALLEL的目标的例外)。.NOTPARALLEL的任何依赖都将忽略。
任何定义的隐含规则后缀如果作为目标出现都会视为一个特殊规则,即使两个后缀串联起来也是如此,例如‘.c.o’。这些目标称为后缀规则,这种定义方法是过时的定义隐含规则的方法(目前仍然广泛使用的方法)。原则上,任何目标名都可采用这种方法指定,如果您要把它分为两个并把它们加到后缀列表中。实际上,后缀一般以‘.’开始,因此,这些特别的目标同样以‘.’开始。
2.4.4具有多条规则的目标
一个目标文件可以有多个规则。在所有规则中提及的依赖都将融合在一个该目标的依赖列表中。如果该目标比任何一个依赖‘旧’,命令将执行。
一个文件只能执行一套命令,但如果一条以上的规则对同一文件给出多条命令,make将使用最后给出的规则,同时打印错误信息。(作为特例,如果文件名以点‘.’开始,不打印出错信息。这种古怪的行为仅仅是为了和其它版本的make兼容,你应该避免这样使用)。用同样的目标执行在makefile不同部分定义的命令,偶然会有用;对于这种情况,你可以使用双冒号规则。
一条特别的只有依赖的规则可以用来立即给多条目标文件提供一些额外的依赖。例如,使用名为‘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将搜寻合适的隐含规则进而确定一些命令来完成生成或重建目标。
2.4.5目录中搜寻依赖
对于大型系统,把源文件安放在一个单独的目录中,而把二进制文件放在另一个目录中是十分常见的。Make 的目录搜寻特性使自动在几个目录搜寻依赖十分容易。当您在几个目录中重新安排您的文件,您不必改动单独的规则,仅仅改动一下搜寻路径即可。
2.4.5.1 VPATH:所有依赖的搜寻路径
make变量VPATH的值指定了make搜寻的目录。经常用到的是那些包含依赖的目录,并不是当前的目录;但VPATH指定了make对所有文件都适用的目录搜寻序列,包括了规则的目标所需要的文件。
如果一个作为目标或依赖的文件在当前目录中不存在,make就会在VPATH指定的目录中搜寻该文件。如果在这些目录中找到要寻找的文件,则就象这些文件在当前目录下存在一样,规则把这些文件指定为依赖。
在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。
2.4.5.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'。
2.4.5.3 目录搜寻过程
当通过目录搜寻找到一个文件,该文件有可能不是您在依赖列表中所列出的依赖;有时通过目录搜寻找到的路径也可能被废弃。Make决定对通过目录搜寻找到的路径保存或废弃所依据的算法如下:
1、 如果一个目标文件在makefile文件所在的目录下不存在,则将会执行目录搜寻。
2、 如果目录搜寻成功,则路径和所得到的文件暂时作为目标文件储存。
3、 所有该目标的依赖用相同的方法考察。
4、 把依赖处理完成后,该目标可能需要或不需要重新创建:
1、 如果该目标不需要重建,目录搜寻时所得到的文件的路径用作该目标所有依赖的路径,同时包含该目标文件。简而言之,如果make不必重建目标,则您使用通过目录搜寻得到的路径。
2、 如果该目标需要重建,目录搜寻时所得到的文件的路径将废弃,目标文件在makefile文件所在的目录下重建。简而言之,如果make要重建目标,是在makefile文件所在的目录下重建目标,而不是在目录搜寻时所得到的文件的路径下。
该算法似乎比较复杂,但它却可十分精确的解释实际您所要的东西。
其它版本的make使用一种比较简单的算法:如果目标文件在当前目录下不存在,而它通过目录搜寻得到,不论该目标是否需要重建,始终使用通过目录搜寻得到的路径。
实际上,如果在GNU make中使您的一些或全部目录具备这种行为,您可以使用GPATH变量来指定这些目录。
GPATH变量和VPATH变量具有相同的语法和格式。如果通过目录搜寻得到一个过时的目标,而目标存在的目录又出现在GPATH变量,则该路径将不废弃,目标将在该路径下重建。
2.4.5.4 编写目录搜寻的shell命令
即使通过目录搜寻在其它目录下找到一个依赖,这不能改变规则的命令,这些命令同样按照原来编写的方式执行。因此,您应该小心的编写这些命令,以便它们可以在make能够在发现依赖的目录中处理依赖。
借助诸如‘$^’的自动变量可更好的使用shell命令。例如,‘$^’的值代表所有的依赖列表,并包含寻找依赖的目录;‘$@’的值是目标。
foo.o : foo.c
cc -c $(CFLAGS) $^ -o $@
变量CFLAGS存在可以方便您利用隐含规则指定编译C语言源程序的旗标。我们这里使用它是为了保持编译C语言源程序一致性。
依赖通常情况下也包含头文件,不过通常您不想在命令中提交头文件,可以通过自动变量‘$<’实现,自动变量‘$<’的值是第一个依赖。例如:
VPATH = src:../headers
foo.o : foo.c defs.h hack.h
cc -c $(CFLAGS) $< -o $@
2.4.5.5 目录搜寻和隐含规则
当隐含规则寻找文件时搜寻的目录是由变量VPATH或vpath指令指定的。例如,如果文件‘foo.o’没有具体的规则,make则使用隐含规则:如文件foo.c存在,make使用内置的规则编译它;如果文件foo.c不在当前目录下,就搜寻适当的目录,如在别的目录下找到foo.c,make同样使用内置的规则编译它。
隐含规则的命令使用自动变量是必需的,所以隐含规则可以自然地使用目录搜寻得到的文件。
2.4.5.6 连接库的搜寻目录
对于连接库文件,目录搜寻采用一种特别的方式。它的形式是‘-lname’。(您可能觉得奇怪,因为依赖正常是一些文件名,库文件名通常是‘libname.a’ 的形式,而不是‘-lname’。)
当一个依赖的名字是‘-lname’的形式时,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’或/usr/lib/libcurses.a更旧,将导致命令‘cc foo.c /usr/lib/libcurses.a -o foo'执行。
尽管缺省情况下是搜寻‘libname.so' 和‘libname.a'文件,但是具体搜寻的文件及其类型可使用.LIBPATTERNS变量指定,这个变量值中的每一个字都是一个字符串格式。当寻找名为‘-lname’的依赖时,make首先用name替代列表中第一个字中的格式部分形成要搜寻的库文件名,然后使用该库文件名在上述的目录中搜寻。如果没有发现库文件,则使用列表中的下一个字,其余以此类推。
.LIBPATTERNS变量缺省的值是"‘lib%.so lib%.a'",该值对前面描述的缺省行为提供支持。您可以通过将该值设为空值从而彻底关闭对连接库的扩展。
2.4.6自动生成依赖
在为一个程序编写的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命令将它们读入。
在GNU make中,重新构造makefile文件的特点使这个惯例成为了过时的东西――您永远不必具体告诉make重新生成依赖,因为GNU make总是重新构造任何过时的makefile文件。
我们推荐使用自动生成依赖的习惯是把makefile文件和源程序文件一一对应起来。如,对每一个源程序文件‘name.c’有一名为‘name.d’的makefile文件和它对应,该makefile文件中列出了名为‘name.o’的OBJ文件所依赖的文件。这种方式的优点是仅在源程序文件改变的情况下才有必要重新扫描生成新的依赖。
这里有一个根据C语言源程序‘name.c’生成名为‘name.d’依赖文件的格式规则:
%.d: %.c
set -e; $(CC) -M $(CPPFLAGS) $< \
| sed 's/\($*\)\.o[ :]*/\1.o $@ : /g' > $@; \
[ -s $@ ] || rm -f $@
‘-e’开关是告诉shell如果$(CC)命令运行失败(非零状态退出)立即退出。正常情况下,shell退出时带有最后一个命令在管道中的状态(sed),因此make不能注意到编译器产生的非零状态。
对于GNU C编译器您可以使用‘-MM’开关代替‘-M’,这是省略了有关系统头文件的依赖。
命令Sed的作用是翻译(例如):
main.o : main.c defs.h
到:
main.o main.d : main.c defs.h
这使每一个‘.d’文件和与之对应的‘.o’文件依靠相同的源程序文件和头文件,据此,Make可以知道如果任一个源程序文件和头文件发生变化,则必须重新构造依赖文件。
一旦您定义了重新构造‘.d’文件的规则,您可以使用include命令直接将它们读入,例如:
sources = foo.c bar.c
include $(sources:.c=.d)
(这个例子中使用一个代替变量参照从源程序文件列表‘foo.c bar.c'翻译到依赖文件列表‘foo.d bar.d'。)所以,‘.d’的makefile文件和其它makefile文件一样,即使没用您的任何进一步的指令,make同样会在必要的时候重新构建它们。
第三章:变量
变量是在makefile中定义的名字,其用来代替一个文本字符串,该文本字符串称为该变量的值。在具体要求下,这些值可以代替目标、依赖、命令以及makefile文件中其它部分。(在其它版本的make中,变量称为宏(macros)。)
在makefile文件读入时,除规则中的shell命令、定义变量时等号(=)右边的部分、以及使用define指令定义的变量体此时不扩展外,makefile文件其它各个部分的变量和函数都将扩展。
变量可以代替文件列表、传递给编译器的选项、要执行的程序、查找源文件的目录、输出写入的目录,或您可以想象的任何文本。
变量名是不包括‘:',‘#',‘='、前导或结尾空格的任何字符串。然而变量名包含字母、数字以及下划线以外的其它字符的情况应尽量避免,因为它们可能在将来被赋予特别的含义,而且对于一些shell它们也不能通过环境传递给子make。变量名是大小写敏感的,例如变量名‘foo', ‘FOO', 和 ‘Foo'代表不同的变量。
使用大写字母作为变量名是以前的习惯,但我们推荐在makefile内部使用小写字母作为变量名,预留大写字母作为控制隐含规则参数或用户重载命令选项参数的变量名。
一少部分的变量使用一个标点符号或几个字符作为变量名,这些变量是自动变量,它们又特定的用途。
3.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。然而,这除了在使用自动变量的情况下,在其它实际工作中应该完全避免。
3.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函数连接用法。该例子也表明了变量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
和下面的语句严格等同
ifeq ($(origin FOO), undefined)
FOO = bar
endif
注意,一个变量即使是空值,它仍然已被定义,这时使用‘?=’定义无效。
3.3 变量引用高级技术
3.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的简写形式。我们提供替换引用和扩展函数也是为了和其他的make工具兼容。
另一种替换引用是使用强大的扩展函数patsubst。它的形式和上述的‘$(var:a=b)’一样,不同在于它必须包含单个‘%’字符,其实这种形式等同于‘$(patsubst a,b,$(var))’。例如:
foo := a.o b.o c.o
bar := $(foo:%.o=%.c)
社值变量‘bar'的值为‘a.c b.c c.c'。
3.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_files 或 1_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文件中,但二者不同。
3.4 变量取值
变量有以下几种方式取得它们的值:
? 您可以在运行make时为变量指定一个重载值。
? 您可以在makefile文件中指定值,即变量赋值或逐字定义变量。
? 把环境变量变为make的变量。
? 自动变量可根据规则提供值,它们都有简单的习惯用法。
? 变量可以用常量初始化。
3.5设置变量
在makefile文件中设置变量,编写以变量名开始后跟‘=’或‘:=’的一行即可。任何跟在‘=’或‘:=’后面的内容就变为变量的值。例如:
objects = main.o foo.o bar.o utils.o
定义一个名为objects的变量,变量名前后的空格和紧跟‘=’的空格将被忽略。
使用‘=’定义的变量是递归调用扩展型变量;以‘:=’定义的变量是简单扩展型变量。对于简单扩展型变量在定义的同时就被立即扩展。
变量名中也可以包含变量引用和函数调用,它们在该行读入时扩展,这样可以计算出能够实际使用的变量名。
变量值的长度没有限制,但受限于计算机中的实际交换空间。当定义一个长变量时,在合适的地方插入反斜杠,把变量值分为多个文本行是不错的选择。这不影响make的功能,但可使makefile文件更加易读。
绝大多数变量如果您不为它设置值,空字符串将自动作为它的初值。虽然一些变量有内建的非空的初始化值,但您可随时按照通常的方式为它们赋值。另外一些变量可根据规则自动设定新值,它们被称为自动变量。
如果您喜欢仅对没有定义过的变量赋值,您可以使用速记符‘?=’代替‘=’。下面两种设置变量的方式完全等同:
FOO ?= bar
和
ifeq ($(origin FOO), undefined)
FOO = bar
endif
3.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是递归调用型变量,意味着‘$(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)’仍然可以使用它的值。
3.7 变量重载
使用‘=’定义的变量:‘v=x’将变量v的值设为x。如果您用该方法定义了一个变量,在makefile文件后面任何对该变量的普通赋值都将被make忽略,要使它们生效应在命令行将它们重载。
最为常见的重载方法是使用传递附加标志给编译器。例如,在一个makefile文件中,变量CFLAGS已经包含了运行C编译器的每一个命令,因此,如果仅仅键入命令make时,文件‘foo.c’将按下面的方式编译:
cc -c $(CFLAGS) foo.c
这样您在makefile文件中对变量CFALAGS设置的任何影响编译器运行的选项都能生效,但是每次运行make时您都可以将该变量重载,例如:如果您说‘make CFLAGS='-g -O'’,任何C编译器都将使用‘cc -c -g -O’编译程序。这还说明了在重载变量时,怎样使用shell命令中的引用包括空格和其它特殊字符在内的变量的值。
变量CFALAGS仅仅是您可以使用这种方式重载的许多标准变量中的一个。
您也可以编写makefile察看您自己的附加变量,从而使用户可通过更改这些变量控制make运行时的其它面貌。
当您使用命令参数重载变量时,您可以定义递归调用扩展型变量或简单扩展型变量。上例中定义的是递归调用扩展型变量,如果定义简单扩展型变量,请使用‘:=’代替‘=’。注意除非您在变量值中使用变量引用或函数调用,这两种变量没有任何差异。
利用这种方式也可以改变您在makfile文件中重载的变量。在makfile文件中重载的变量是使用override指令,是和‘override variable = value’相似的命令行。
3.8 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
3.9 定义多行变量
设置变量值的另一种方法时使用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
3.10 环境变量
make使用的变量可以来自make的运行环境。任何make能够看见的环境变量,在make开始运行时都转变为同名同值的make变量。但是,在makefile文件中对变量的具体赋值,或使用带有参数的命令,都可以对环境变量进行重载(如果明确使用‘-e’标志,环境变量的值可以对makefile文件中的赋值进行重载,但是这在实际中不推荐使用。)
这样,通过在环境中设置变量CFLAGS,您可以实现在绝大多数makefile文件中使用您选择的编译开关对C源程序进行编译。因为您知道没有makefile将该变量用于其它任务,所以这种使用标准含义的变量是安全的(但这也是不可靠的,一些makefile文件可能设置变量CFLAGS,从而使环境中变量CFLAGS的值失效)。当使用递归调用的make时,在外层make的变量,可以通过环境变量传递给内层的make。缺省方式下,只有环境变量或在命令行中定义的变量才能传递给内层make。您可以使用export指令传递其它变量,
环境变量的其它使用方式都不推荐使用。将makefile的运行完全依靠环境变量的设置、超出makefile文件的控制范围,这种做法是不明智的,因为不同的用户运行同一个makefile文件有可能得出不同的结果。这和大部分makefile文件的意图相违背。
变量SHELL在环境中存在,用来指定用户对交互的shell的选择,因此使用变量SHELL也存在类似的问题。这种根据选定值影响make运行的方式是很不受欢迎的。所以,make将忽略环境中变量SHELL的值(在MS-DOS 和 MS-Windows中运行例外,但此时变量SHELL通常不设置值)。
3.11 特定目标变量的值
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’。
3.12 特定格式变量的值
除了特定目标变量的值(外,GNU make也支持特定格式变量的值。对任何符合特定格式的目标,都可以定义特定格式变量。在搜寻了具体规则的特定目标变量后,在搜寻格式目标的特定目标变量前将搜寻特定格式变量。
设置特定格式变量格式如下:
pattern ... : variable-assignment
或这样:
pattern ... : override variable-assignment
这里的‘pattern’是%-格式。象特定目标变量的值一样,‘pattern ...’中可含有多个格式,如此,则设置的特定格式变量的值可在匹配列表中的任一个格式中使用。‘variable-assignment’使用任何赋值方式都是有效的,在命令行中定义的变量的值占据优先的地位,而使用override指令定义的特定格式变量的值则占据优先地位。例如:
%.o : CFLAGS = -O
搜寻所有匹配格式%.o的目标,并将它的变量CFLAGS的值设置为‘-0’。
3.13 变量MAKEFILES
如果定义了环境变量MAKEFILES,make认为该变量的值是一列附加的makefile文件名,文件名之间由空格隔开,并且这些makefile文件应首先读取。Make完成这个工作和完成include指令的方式基本相同,即在特定的目录中搜寻这些文件。值得注意的是,缺省最终目标不会出现在这些makefile文件中,而且如果MAKEFILES列出的一些makefile文件没有找到也不会出现任何错误信息。
环境变量MAKEFILES主要在make递归调用过程中起通讯作用。在make顶级调用之前设置环境变量并不是十分好的主意,因为这样容易将makefile文件与外界的关系弄的更加混乱。然而如果运行make而没有指定的makefile文件时,环境变量MAKEFILES中makefile文件可以使内置的隐含规则更好的发挥作用,如搜寻定义的路径等。
一些用户喜欢在登录时自动设置临时的环境变量MAKEFILES并且在这种条件下编写makefile程序。这是非常糟糕的主意,因为这样会使其他一些makefile文件在这种情况下运行失效。最好的方法是直接在makefile文件中写出具体的include指令
3.14 MAKEFILE_LIST变量
当make读不同的makefile时,将从MAKEFILES变量,命令行,缺省文件,和include指令包含的变量中得到所能得到的一切变量值,这些变量值的名字,会自动的在make解析他们之前添加到MAKEFILE_LIST变量中。
这意味这如果makefile做的第一件事就是检查MAKEFILE_LIST变量的值,则MAKEFILE_LIST中的最后的字就是当前makefile的名字。如果当前的makefile使用了include指令,MAKEFILE_LIST中的最后一个字就是刚刚包括的文件名。
如果名为makefile的makefile文件中包含如下内容:
name1 := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
include inc.mk
name2 := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
all:
@echo name1 = $(name1)
@echo name2 = $(name2)
则相应的输出为:
name1 = Makefile
name2 = inc.mk
3.15 其他特殊变量
GNU make支持一个特殊变量,这个变量不接收你给他赋的值,它总是返回它的特定值。
第一个特殊变量为.VARIABLES。当扩展的时候,这个变量包含在使用它以前的所有makefile文件中所有全局变量的名字。这些变量包括空变量和内嵌变量,但是不包括在仅仅在特定目标中使用的变量。
3.16 变量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。所以您的验证文件、打印命令的请求等都能传递给子系统。
3.15 与子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中的工作方式相同。
如果您要将所有的变量都输出,您可以单独使用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,如果递归调用采用一种运行方式,直接执行采用另一种运行方式。
您可以使用变量MAKEFILES使所有的子make使用附加的makefile文件。变量MAKEFILES的值是makefile文件名的列表,文件名之间用空格隔开。在外层makefile中定义该变量,该变量的值将通过环境变量向下传递;因此它可以作为子make的额外的makefile文件,在子make读正常的或指定的makefile文件前,将它们读入。
3.16 隐含规则使用的变量
内建隐含规则的命令对预定义变量的使用是开放的;您可以在makefile文件中改变变量的值,也可以使用make的运行参数或在环境中改变,如此,在不对这些规则本身重新定义的情况下,就可以改变这些规则的工作方式。您还可以使用选项‘-R’或‘--no-builtin-variables’删除所有隐含规则使用的变量。
例如,编译C程序的命令实际是‘$(CC) -c $(CFLAGS) $(CPPFLAGS)’,变量缺省的值是‘cc’或空值,该命令实际是‘cc –c’。如重新定义变量‘CC’的值为‘ncc’,则所有隐含规则将使用‘ncc’作为编译C语言源程序的编译器。通过重新定义变量‘CFLAGS’的值为‘-g’,则您可将‘-g’选项传递给每个编译器。所有的隐含规则编译C程序时都使用‘$CC’获得编译器的名称,并且都在传递给编译器的参数中都包含‘$(CFLAGS)’。
隐含规则使用的变量可分为两类:一类是程序名变量(象cc),另一类是包含程序运行参数的变量(象CFLAGS)。(‘程序名’可能也包含一些命令参数,但是它必须以一个实际可以执行的程序名开始。) 如果一个变量值中包含多个参数,它们之间用空格隔开。
这里是内建规则中程序名变量列表:
AR
档案管理程序;缺省为:‘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 或 Ratfor程序的程序;缺省为:‘lex'.
PC
Pascal 程序编译程序;缺省为:‘pc'.
YACC
将 Yacc语言转变为 C程序的程序;缺省为:‘yacc'.
YACCR
将 Yacc语言转变为 Ratfor程序的程序;缺省为:‘yacc -r'.
MAKEINFO
将Texinfo 源文件转换为信息文件的程序;缺省为:‘makeinfo'.
TEX
从TeX源产生TeX DVI文件的程序;缺省为:‘tex'.
TEXI2DVI
从Texinfo源产生TeX DVI 文件的程序;缺省为:‘texi2dvi'.
WEAVE
将Web翻译成TeX的程序;缺省为:‘weave'.
CWEAVE
将CWeb翻译成TeX的程序;缺省为:‘cweave'.
TANGLE
将Web翻译成 Pascal的程序;缺省为:‘tangle'.
CTANGLE
将Web翻译成C的程序;缺省为:‘ctangle'.
RM
删除文件的命令;缺省为:‘rm -f'.
这里是值为上述程序附加参数的变量列表。在没有注明的情况下,所有变量的值为空值。
ARFLAGS
用于档案管理程序的标志,缺省为:‘rv'.
ASFLAGS
用于汇编编译器的额外标志 (当具体调用‘.s'或‘.S'文件时)。
CFLAGS
用于C编译器的额外标志。
CXXFLAGS
用于C++编译器的额外标志。
COFLAGS
用于RCS co程序的额外标志。
CPPFLAGS
用于C预处理以及使用它的程序的额外标志 (C和 Fortran 编译器)。
FFLAGS
用于Fortran编译器的额外标志。
GFLAGS
用于SCCS get程序的额外标志。
LDFLAGS
用于调用linker(‘ld’)的编译器的额外标志。
LFLAGS
用于Lex的额外标志。
PFLAGS
用于Pascal编译器的额外标志。
RFLAGS
用于处理Ratfor程序的Fortran编译器的额外标志。
YFLAGS
用于Yacc的额外标志。Yacc。
3.17 自动变量
假设您编写一个编译‘.c’文件生成‘.o’文件的规则:您怎样编写命令‘CC’,使它能够操作正确的文件名?您当然不能将文件名直接写进命令中,因为每次使用隐含规则操作的文件名都不一样。
您应该使用make的另一个特点,自动变量。这些变量在规则每次执行时都基于目标和依赖产生新值。例如您可以使用变量‘$@’代替目标文件名,变量‘$<’代替依赖文件名。
下面是自动变量列表:
$@
规则的目标文件名。对于有多个目标的格式规则,变量‘$@’是那个导致规则命令运行的目标文件名。
$<
第一个依赖的文件名。如果目标更新命令来源于隐含规则,该变量的值是隐含规则添加的第一个依赖。
$?
所有比目标‘新’的依赖名,名字之间用空格隔开。
$^
所有依赖的名字,名字之间用空格隔开。对同一个目标来说,一个文件只能作为一个依赖,不管该文件的文件名在依赖列表中出现多少次。所以,如果在依赖列表中,同一个文件名出现多次,变量‘$^’的值仍然仅包含该文件名一次。
$+
该变量象‘$^',但是,超过一次列出的依赖将按照它们在makefile文件中出现的次序复制。这主要的用途是对于在按照特定顺序重复库文件名很有意义的地方使用连接命令。
$*
和隐含规则匹配的stem(词根)。如果一个目标为‘dir/a.foo.b',目标格式规则为:‘a.%.b' ,则stem为‘dir/foo'。在构建相关文件名时stem 十分有用。在静态格式规则中,stem是匹配目标格式中字符‘%’的文件名中那一部分。在一个没有stem具体规则中;变量‘$*' 不能以该方法设置。如果目标名以一种推荐的后缀结尾,变量‘$*'设置为目标去掉该后缀后的部分。例如,如果目标名是‘foo.c',则变量‘$*' 设置为‘foo', 因为‘.c' 是一个后缀。GNU make 处理这样奇怪的事情是为了和其它版本的make兼容。在隐含规则和静态格式规则以外,您应该尽量避免使用变量‘$*'。在具体规则中如果目标名不以推荐的后缀结尾,则变量‘$*’在该规则中设置为空值。
`$(@D)'
目标文件名中的路径部分,结尾斜杠已经移走。如果变量`$@'的值是`dir/foo.o',变体 `$(@D)'的值是`dir'。 如果变量`$@'的值不包含斜杠,则变体的值是`.'。
`$(@F)'
目标文件名中的真正文件名部分。如果变量`$@'的值是`dir/foo.o',变体 `$(@F)'的值是` foo.o '。`$(@F)' 等同于 `$(notdir $@)'。
`$(*D)'
`$(*F)'
stem(词根)中的路径名和文件名;在这个例子中它们的值分别为:`dir' 和 `foo' 。
`$(
`$(
第一个依赖名中的路径名和文件名。
`$(^D)'
`$(^F)'
所有依赖名中的路径名和文件名列表。
`$(?D)'
`$(?F)'
所有比目标‘新’的依赖名中的路径名和文件名列表。
注意,在我们讨论自动变量时,我们使用了特殊格式的惯例;我们写"the value of‘$<'", 而不是"the variable <" ;和我们写普通变量,例如变量 objects 和 CFLAGS一样。我们认为这种惯例在这种情况下看起来更加自然。这并没有其它意义,变量‘$<'的变量名为 < 和变量‘$(CFLAGS)' 实际变量名为CFLAGS一样。您也可以使用‘$(<)'代替‘$<'。
第四章:条件控制语句
一个条件语句可以导致根据变量的值执行或忽略makefile文件中一部分脚本。条件语句可以将一个变量与其它变量的值相比较,或将一个变量与一字符串常量相比较。条件语句用于控制make实际看见的makefile文件部分,不能用于在执行时控制shell命令。
4.1 条件语句的例子
下述的条件语句的例子告诉make如果变量CC的值是‘gcc’时使用一个数据库,如不是则使用其它数据库。它通过控制选择两命令行之一作为该规则的命令来工作。‘CC=gcc’作为参数不仅用于决定使用哪一个编译器,而且决定连接哪一个数据库。
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可以在条件指令行的其他地方没有什么作用,但是在指令名和参数中加空格和Tab不是这样。以‘#’开始的注释可以在行的结尾。
在条件语句中另两个有影响的指令是else和endif。这两个指令以一个单词的形式出现,没有任何参数。在指令行前面允许有多余的空格,空格和Tab可以插入到行的中间,以‘#’开始的注释可以在行的结尾。
条件语句影响make使用的makefile文件。如果条件为‘真’,make读入‘text-if-true’包含的行;如果条件为‘假’,make读入‘text-if-false’包含的行(如果存在的话);makefile文件的语法单位,例如规则,可以跨越条件语句的开始或结束。
当读入makefile文件时,Make计算条件的值。因而您不能在测试条件时使用自动变量,因为他们是命令执行时才被定义。
为了避免不可忍受的混乱,在一个makefile文件中开始一个条件语句,而在另外一个makefile文件中结束,这种情况是不允许的。然而如果您试图引入包含的makefile文件不中断条件语句,您可以在条件语句中编写include指令。
4.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中的函数
函数允许您在makefile文件中处理文本、计算文件、操作使用命令等。在函数调用时您必须指定函数名以及函数操作使用的参数。函数处理的结果将返回到makefile文件中的调用点,其方式和变量替换一样。
5.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中的空格用逗号替换,然后返回结果。
5.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把一些值和空字符串‘’比较时,您通常要将一些仅由空格组成的字符串认为是空字符串。如此下面的例子在实现预期结果时可能失败:
.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’和‘’。
$(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可以取掉重复的字,您就是不关心排序也可以使用它的这个特点。
$(word n,text)
返回‘text’中第n个字的值。n的合法值从1开始,如果n大于文本中字的个数,返回值为空。例如:
$(word 2, foo bar baz)
返回值是‘bar’。
$(wordlist s,e,text)
返回‘text’中从s开始到e结束的字符(包括s和e),s和e的合法值从1开始,如果s大于‘text’中字的个数,wordlist的返回值为空,如果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)是一样的,但是firstword因为它的简洁被保留。
这里有一个实际使用函数subst和patsubst的例子。假设一个makefile文件使用变量VPATH指定make搜寻依赖文件的一系列路径。这个例子表明怎样告诉C编译器在相同路径列表中搜寻头文件。
变量VPATH的值是一列用冒号隔开的路径名,如‘src:../headers'。首先,函数subst将冒号变为空格:
$(subst :, ,$(VPATH))
这产生值‘src ../headers'。然后,函数patsubst为每一个路径名加入‘-I’标志,这样这些路径可以加到变量CFLAGS中,就可以自动传递给C编译器:
override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH)))
结果是在以前给定的变量CFLAGS的值后追加文本‘-Isrc -I../headers’。Override指令的作用是即使以前使用命令参数指定变量CFLAGS的值,新值也能起作用。
5.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的结果合并,产生原始给定的文件列表。
$(wildcard pattern)
参数‘pattern’是一个文件名格式,包含通配符(和shel中的文件名一样)。函数wildcard的结果是一列和格式匹配的且文件存在的文件名,文件名之间用一个空格隔开。
5.4函数wildcard
通配符在规则中可以自动扩展,但设置在变量中或在函数的参数中通配符一般不能正常扩展。如果您需要在这些场合扩展通配符,您应该使用函数wildcard,格式如下:
$(wildcard pattern...)
参数‘pattern’是一个文件名格式,包含通配符(和shel中的文件名一样)。函数wildcard的结果是一列和格式匹配的且文件存在的文件名,文件名之间用一个空格隔开。可以在makefile文件的任何地方使用该字符串,应用时该字符串被一列在指定目录下存在的并且文件名和给出的文件名的格式相符合的文件所代替,文件名中间由空格隔开。如果没有和指定格式一致的文件,则函数wildcard的输出将会省略。注意这和在规则中通配符扩展的方式不同,在规则中使用逐字扩展方式,而不是省略方式。
在定义变量,或作为函数的参数时,如果要用到通配符,一般就要用wildcard函数。
下面是使用wildcard函数的一个例子
编译特定目录下所有C语言源程序并把它们连接在一起的,可以用如下方法写:
objects := $(patsubst %.c,%.o,$(wildcard *.c))
foo : $(objects)
cc -o foo $(objects)
这里我们使用了另外一个函数:patsubst,
这里使用了编译C语言源程序的隐含规则,因此没有必要为每个文件写具体编译规则。 ‘:=’是‘=’的变异。
5.5函数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/*)’; 第二次循环变量dir的值被赋为‘b’,所以产生函数foreach结果的第二个字为‘$(wildcard b/*)’; 第三次循环变量dir的值被赋为‘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!’变量,上例是有效的,但它极易带来错误。
5.6 函数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的调用)。
5.7 函数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时,去掉参数中任何多余的空格才是最安全的方法。
5.8 value 函数
value函数提供一种使用不扩展的变量值的方法,当然这是对还没扩展的变量有效,例如,如果你在创建了一个变量,它的值在定义时就已经扩展,在这种情况下,value函数将会返回的结果和直接使用该变量是一样的。
这个函数的结果是变量没有扩展前的字符串,例如:
FOO = $PATH
all:
@echo $(FOO)
@echo $(value FOO)
第一行的输出为ATH,因为“$P”将会扩展成一个make变量,而第二行的输出为你的$PATH环境变量,因为value函数避免了扩展。
value函数经常和eval函数联合使用。
5.9 eval 函数
eval函数十分特殊,它可以让你定义新的makefile结构,这种结构并不是恒定的,它是其他变量和函数的评估结果。eval函数的参数是扩展的,扩展结果被当作makefile的语法分析。扩展结果可以定义新的make变量,目标,隐含规则和常规规则,等等。
eval函数的结果总是空字符,因此,它可以被放到makefile的任何地方,而不会导致语法错误。
认识到eval的参数被扩展两次是很重要的,首先被eval扩展一次,然后扩展的结果有被扩展一次,当这些结果被解释成makefile语法时。这意味着你需要提供“$”转义额外的级别。value函数在这种情况下有时是有用的,来避免不希望的扩展。
这有一个例子是关于eval函数如何被使用的。这个例子结合了一系列的概念和其他函数。尽管在这个例子中使用eval函数可能看起来会过渡的复杂,还不缛直接写出规则,但是考虑到两点,第一临时定义(在 PROGRAM_template中)可能需要比这里复杂的多的应用,第二,你可以将这个例子中复杂的“generic”部分放到其他makefile中,然后将该makefile文件包含到其他独立的makefile文件中。现在这些独立的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)
5.10 函数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指令定义。
‘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’重新定义。
5.11 函数shell
除了函数wildcard之外,函数shell和其它函数不同,它是make与外部环境的通讯工具。函数shell和在大多数shell中后引号(’)执行的功能一样:它进行命令扩展。这意味着它起着调用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’的结果相同。
5.12 控制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仍然继续执行。扩展该函数的结果是空字符串。
第六章:make执行命令的过程(包括makefile自身的命令)
第七章:编译器命令
第八章:SHELL命令和批处理命令
第九章:makefile编程方法
第十章:makefile编程规范
附录一: 通配符
? ‘*’通配符
‘*’ 可以匹配任何字符序列(字符串),包括无字符的情况;
‘*’ 通配符表示“任何字符,包括无字符”
? ‘?’ 通配符
‘?’ 可以匹配任何一个字符。不包括无字符的情况;
‘?’ 通配符表示“任何单个的字符”。
? ‘[...]’ 通配符
‘[...]’ 可以匹配任何在括号内的一个字符。不包括无字符的情况;
‘[...]’ 通配符表示“任何在括号内的单个的字符”。
通配符‘[...]’还有一些特殊用法:
1、在‘[...]’中第一个字符如果是‘!’或‘^’,可以匹配任何一个字符除了括号内‘!’或‘^’之后的字符,这时通配符发生转义表示“任何不在括号内的单个的字符(括号内的字符不包括第一个字符‘!’或‘^’)”。
2、在‘[...]’内除了‘\’其他通配符号都将失去其意义。如果‘[...]’有字符‘-’或‘]’则‘-’或‘]’在‘[...]’中必须是第一个字符或最后一个字符。如:‘[-\\[*?]]’表示可以匹配‘-’, ‘\’, ‘[’, ‘*’, ‘?’ 或者 ‘]’这几个字符中的任意一个。
3、 可以采用这种格式‘[a-e]’,a-e 表示a到e之间的所有字符,它等价于‘[abcde]’ 。
参考文献:
阅读(4641) | 评论(0) | 转发(0) |