makefile写法整理

来源:互联网 发布:c:\windows\system32 编辑:程序博客网 时间:2024/06/02 04:01

1  Makefile基本语法

Makefile:程序模块的内部关系决定了源程序编译和链接的顺序,通过建立makefile可以描述模块间的相互依赖关系。Make命令从中读取这些信息,然后根据这些信息对程序进行管理和维护。在makefile里主要提供的是有关目标文件(即target)与依靠文件(即dependencyies)之间的关系,还指明了用什么命令生成和更新目标文件。有了这些信息,make会处理磁盘上的文件,如果目的文件的时间标志(该文件生成或被改动进的时间)比任意一个依靠文件旧,make就执行相应的命令,以便更新目的文件(目的文件不一定是最后的可执行文件,它可以是任何一个文件)。 

1)makefile的基本单位是“规则”,即描述一个目标所依赖的文件或模块,并给出其生成和算法语言需要用到的命令。规则的格式如下: 

目标[属性] 

分隔符号 [依赖文件][命令列] 

{<tab>命令列} 

与Linux下面的命令格式相同,[]中的内容表示为可选择项,{}中的内容表示可出现多次。 

A. 目标:目标文件列表,即要维护的文件列表。 

B. 属性:表示该文件的属性。 

C. 分隔符:用来分割目标文件和依赖文件的符号,如冒号“:”等。 

D. 依赖文件:目标文件所依赖的文件的列表。 

E. 命令列:重新生成目标文件的命令,可以有多条命令。 

注意:在makefile中,除了第一条命令,每一个命令行的开头必须是一个<tab>符号,也就是制表符,而不能因为制表符相当于4个空格而不去键入tab符号。因为make命令是通过每一行的tab符号来识别命令行的。另外,对于第一条命令而言,不必用<tab>键,就可以直接跟在依赖文件的列表后面。对于注释的了,起头应该用#符号,并用换行符号结束。如果要引用#符号,要用到“”。 

2) make命令的使用格式为: 

make [选项][宏定义][目标文件] 

make命令有多个选项参数,列举参数含义如下: 

A. -f:指定需要维护的目标。 

B. -i:忽略运行makefile中命令产生的错误,不退出make. 

C. -r:忽略内部规则。 

D. -s:执行但是不显示所执行的命令。 

E. -x:将所有的宏定义都输出到shell环境。 

F. -V:列出make的版本号。 

选项给出了make命令工作的方式方法,宏定义给出了makefile时所用的宏值,目标文件就是需要更新的文件列表。 

3)使用伪目标:make命令的目标可分为实目标和伪目标两种。而有时需要用make命令来做些辅助性的工作,或者对多个文件进行维护。可以通过设置伪目标来实现。 

4)指定需要维护的目标:一般make维护的是makefile中的第一个目标文件。但有时用户并不关心最终的目标文件如何。反而关心中间的目标文件。使用目标参数make的执行。 

5)makefile变量:makefile里主要包含了一些规则,除此之外就是变量定义,被称为宏定义。Makefile里的变量就像一个环境变量。事实上,环境变量在make过程中可以看成make的变量。这些宏定义是大小写敏感的,一般使用大写字母。它们几乎可以从任何地方被引用,也可以被用来做很多事情,比如: 

A.储存一个文件名列表:生成可执行文件的规则包含一些目标文件名作为依赖文件。在这个规则的命令行里,同样的那些文件被输送给gcc做为命令参数。如果在这里使用一个宏来储存所有目标文件名,那么就会很容易加入新的目标文件,而且不易出错。 

B.储存可执行文件名:如果程序被用在一个非GCC的系统里,或者想使用一个不同的编译器,就必须将所有使用编译器的地方改成新的编译器名。但是如果使用一个宏来代替编译器名,那么只需要改变一个地方,其他所有地方的命令名就都改变了。 

C.储存编译器命令选项:假设想给所有的编译命令传递一组相同的选项(例如-Wall -O -g),如果把这组选项存入一个宏,那么可以把这个宏放在所有调用编译器的地方。而当要改变选项的时候,只需在宏定义的地方改变这个变量的内容。要定义一个宏,只要在一行的开始写下这个宏的名字,后面跟一个“=“和要设定这个变量的值。以后引用这个变量时,写一个$符,后面是括号里的变量名。格式如下: 

$(宏名) 或${宏名} 

make将$符号作为引用的开始。如果要表示$符号,那么应用$$即可。宏引用还支持多层引用,在处理时按照顺序依次展开。当宏名是单个字符时,可以省略括号,宏定义可以在makefile文件中。 

$@:扩展成当前规则的目标文件名 

$<:扩展成依赖列表中的第一个依赖文件 

$^:扩展成整个依赖的文件列表(除掉了里面所有重复的文件名) 

$?:表示目标文件中新的依赖文件的列表 

$*:是表示依赖文件的文件名,不含扩展名 

6)在makefile中使用函数:makefile里的函数跟它的变量很相似。在调用时,用一个$开始,是开括号,函数名,再空格,然后跟一列由逗号分隔的参数,最后用关括号结束。

2  常用含义

objects = foo.o bar.o

all:$(objects)

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

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

%o:所有以“.o”结尾的目标,也就是foo.o bar.o

依赖模式“%.c”:取模式“%.o”%,也就是foo bar,并为其机上.c后缀,即foo.cbar.c

$<:表示所有依赖目标集,也就是foo.c bar.c

$@:表示目标集,也就是foo.o bar.o

$^

指定输入输出文件的路径  g++   -c   ./src/x.cpp   -o   ./obj/x.o

'filter-out' 函数使用两个用空格分开的列表,它把第二列表中所 有的存在于第一列 表中的项目删除。我

3  GNU make 指南

译者按: 本文是一篇介绍 GNU Make 的文章,读完后读者应该基本掌握了 make 的用法。而 make 是所有想在 Unix (当然也包括 Linux )系统上编程的用户必须掌握的工具。如果你写的程序中没有用到 make ,则说明你写的程序只是个人的练习程序,不具有任何实用的价值。也许这么说有点 儿偏激,但 make 实在是应该用在任何稍具规模的程序中的。希望本文可以为中国的 Unix 编程初学者提供一点儿有用的资料。中国的 Linux 用户除了学会安装红帽子以外, 实在应该尝试写一些有用的程序。个人想法,大家参考。 

C-Scene 题目 #2
多文件项目和 GNU Make 工具
作者: 乔治富特 (Goerge Foot)
电子邮件: george.foot@merton.ox.ac.uk
Occupation: Student at Merton College, Oxford University, England
职业:学生,默尔顿学院,牛津城大学,英格兰
IRC匿名: gfoot

拒绝承诺:作者对于任何因此而对任何事物造成的所有损害(你所拥有或不拥有的实际的,抽象的,或者虚拟的)。所有的损坏都是你自己的责任,而与我无关。

所有权: “多文件项目”部分属于作者的财产,版权归乔治富特1997年五月至七月。其它部分属 CScene 财产,版权 CScene 1997年,保留所有版权。本 CScene 文章的分发,部分或全部,应依照所有其它 CScene 的文章的条件来处理。

3 .1 介绍

~~~~~~~~~~~~~~~
本文将首先介绍为什么要将你的C源代码分离成几个合理的独立档案,什么时候需要分,怎么才能分的好。然后将会告诉你 GNU Make 怎样使你的编译和连接步骤自动化。对于其它 Make 工具的用户来说,虽然在用其它类似工具时要做适当的调整,本文的内容仍然是非常有用的。如果对你自己的编程工具有怀疑,可以实际的试一试,但请先阅读用户手册。

3 .2 多文件项目

~~~~~~~~~~~~~~~~~~~~~~

1.1为什么使用它们?

首先,多文件项目的好处在那里呢?
它们看起来把事情弄的复杂无比。又要 header 文件,又要 extern 声明,而且如果需要查找一个文件,你要在更多的文件里搜索。

但其实我们有很有力的理由支持我们把一个项目分解成小块。当你改动一行代码,编译器需要全部重新编译来生成一个新的可执行文件。但如果你的项目是分开在几个小文件里,当你改动其中一个文件的时候,别的源文件的目标文件(object files)已经存在,所以没有什么原因去重新编译它们。你所需要做的只是重现编译被改动过的那个文件,然后重新连接所有的目标文件罢了。在大型的项目中,这意味着从很长的(几分钟到几小时)重新编译缩短为十几,二十几秒的简单调整。

只要通过基本的规划,将一个项目分解成多个小文件可使你更加容易的找到一段代码。很简单,你根据代码的作用把你的代码分解到不同的文件里。当你要看一段代码时,你可以准确的知道在那个文件中去寻找它。

从很多目标文件生成一个程序包(Library)比从一个单一的大目标文件生成要好的多。当然实际上这是否真是一个优势则是由你所用的系统来决定的。但是当使用gcc/ld (一个 GNU C 编译/连接器) 把一个程序包连接到一个程序时,在连接的过程中,它会尝试不去连接没有使用到的部分。但它每次只能从程序包中把一个完整的目标文件排除在外。因此如果你参考一个程序包中某一个目标档中任何一个符号的话,那么这个目标文件整个都会被连接进来。要是一个程序包被非常充分的分解了的话,那么经连接后,得到的可执行文件会比从一个大目标文件组成的程序包连接得到的文件小得多。

又因为你的程序是很模块化的,文件之间的共享部分被减到最少,那就有很多好处——可以很容易的追踪到臭虫,这些模块经常是可以用在其它的项目里的,同时别人也可以更容易的理解你的一段代码是干 什么的。当然此外还有许多别的好处……

1.2 何时分解你的项目

很明显,把任何东西都分解是不合理的。象“世界,你们好”这样的简单程序根本就不能分,因为实在也没什么可分的。把用于测试用的小程序分解也是没什么意思的。但一般来说,当分解项目有助于布局、发展和易读性的时候,我都会采取它。在大多数的情况下,这都是适用的。(所谓“世界,你们好”,既 'hello world' ,只是一个介绍一种编程语言时惯用的范例程序,它会在屏幕上显示一行 'hello world' 。是最简单的程序。)

如果你需要开发一个相当大的项目,在开始前,应该考虑一下你将如何实现它,并且生成几个文件(用适当的名字)来放你的代码。当然,在你的项目开发的过程中,你可以建立新的文件,但如果你这么做的话,说明你可能改变了当初的想法,你应该想想是否需要对整体结构也进行相应的调整。

对于中型的项目,你当然也可以采用上述技巧,但你也可以就那么开始输入你的代码,当你的码多到难以管理的时候再把它们分解成不同的档案。但以我的经验来说,开始时在脑子里形成一个大概的方案,并且尽量遵从它,或在开发过程中,随着程序的需要而修改,会使开发变得更加容易。

1.3 怎样分解项目

先说明,这完全是我个人的意见,你可以(也许你真的会?)用别的方式来做。这会触动到有关编码风格的问题,而大家从来就没有停止过在这个问题上的争论。在这里我只是给出我自己喜欢的做法(同时也给出这么做的原因):

i) 不要用一个 header 文件指向多个源码文件(例外:程序包 的 header 文件)。用一个 header定义一个源码文件的方式 会更有效,也更容易查寻。否则改变一个源文件的结构(并且 它的 header 文件)就必须重新编译好几个文件。

ii) 如果可以的话,完全可以用超过一个的 header 文件来指向同 一个源码文件。有时将不可公开调用的函数原型,类型定义等等,从它们的C源码文件中分离出来是非常有用的。使用一 个 header 文件装公开符号,用另一个装私人符号意味着如果你改变了这个源码文件的内部结构,你可以只是重新编译它而 不需要重新编译那些使用它的公开 header 文件的其它的源文 件。

iii) 不要在多个 header 文件中重复定义信息。 如果需要, 在其中一个 header 文件里 #include 另一个,但是不要重复输入相同的 header 信息两次。原因是如果你以后改 变了这个信息,你只需要把它改变一次,不用搜索并改变另外一 个重复的信息。

iv) 在每一个源码文件里, #include 那些声明了源码文件中的符 号的所有 header 文件。这样一来,你在源码文件和 header 文件对某些函数做出的矛盾声明可以比较容易的被编译器发现。

1.4 对于常见错误的注释


a) 定义符 (Identifier) 在源码文件中的矛盾:在C里,变量和函数的缺省状态是公用的。因此,任何C源码档案都可以引用存在于其它源 码档中的通用 (global) 函数和通用变量,既使这个档案没有那个变量或函数的声明或原型。因此你必须保证在不同的两个档案里不能 用同一个符号名称,否则会有连接错误或者在编译时会有警告。

一种避免这种错误的方法是在公用的符号前加上跟其所在源文件有 关的前缀。比如:所有在 gfx.c 里的函数都加上前缀“gfx_”。如果 你很小心的分解你的程序,使用有意义的函数名称,并且不是过分 使用通用变量,当然这根本就不是问题。

要防止一个符号在它被定义的源文件以外被看到,可在它的定义前 加上关键字“static”。这对只在一个档案内部使用,其它档案都 都不会用到的简单函数是很有用的。

b) 多次定义的符号: header 档会被逐字的替换到你源文件里 #include 的位置的。因此,如果 header 档被 #include 到一个以上的源文件 里,这个 header 档中所有的定义就会出现在每一个有关的源码文件里。这会使它们里的符号被定义一次以上,从而出现连接错误(见 上)。

解决方法: 不要在 header 档里定义变量。你只需要在 header 档里声明它们然后在适当的C源码文件(应该 #include 那个 header 档的那个)里定义它们(一次)。对于初学者来说,定义和声明是 很容易混淆的。声明的作用是告诉编译器其所声明的符号应该存在,并且要有所指定的类型。但是,它并不会使编译器分配贮存空间。 而定义的做用是要求编译器分配贮存空间。当做一个声明而不是做定义的时候,在声明前放一个关键字“extern”。

例如,我们有一个叫“counter”的变量,如果想让它成为公用的, 我们在一个源码程序(只在一个里面)的开始定义它:“int counter;”,再在相关的 header 档里声明它:“extern int counter;”。

函数原型里隐含着 extern 的意思,所以不需顾虑这个问题。

c) 重复定义,重复声明,矛盾类型:
请考虑如果在一个C源码文件中 #include 两个档 a.h 和 b.h, 而 a.h 又 #include 了 b.h 档(原因是 b.h 档定义了一些 a.h 需要的类型),会发生什么事呢?这时该C源码文件 #include 了 b.h 两次。因此每一个在 b.h 中的 #define 都发生了两次,每一 个声明发生了两次,等等。理论上,因为它们是完全一样的拷贝,所以应该不会有什么问题,但在实际应用上,这是不符合C的语法 的,可能在编译时出现错误,或至少是警告。


解决的方法是要确定每一个 header 档在任一个源码文件中只被包 含了一次。我们一般是用预处理器来达到这个目的的。当我们进入 每一个 header 档时,我们为这个 header 档 #define 一个巨集 指令。只有在这个巨集指令没有被定义的前提下,我们才真正使用 该 header 档的主体。在实际应用上,我们只要简单的把下面一段 码放在每一个 header 档的开始部分:

#ifndef FILENAME_H
#define FILENAME_H

然后把下面一行码放在最后:

#endif

用 header 档的档名(大写的)代替上面的 FILENAME_H,用底线 代替档名中的点。有些人喜欢在 #endif 加上注释来提醒他们这个 #endif 指的是什么。例如:

#endif /* #ifndef FILENAME_H */

我个人没有这个习惯,因为这其实是很明显的。当然这只是各人的 风格不同,无伤大雅。

你只需要在那些有编译错误的 header 档中加入这个技巧,但在所 有的 header 档中都加入也没什么损失,到底这是个好习惯。
1.5 重新编译一个多文件项目

清楚的区别编译和连接是很重要的。编译器使用源码文件来产生某种 形式的目标文件(object files)。在这个过程中,外部的符号参考并 没有被解释或替换。然后我们使用连接器来连接这些目标文件和一些标准的程序包再加你指定的程序包,最后连接生成一个可执行程序。 在这个阶段,一个目标文件中对别的文件中的符号的参考被解释,并报告不能被解释的参考,一般是以错误信息的形式报告出来。

基本的步骤就应该是,把你的源码文件一个一个的编译成目标文件的格 式,最后把所有的目标文件加上需要的程序包连接成一个可执行文件。具体怎么做是由你的编译器决定的。这里我只给出 gcc (GNU C 编译 器)的有关命令,这些有可能对你的非 gcc 编译器也适用。

gcc 是一个多目标的工具。它在需要的时候呼叫其它的元件(预处理 程序,编译器,组合程序,连接器)。具体的哪些元件被呼叫取决于 输入文件的类型和你传递给它的开关。

一般来说,如果你只给它C源码文件,它将预处理,编译,组合所有 的文件,然后把所得的目标文件连接成一个可执行文件(一般生成的 文件被命名为 a.out )。你当然可以这么做,但这会破坏很多我们 把一个项目分解成多个文件所得到的好处。

如果你给它一个 -c 开关,gcc 只把给它的文件编译成目标文件,用源码文件的文件名命名但把其后缀由“.c”或“.cc”变成“.o”。 如果你给它的是一列目标文件, gcc 会把它们连接成可执行文件,缺省文件名是 a.out 。你可以改变缺省名,用开关 -o 后跟你指定 的文件名。

因此,当你改变了一个源码文件后,你需要重新编译它: 'gcc -c filename.c' 然后重新连接你的项目: 'gcc -o exec_filename *.o'。 如果你改变了一个 header 档,你需要重新编译所有 #include 过这个档的源码文件,你可以用 'gcc -c file1.c file2.c file3.c' 然后象上边一样连接。

当然这么做是很繁琐的,幸亏我们有些工具使这个步骤变得简单。 本文的第二部分就是介绍其中的一件工具:GNU Make 工具。

(好家伙,现在才开始见真章。您学到点儿东西没?)

3 .3 GNU Make 工具

~~~~~~~~~~~~~~~~

2.1 基本 makefile 结构

GNU Make 的主要工作是读进一个文本文件, makefile 。这个文件里主要是有关哪些文件(‘target’目的文件)是从哪些别的 文件(‘dependencies’依靠文件)中产生的,用什么命令来进行这个产生过程。有了这些信息, make 会检查磁碟上的文件,如果 目的文件的时间戳(该文件生成或被改动时的时间)比至少它的一个依靠文件旧的话, make 就执行相应的命令,以便更新目的文件。 (目的文件不一定是最后的可执行档,它可以是任何一个文件。)

makefile 一般被叫做“makefile”或“Makefile”。当然你可以 在 make 的命令行指定别的文件名。如果你不特别指定,它会寻 找“makefile”或“Makefile”,因此使用这两个名字是最简单 的。

一个 makefile 主要含有一系列的规则,如下:

: ...
(tab)<command>
(tab)<command>
.
.
.

例如,考虑以下的 makefile :

=== makefile 开始 ===
myprog : foo.o bar.o
gcc foo.o bar.o -o myprog

foo.o : foo.c foo.h bar.h
gcc -c foo.c -o foo.o

bar.o : bar.c bar.h
gcc -c bar.c -o bar.o
=== makefile 结束 ===

这是一个非常基本的 makefile —— make 从最上面开始,把上面第一个目的,‘myprog’,做为它的主要目标(一个它需要保 证其总是最新的最终目标)。给出的规则说明只要文件‘myprog’ 比文件‘foo.o’或‘bar.o’中的任何一个旧,下一行的命令将 会被执行。


但是,在检查文件 foo.o 和 bar.o 的时间戳之前,它会往下查 找那些把 foo.o 或 bar.o 做为目标文件的规则。它找到的关于 foo.o 的规则,该文件的依靠文件是 foo.c, foo.h 和 bar.h 。 它从下面再找不到生成这些依靠文件的规则,它就开始检查磁碟上这些依靠文件的时间戳。如果这些文件中任何一个的时间戳比 foo.o 的新,命令 'gcc -o foo.o foo.c' 将会执行,从而更新文件 foo.o 。

接下来对文件 bar.o 做类似的检查,依靠文件在这里是文件 bar.c 和 bar.h 。

现在, make 回到‘myprog’的规则。如果刚才两个规则中的任 何一个被执行,myprog 就需要重建(因为其中一个 .o 档就会比 ‘myprog’新),因此连接命令将被执行。

希望到此,你可以看出使用 make 工具来建立程序的好处——前 一章中所有繁琐的检查步骤都由 make 替你做了:检查时间戳。你的源码文件里一个简单改变都会造成那个文件被重新编译(因 为 .o 文件依靠 .c 文件),进而可执行文件被重新连接(因为 .o 文件被改变了)。其实真正的得益是在当你改变一个 header 档的时候——你不再需要记住那个源码文件依靠它,因为所有的 资料都在 makefile 里。 make 会很轻松的替你重新编译所有那 些因依靠这个 header 文件而改变了的源码文件,如有需要,再 进行重新连接。

当然,你要确定你在 makefile 中所写的规则是正确无误的,只 列出那些在源码文件中被 #include 的 header 档……


2.2 编写 make 规则 (Rules)

最明显的(也是最简单的)编写规则的方法是一个一个的查 看源码文件,把它们的目标文件做为目的,而C源码文件和被它 #include 的 header 档做为依靠文件。但是你也要把其它被这些 header 档 #include 的 header 档也列为依靠文件,还有那些被包括的文件所包括的文件……然后你会发现要对越来越多的文件 进行管理,然后你的头发开始脱落,你的脾气开始变坏,你的脸色变成菜色,你走在路上开始跟电线杆子碰撞,终于你捣毁你的 电脑显示器,停止编程。到低有没有些容易点儿的方法呢?

当然有!向编译器要!在编译每一个源码文件的时候,它实在应 该知道应该包括什么样的 header 档。使用 gcc 的时候,用 -M 开关,它会为每一个你给它的C文件输出一个规则,把目标文件 做为目的,而这个C文件和所有应该被 #include 的 header 文件将做为依靠文件。注意这个规则会加入所有 header 文件,包 括被角括号(`<', `>')和双引号(`"')所包围的文件。其实我们可以 相当肯定系统 header 档(比如 stdio.h, stdlib.h 等等)不会 被我们更改,如果你用 -MM 来代替 -M 传递给 gcc,那些用角括 号包围的 header 档将不会被包括。(这会节省一些编译时间)

由 gcc 输出的规则不会含有命令部分;你可以自己写入你的命令 或者什么也不写,而让 make 使用它的隐含的规则(参考下面的 2.4 节)。

2.3 Makefile 变量

上面提到 makefiles 里主要包含一些规则。它们包含的其它的东 西是变量定义。

makefile 里的变量就像一个环境变量(environment variable)。 事实上,环境变量在 make 过程中被解释成 make 的变量。这些 变量是大小写敏感的,一般使用大写字母。它们可以从几乎任何 地方被引用,也可以被用来做很多事情,比如:

i) 贮存一个文件名列表。在上面的例子里,生成可执行文件的 规则包含一些目标文件名做为依靠。在这个规则的命令行 里同样的那些文件被输送给 gcc 做为命令参数。如果在这 里使用一个变数来贮存所有的目标文件名,加入新的目标 文件会变的简单而且较不易出错。

ii) 贮存可执行文件名。如果你的项目被用在一个非 gcc 的系 统里,或者如果你想使用一个不同的编译器,你必须将所 有使用编译器的地方改成用新的编译器名。但是如果使用一 个变量来代替编译器名,那么你只需要改变一个地方,其 它所有地方的命令名就都改变了。

iii) 贮存编译器旗标。假设你想给你所有的编译命令传递一组 相同的选项(例如 -Wall -O -g);如果你把这组选项存 入一个变量,那么你可以把这个变量放在所有呼叫编译器 的地方。而当你要改变选项的时候,你只需在一个地方改 变这个变量的内容。

要设定一个变量,你只要在一行的开始写下这个变量的名字,后 面跟一个 = 号,后面跟你要设定的这个变量的值。以后你要引用 这个变量,写一个 $ 符号,后面是围在括号里的变量名。比如在 下面,我们把前面的 makefile 利用变量重写一遍:

=== makefile 开始 ===
OBJS = foo.o bar.o
CC = gcc
CFLAGS = -Wall -O -g

myprog : $(OBJS)
$(CC) $(OBJS) -o myprog

foo.o : foo.c foo.h bar.h
$(CC) $(CFLAGS) -c foo.c -o foo.o

bar.o : bar.c bar.h
$(CC) $(CFLAGS) -c bar.c -o bar.o
=== makefile 结束 ===

还有一些设定好的内部变量,它们根据每一个规则内容定义。三个比较有用的变量是 $*,$?,$@, $< 和 $^ (这些变量不需要括号括住)。

$@ 扩展成当前规则的目的文件名, $< 扩展成依靠列表中的第一个依靠文件,而 $^ 扩展成整个依靠的列表(除掉了里面所有重 复的文件名)。$?比目标文件(target)新的dependent file.$?的值只有在使用外显示(explicit)的规则时才会被设定.$*是内存dependent file的文件名,不含扩展名.

利用这些变量,我们可以把上面的 makefile 写成:

=== makefile 开始 ===
OBJS = foo.o bar.o
CC = gcc
CFLAGS = -Wall -O -g

myprog : $(OBJS)
$(CC) $^ -o $@

foo.o : foo.c foo.h bar.h
$(CC) $(CFLAGS) -c $< -o $@

bar.o : bar.c bar.h
$(CC) $(CFLAGS) -c $< -o $@
=== makefile 结束 ===

你可以用变量做许多其它的事情,特别是当你把它们和函数混合 使用的时候。如果需要更进一步的了解,请参考 GNU Make 手册。 ('man make', 'man makefile')

2.4 隐含规则 (Implicit Rules)

请注意,在上面的例子里,几个产生 .o 文件的命令都是一样的。 都是从 .c 文件和相关文件里产生 .o 文件,这是一个标准的步骤。其实 make 已经知道怎么做——它有一些叫做隐含规则的内 置的规则,这些规则告诉它当你没有给出某些命令的时候,应该怎么办。

如果你把生成 foo.o 和 bar.o 的命令从它们的规则中删除, make 将会查找它的隐含规则,然后会找到一个适当的命令。它的命令会 使用一些变量,因此你可以按照你的想法来设定它:它使用变量 CC 做为编译器(象我们在前面的例子),并且传递变量 CFLAGS (给 C 编译器,C++ 编译器用 CXXFLAGS ),CPPFLAGS ( C 预 处理器旗标), TARGET_ARCH (现在不用考虑这个),然后它加 入旗标 '-c' ,后面跟变量 $< (第一个依靠名),然后是旗 标 '-o' 跟变量 $@ (目的文件名)。一个C编译的具体命令将 会是:

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

当然你可以按照你自己的需要来定义这些变量。这就是为什么用 gcc 的 -M 或 -MM 开关输出的码可以直接用在一个 makefile 里

2.5 假象目的 (Phony Targets)

假设你的一个项目最后需要产生两个可执行文件。你的主要目标 是产生两个可执行文件,但这两个文件是相互独立的——如果一个文件需要重建,并不影响另一个。你可以使用“假象目的”来 达到这种效果。一个假象目的跟一个正常的目的几乎是一样的,只是这个目的文件是不存在的。因此, make 总是会假设它需要 被生成,当把它的依赖文件更新后,就会执行它的规则里的命令 行。

如果在我们的 makefile 开始处输入:

all : exec1 exec2

其中 exec1 和 exec2 是我们做为目的的两个可执行文件。 make 把这个 'all' 做为它的主要目的,每次执行时都会尝试把 'all' 更新。但既然这行规则里没有哪个命令来作用在一个叫 'all' 的 实际文件(事实上 all 并不会在磁碟上实际产生),所以这个规 则并不真的改变 'all' 的状态。可既然这个文件并不存在,所以 make 会尝试更新 all 规则,因此就检查它的依靠 exec1, exec2 是否需要更新,如果需要,就把它们更新,从而达到我们的目的。

假象目的也可以用来描述一组非预设的动作。例如,你想把所有由 make 产生的文件删除,你可以在 makefile 里设立这样一个规则:

veryclean :
rm *.o
rm myprog

前提是没有其它的规则依靠这个 'veryclean' 目的,它将永远 不会被执行。但是,如果你明确的使用命令 'make veryclean' , make 会把这个目的做为它的主要目标,执行那些 rm 命令。

如果你的磁碟上存在一个叫 veryclean 文件,会发生什么事?这 时因为在这个规则里没有任何依靠文件,所以这个目的文件一定是最新的了(所有的依靠文件都已经是最新的了),所以既使用户明 确命令 make 重新产生它,也不会有任何事情发生。解决方法是标明所有的假象目的(用 .PHONY),这就告诉 make 不用检查它们 是否存在于磁碟上,也不用查找任何隐含规则,直接假设指定的目的需要被更新。在 makefile 里加入下面这行包含上面规则的规则:

.PHONY : veryclean

就可以了。注意,这是一个特殊的 make 规则,make 知道 .PHONY 是一个特殊目的,当然你可以在它的依靠里加入你想用的任何假象 目的,而 make 知道它们都是假象目的。


2.6 函数 (Functions)

makefile 里的函数跟它的变量很相似——使用的时候,你用一个 $ 符号跟开括号,函数名,空格后跟一列由逗号分隔的参数,最后用关括号结束。例如,在 GNU Make 里有一个叫 'wildcard' 的函 数,它有一个参数,功能是展开成一列所有符合由其参数描述的文件名,文件间以空格间隔。你可以像下面所示使用这个命令:

SOURCES = $(wildcard *.c)

这行会产生一个所有以 '.c' 结尾的文件的列表,然后存入变量 SOURCES 里。当然你不需要一定要把结果存入一个变量。

另一个有用的函数是 patsubst ( patten substitude, 匹配替换的缩写)函数。它需要3个参数——第一个是一个需要匹配的 式样,第二个表示用什么来替换它,第三个是一个需要被处理的由空格分隔的字列。例如,处理那个经过上面定义后的变量,

OBJS = $(patsubst %.c,%.o,$(SOURCES))

这行将处理所有在 SOURCES 字列中的字(一列文件名),如果它的 结尾是 '.c' ,就用 '.o' 把 '.c' 取代。注意这里的 % 符号将匹 配一个或多个字符,而它每次所匹配的字串叫做一个‘柄’(stem) 。 在第二个参数里, % 被解读成用第一参数所匹配的那个柄。


2.7 一个比较有效的 makefile

利用我们现在所学的,我们可以建立一个相当有效的 makefile 。 这个 makefile 可以完成大部分我们需要的依靠检查,不用做太大 的改变就可直接用在大多数的项目里。

首先我们需要一个基本的 makefile 来建我们的程序。我们可以让 它搜索当前目录,找到源码文件,并且假设它们都是属于我们的项目的,放进一个叫 SOURCES 的变量。这里如果也包含所有的 *.cc 文件,也许会更保险,因为源码文件可能是 C++ 码的。

SOURCES = $(wildcard *.c *.cc)

利用 patsubst ,我们可以由源码文件名产生目标文件名,我们需 要编译出这些目标文件。如果我们的源码文件既有 .c 文件,也有 .cc 文件,我们需要使用相嵌的 patsubst 函数呼叫:

OBJS = $(patsubst %.c,%.o,$(patsubst %.cc,%.o,$(SOURCES)))

最里面一层 patsubst 的呼叫会对 .cc 文件进行后缀替代,产生的结 果被外层的 patsubst 呼叫处理,进行对 .c 文件后缀的替代。

现在我们可以设立一个规则来建可执行文件:

myprog : $(OBJS)
gcc -o myprog $(OBJS)

进一步的规则不一定需要, gcc 已经知道怎么去生成目标文件 (object files) 。下面我们可以设定产生依靠信息的规则:

depends : $(SOURCES)
gcc -M $(SOURCES) > depends

在这里如果一个叫 'depends' 的文件不存在,或任何一个源码文件 比一个已存在的 depends 文件新,那么一个 depends 文件会被生 成。depends 文件将会含有由 gcc 产生的关于源码文件的规则(注 意 -M 开关)。现在我们要让 make 把这些规则当做 makefile 档 的一部分。这里使用的技巧很像 C 语言中的 #include 系统——我 们要求 make 把这个文件 include 到 makefile 里,如下:

include depends

GNU Make 看到这个,检查 'depends' 目的是否更新了,如果没有, 它用我们给它的命令重新产生 depends 档。然后它会把这组(新) 规则包含进来,继续处理最终目标 'myprog' 。当看到有关 myprog 的规则,它会检查所有的目标文件是否更新——利用 depends 文件 里的规则,当然这些规则现在已经是更新过的了。

这个系统其实效率很低,因为每当一个源码文件被改动,所有的源码 文件都要被预处理以产生一个新的 'depends' 文件。而且它也不是 100% 的安全,这是因为当一个 header 档被改动,依靠信息并不会 被更新。但就基本工作来说,它也算相当有用的了。

2.8 一个更好的 makefile

这是一个我为我大多数项目设计的 makefile 。它应该可以不需要修 改的用在大部分项目里。我主要把它用在 djgpp 上,那是一个 DOS 版的 gcc 编译器。因此你可以看到执行的命令名、 'alleg' 程序包、 和 RM -F 变量都反映了这一点。

=== makefile 开始 ===

######################################
#
# Generic makefile
#
# by George Foot
# email: george.foot@merton.ox.ac.uk
#
# Copyright (c) 1997 George Foot
# All rights reserved.
# 保留所有版权
#
# No warranty, no liability;
# you use this at your own risk.
# 没保险,不负责
# 你要用这个,你自己担风险
#
# You are free to modify and
# distribute this without giving
# credit to the original author.
# 你可以随便更改和散发这个文件
# 而不需要给原作者什么荣誉。
# (你好意思?)
#
######################################

### Customising
# 用户设定
#
# Adjust the following if necessary; EXECUTABLE is the target
# executable's filename, and LIBS is a list of libraries to link in
# (e.g. alleg, stdcx, iostr, etc). You can override these on make's
# command line of course, if you prefer to do it that way.
#
# 如果需要,调整下面的东西。 EXECUTABLE 是目标的可执行文件名, LIBS
# 是一个需要连接的程序包列表(例如 alleg, stdcx, iostr 等等)。当然你
# 可以在 make 的命令行覆盖它们,你愿意就没问题。
#

EXECUTABLE := mushroom.exe
LIBS := alleg

# Now alter any implicit rules' variables if you like, e.g.:
#
# 现在来改变任何你想改动的隐含规则中的变量,例如

CFLAGS := -g -Wall -O3 -m486
CXXFLAGS := $(CFLAGS)

# The next bit checks to see whether rm is in your djgpp bin
# directory; if not it uses del instead, but this can cause (harmless)
# `File not found' error messages. If you are not using DOS at all,
# set the variable to something which will unquestioningly remove
# files.
#
# 下面先检查你的 djgpp 命令目录下有没有 rm 命令,如果没有,我们使用
# del 命令来代替,但有可能给我们 'File not found' 这个错误信息,这没
# 什么大碍。如果你不是用 DOS ,把它设定成一个删文件而不废话的命令。
# (其实这一步在 UNIX 类的系统上是多余的,只是方便 DOS 用户。 UNIX
# 用户可以删除这5行命令。)

ifneq ($(wildcard $(DJDIR)/bin/rm.exe),)
RM-F := rm -f
else
RM-F := del
endif

# You shouldn't need to change anything below this point.
#
# 从这里开始,你应该不需要改动任何东西。(我是不太相信,太NB了!)

SOURCE := $(wildcard *.c) $(wildcard *.cc)
OBJS := $(patsubst %.c,%.o,$(patsubst %.cc,%.o,$(SOURCE)))
DEPS := $(patsubst %.o,%.d,$(OBJS))
MISSING_DEPS := $(filter-out $(wildcard $(DEPS)),$(DEPS))
MISSING_DEPS_SOURCES := $(wildcard $(patsubst %.d,%.c,$(MISSING_DEPS)) \
$(patsubst %.d,%.cc,$(MISSING_DEPS)))
CPPFLAGS += -MD

.PHONY : everything deps objs clean veryclean rebuild

everything : $(EXECUTABLE)

deps : $(DEPS)

objs : $(OBJS)

clean :
@$(RM-F) *.o
@$(RM-F) *.d

veryclean: clean
@$(RM-F) $(EXECUTABLE)

rebuild: veryclean everything

ifneq ($(MISSING_DEPS),)
$(MISSING_DEPS) :
@$(RM-F) $(patsubst %.d,%.o,$@)
endif

-include $(DEPS)

$(EXECUTABLE) : $(OBJS)
gcc -o $(EXECUTABLE) $(OBJS) $(addprefix -l,$(LIBS))

=== makefile 结束 ===

有几个地方值得解释一下的。首先,我在定义大部分变量的时候使 用的是 := 而不是 = 符号。它的作用是立即把定义中参考到的函 数和变量都展开了。如果使用 = 的话,函数和变量参考会留在那 儿,就是说改变一个变量的值会导致其它变量的值也被改变。例 如:

A = foo
B = $(A)
# 现在 B 是 $(A) ,而 $(A) 是 'foo' 。
A = bar
# 现在 B 仍然是 $(A) ,但它的值已随着变成 'bar' 了。
B := $(A)
# 现在 B 的值是 'bar' 。
A = foo
# B 的值仍然是 'bar' 。

make 会忽略在 # 符号后面直到那一行结束的所有文字。

ifneg...else...endif 系统是 makefile 里让某一部分码有条件的 失效/有效的工具。 ifeq 使用两个参数,如果它们相同,它把直 到 else (或者 endif ,如果没有 else 的话)的一段码加进 makefile 里;如果不同,把 else 到 endif 间的一段码加入 makefile (如果有 else )。 ifneq 的用法刚好相反。

'filter-out' 函数使用两个用空格分开的列表,它把第二列表中所 有的存在于第一列表中的项目删除。我用它来处理 DEPS 列表,把所 有已经存在的项目都删除,而只保留缺少的那些。

我前面说过, CPPFLAGS 存有用于隐含规则中传给预处理器的一些 旗标。而 -MD 开关类似 -M 开关,但是从源码文件 .c 或 .cc 中 形成的文件名是使用后缀 .d 的(这就解释了我形成 DEPS 变量的 步骤)。DEPS 里提到的文件后来用 '-include' 加进了 makefile 里,它隐藏了所有因文件不存在而产生的错误信息。

如果任何依靠文件不存在, makefile 会把相应的 .o 文件从磁碟 上删除,从而使得 make 重建它。因为 CPPFLAGS 指定了 -MD , 它的 .d 文件也被重新产生。

最后, 'addprefix' 函数把第二个参数列表的每一项前缀上第一 个参数值。

这个 makefile 的那些目的是(这些目的可以传给 make 的命令行 来直接选用):

everything:(预设) 更新主要的可执行程序,并且为每一个 源码文件生成或更新一个 '.d' 文件和一个 '.o' 文件。

deps: 只是为每一个源码程序产生或更新一个 '.d' 文件。

objs: 为每一个源码程序生成或更新 '.d' 文件和目标文件。

clean: 删除所有中介/依靠文件( *.d 和 *.o )。

veryclean: 做 `clean' 和删除可执行文件。

rebuild: 先做 `veryclean' 然后 `everything' ;既完全重建。

除了预设的 everything 以外,这里头只有 clean , veryclean , 和 rebuild 对用户是有意义的。

我还没有发现当给出一个源码文件的目录,这个 makefile 会失败的 情况,除非依靠文件被弄乱。如果这种弄乱的情况发生了,只要输入 `make clean' ,所有的目标文件和依靠文件会被删除,问题就应该 被解决了。当然,最好不要把它们弄乱。如果你发现在某种情况下这 个 makefile 文件不能完成它的工作,请告诉我,我会把它整好的

3 总结
~~~~~~~~~~~~~~~

我希望这篇文章足够详细的解释了多文件项目是怎么运作的,也说明了 怎样安全而合理的使用它。到此,你应该可以轻松的利用 GNU Make 工 具来管理小型的项目,如果你完全理解了后面几个部分的话,这些对于 你来说应该没什么困难。

GNU Make 是一件强大的工具,虽然它主要是用来建立程序,它还有很多 别的用处。如果想要知道更多有关这个工具的知识,它的句法,函数,和许多别的特点,你应该参看它的参考文件 (info pages, 别的 GNU 工具也一样,看它们的 info pages. )。

老铁补充
Shell定义的特殊变量
$#:内存位置参数的个数
$$:该shell script的进程代号(pid)
$!:最后一个后台进程代号
$*:所有位置参数字符串,不限于9个参数
$@:与$*相似,但"$@"的值与"$*"不同
例:若$*= word1 word2 word3
则"$"=" word1 word2 word3"
"$@"=" word1" "word2" "word3"
例16.17
$ cat testsym
echo 'no. of positional parameters"'$#
echo 'process no. of current shell:'$$
ps&< echo 'process no. of last background shell:'$!
rm aaa


ld:UNIX连接器(link editor)
ld是建立可执行程序的最后一个步骤.

编译程序选项
-Ldir:额外加上ld要寻找程序库的目录
-lname:寻找程序库---libname.so或libname.a

文件库(Archive File)
ar key afile [files...]
r:将files加入afile或取代afile内的文件,files将被加在afile的最后
v:verboise.若是配合新增

3 .3.1 自动化变量

3 .3.2 

3 .3.3 函数介绍

3 .4 wildcard

3 .5 notdir

3 .6 patsubst

3 .7 makefile中子函数wildcard与notdir和patsubst的用法示例

1、wildcard : 扩展通配符

2、notdir : 去除路径

3、patsubst :替换通配符

例子:

建立一个测试目录,在测试目录下建立一个名为sub的子目录

$ mkdir test

$ cd test

$ mkdir sub

在test下,建立a.c和b.c2个文件,在sub目录下,建立sa.c和sb.c2 个文件

建立一个简单的Makefile

src=$(wildcard *.c ./sub/*.c)

dir=$(notdir $(src))

obj=$(patsubst %.c,%.o,$(dir) ) 

all:

 @echo $(src)

 @echo $(dir)

 @echo $(obj)

 @echo "end"

 

执行结果分析:

第一行输出:

a.c b.c ./sub/sa.c ./sub/sb.c

wildcard把 指定目录 ./ 和 ./sub/ 下的所有后缀是c的文件全部展开。

第二行输出:

a.c b.c sa.c sb.c

notdir把展开的文件去除掉路径信息

第三行输出:

a.o b.o sa.o sb.o

在$(patsubst %.c,%.o,$(dir) )中,patsubst把$(dir)中的变量符合后缀是.c的全部替换成.o,

任何输出。

或者可以使用

obj=$(dir:%.c=%.o)

效果也是一样的。

这里用到makefile里的替换引用规则,即用您指定的变量替换另一个变量。

它的标准格式是

$(var:a=b) 或 ${var:a=b}

它的含义是把变量var中的每一个值结尾用b替换掉a

4  automake的使用--helloworld

1) 建目录

在你的工作目录下建一个helloworld目录,我们用它来存放helloworld程序及相关文件,如在/home/my/build下:

$ mkdir helloword
$ cd helloworld

2) helloworld.c

然后用你自己最喜欢的编辑器写一个hellowrold.c文件,如命令:vi helloworld.c。使用下面的代码作为helloworld.c的内容。

int main(int argc, char** argv)

{

printf("Hello, Linux World!\n");
return 0;

}

完成后保存退出。

现在在helloworld目录下就应该有一个你自己写的helloworld.c了。

3) 生成configure

我们使用autoscan命令来帮助我们根据目录下的源代码生成一个configure.in的模板文件。

命令:

$ autoscan
$ ls
configure.scan helloworld.c

执行后在hellowrold目录下会生成一个文件:configure.scan,我们可以拿它作为configure.in的蓝本。

现在将configure.scan改名为configure.in,并且编辑它,按下面的内容修改,去掉无关的语句:

note:
Autoconf 2.50用configure.ac
9 B. J, u; s$ o' d% z4 mAutoconf 2.13用configure.in

#=====================configure.in内容开始=======================

# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

AC_INIT(helloworld.c)
AM_INIT_AUTOMAKE(helloworld, 1.0)

# Checks for programs.
AC_PROG_CC

# Checks for libraries.

# Checks for header files.

# Checks for typedefs, structures, and compiler characteristics.

# Checks for library functions.
AC_OUTPUT(Makefile)
#=====================configure.in内容结束=======================

然后执行命令aclocal和autoconf,分别会产生aclocal.m4及configure两个文件:

$ aclocal
$ls
aclocal.m4 configure.in helloworld.c
$ autoconf
$ ls
aclocal.m4 autom4te.cache configure configure.in helloworld.c

大家可以看到configure.in内容是一些宏定义,这些宏经autoconf处理后会变成检查系统特性、环境变量、软件必须的参数的shell脚本。

autoconf 是用来生成自动配置软件源代码脚本(configure)的工具。configure脚本能独立于autoconf运行,且在运行的过程中,不需要用户的干预。

要生成configure文件,你必须告诉autoconf如何找到你所用的宏。方式是使用aclocal程序来生成你的aclocal.m4。

aclocal根据configure.in文件的内容,自动生成aclocal.m4文件。aclocal是一个perl 脚本程序,它的定义是:“aclocal - create aclocal.m4 by scanning configure.ac”。

autoconf从configure.in这个列举编译软件时所需要各种参数的模板文件中创建configure。

autoconf需要GNU m4宏处理器来处理aclocal.m4,生成configure脚本。

m4是一个宏处理器。将输入拷贝到输出,同时将宏展开。宏可以是内嵌的,也可以是用户定义的。除了可以展开宏,m4还有一些内建的函数,用来引用文件,执行命令,整数运算,文本操作,循环等。m4既可以作为编译器的前端,也可以单独作为一个宏处理器。

4) 新建Makefile.am

新建Makefile.am文件,命令:

$ vi Makefile.am

内容如下:

AUTOMAKE_OPTIONS=foreign
bin_PROGRAMS=helloworld
helloworld_SOURCES=helloworld.c

automake会根据你写的Makefile.am来自动生成Makefile.in。

Makefile.am中定义的宏和目标,会指导automake生成指定的代码。例如,宏bin_PROGRAMS将导致编译和连接的目标被生成。

5) 运行automake

命令:

$ automake --add-missing
configure.in: installing `./install-sh'
configure.in: installing `./mkinstalldirs'
configure.in: installing `./missing'
Makefile.am: installing `./depcomp'

automake会根据Makefile.am文件产生一些文件,包含最重要的Makefile.in。

6) 执行configure生成Makefile

$ ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for gawk... gawk
checking whether make sets $(MAKE)... yes
checking for gcc... gcc
checking for C compiler default output... a.out
checking whether the C compiler works... yes
checking whether we are cross compiling... no
checking for suffix of executables...
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ANSI C... none needed
checking for style of include used by make... GNU
checking dependency style of gcc... gcc3
configure: creating ./config.status
config.status: creating Makefile
config.status: executing depfiles commands
$ ls -l Makefile
-rw-rw-r-- 1 yutao yutao 15035 Oct 15 10:40 Makefile

你可以看到,此时Makefile已经产生出来了。

7) 使用Makefile编译代码

$ make

if gcc -DPACKAGE_NAME="" -DPACKAGE_TARNAME="" -DPACKAGE_VERSION="" -
DPACKAGE_STRING="" -DPACKAGE_BUGREPORT="" -DPACKAGE="helloworld" -DVERSION="1.0"

-I. -I. -g -O2 -MT helloworld.o -MD -MP -MF ".deps/helloworld.Tpo" \
-c -o helloworld.o `test -f 'helloworld.c' || echo './'`helloworld.c; \
then mv -f ".deps/helloworld.Tpo" ".deps/helloworld.Po"; \
else rm -f ".deps/helloworld.Tpo"; exit 1; \
fi
gcc -g -O2 -o helloworld helloworld.o 

8) 运行helloworld

$ ./helloworld
Hello, Linux World!

5  深入浅出automake

针对上面提到的各个命令,我们再做些详细的介绍。

5 .1 autoscan

autoscan是用来扫描源代码目录生成configure.scan文件的。autoscan可以用目录名做为参数,但如果你不使用参数的话,那么 autoscan将认为使用的是当前目录。autoscan将扫描你所指定目录中的源文件,并创建configure.scan文件。

5 .2 configure.scan

configure.scan包含了系统配置的基本选项,里面都是一些宏定义。我们需要将它改名为configure.in

5 .3 aclocal

aclocal是一个perl 脚本程序。aclocal根据configure.in文件的内容,自动生成aclocal.m4文件。aclocal的定义是:“aclocal - create aclocal.m4 by scanning configure.ac”。

5 .4 autoconf

autoconf是用来产生configure文件的。configure是一个脚本,它能设置源程序来适应各种不同的操作系统平台,并且根据不同的系统来产生合适的Makefile,从而可以使你的源代码能在不同的操作系统平台上被编译出来。

configure.in文件的内容是一些宏,这些宏经过autoconf 处理后会变成检查系统特性、环境变量、软件必须的参数的shell脚本。configure.in文件中的宏的顺序并没有规定,但是你必须在所有宏的最前 面和最后面分别加上AC_INIT宏和AC_OUTPUT宏。

在configure.ini中:

#号表示注释,这个宏后面的内容将被忽略。

AC_INIT(FILE)

这个宏用来检查源代码所在的路径。

AM_INIT_AUTOMAKE(PACKAGE, VERSION) 

这个宏是必须的,它描述了我们将要生成的软件包的名字及其版本号:PACKAGE是软件包的名字,VERSION是版本号。当你使用make dist命令时,它会给你生成一个类似helloworld-1.0.tar.gz的软件发行包,其中就有对应的软件包的名字和版本号。

AC_PROG_CC

这个宏将检查系统所用的C编译器。

AC_OUTPUT(FILE)

这个宏是我们要输出的Makefile的名字。

我们在使用automake时,实际上还需要用到其他的一些宏,但我们可以用aclocal 来帮我们自动产生。执行aclocal后我们会得到aclocal.m4文件。

产生了configure.in和aclocal.m4 两个宏文件后,我们就可以使用autoconf来产生configure文件了。

5 .5 Makefile.am

Makefile.am是用来生成Makefile.in的,需要你手工书写。Makefile.am中定义了一些内容:

AUTOMAKE_OPTIONS

这个是automake的选项。在执行automake时,它会检查目录下是否存在标准GNU软件包中应具备的各种文件,例如AUTHORS、ChangeLog、NEWS等文件。我们将其设置成foreign时,automake会改用一般软件包的标准来检查。

bin_PROGRAMS

这个是指定我们所要产生的可执行文件的文件名。如果你要产生多个可执行文件,那么在各个名字间用空格隔开。

helloworld_SOURCES

这个是指定产生“helloworld”时所需要的源代码。如果它用到了多个源文件,那么请使用空格符号将它们隔开。比如需要 helloworld.h,helloworld.c那么请写成helloworld_SOURCES= helloworld.h helloworld.c。

如果你在bin_PROGRAMS定义了多个可执行文件,则对应每个可执行文件都要定义相对的filename_SOURCES。

5 .6 automake

我们使用automake --add-missing来产生Makefile.in。

选项--add-missing的定义是“add missing standard files to package”,它会让automake加入一个标准的软件包所必须的一些文件。

我们用automake产生出来的Makefile.in文件是符合GNU Makefile惯例的,接下来我们只要执行configure这个shell 脚本就可以产生合适的 Makefile 文件了。

./configure

make

5 .7 Makefile

在符合GNU Makefiel惯例的Makefile中,包含了一些基本的预先定义的操作:

make

根据Makefile编译源代码,连接,生成目标文件,可执行文件。

make clean

清除上次的make命令所产生的object文件(后缀为“.o”的文件)及可执行文件。

make install

将编译成功的可执行文件安装到系统目录中,一般为/usr/local/bin目录。

make dist

产生发布软件包文件(即distribution package)。这个命令将会将可执行文件及相关文件打包成一个tar.gz压缩的文件用来作为发布软件的软件包。

它会在当前目录下生成一个名字类似“PACKAGE-VERSION.tar.gz”的文件。PACKAGE和VERSION,是我们在configure.in中定义的AM_INIT_AUTOMAKE(PACKAGE, VERSION)。

make distcheck

生成发布软件包并对其进行测试检查,以确定发布包的正确性。这个操作将自动把压缩包文件解开,然后执行configure命令,并且执行make,来确认编译不出现错误,最后提示你软件包已经准备好,可以发布了。

===============================================
helloworld-1.0.tar.gz is ready for distribution
===============================================
make distclean

类似make clean,但同时也将configure生成的文件全部删除掉,包括Makefile。

五、结束语

通过上面的介绍,你应该可以很容易地生成一个你自己的符合GNU惯例的Makefile文件及对应的项目文件。

如果你想写出更复杂的且符合惯例的Makefile,你可以参考一些开放代码的项目中的configure.in和Makefile.am文件,比如:嵌入式数据库sqlite,单元测试cppunit。

6  automake的使用—多级目录使用

   在入门篇我们简单介绍了使用automake自动产生makefile的几个关键步骤,所有文件都在同一个目录下。但在比较大的项目中,很少将所有文件放在一个目录下的。本文针对这种情况做个简单介绍。

多级目录结构的软件,一般是单个程序、库文件或模块放在各自的目录中。automake要求每个目录都有自己的Makefile.am文件来编译各自目录下的代码。在顶级的目录中,有一个Makefile.am文件,该文件通过SUBDIRS指明了这个目录下有多少个直接下级目录的代码需要编译。下级目录的Makefile.am也指明自己需要编译的下级目录。通过这样的层层递归i,从而完成多级目录结构的编译。

下面看一个具体的示例:

本例假设目录结构如下:

helloworld    
    |src
    ||--test.cpp
    ||dohello   
    |||--dohello.h
    |||--dohello.cpp 
    |doc
    ||--userguide

顶级目录helloworld,该目录下有src和doc两个目录。src目录下有一个test.cpp文件,一个dohello目录,dohello目录下又有dohello.h和dohello.cpp两个文件。doc下有一个readme文件。
    整个程序中test.cpp是主文件,里面有main函数。test.cpp调用了dohello.h和dohello.cpp中的类。

下面是编译步骤:

6 .1 运行autoscan产生configure.scan

    在顶级目录helloworld下运行autoscan命令。产生configure.scan文件内容如下:
#                                               -*- Autoconf -*-

# Process this file with autoconf to produce a configure script.

AC_PREREQ(2.59)

AC_INIT(FULL-PACKAGE-NAME, VERSION, BUG-REPORT-ADDRESS)

AC_CONFIG_SRCDIR([src/test.cpp])

AC_CONFIG_HEADER([config.h])

# Checks for programs.

AC_PROG_CXX

AC_PROG_CC

# Checks for libraries.

# Checks for header files.

AC_HEADER_STDC

AC_CHECK_HEADERS([stdlib.h])

# Checks for typedefs, structures, and compiler characteristics.

# Checks for library functions.

AC_OUTPUT

6 .2 编辑configure.ac

将configure.scan改名成configure.ac,然后修改如下:
#                                               -*- Autoconf -*-

# Process this file with autoconf to produce a configure script.

AC_PREREQ(2.59)

AC_INIT(hello, 1.0, quentin2052ck@gmail.com)#软件包名称和版本号,另一种写法是写在AM_INIT_AUTOMAKE中.

AC_CONFIG_SRCDIR([src/test.cpp])

#AC_CONFIG_HEADER([config.h])

AM_INIT_AUTOMAKE

# Checks for programs.

AC_PROG_CXX

AC_PROG_CC

AC_PROG_RANLIB       #使用了静态库编译,需要此宏定义

# Checks for libraries.

# Checks for header files.

AC_HEADER_STDC

AC_CHECK_HEADERS([stdlib.h])

# Checks for typedefs, structures, and compiler characteristics.

# Checks for library functions.

AC_OUTPUT(Makefile src/Makefile src/dohello/Makefile) #需要生成的Makefile,本例需要生成三个。

6 .3 运行aclocal和autoconf

configure.ac修改完后则先后执行aclocal和autoconf命令。

6 .4 建立各个目录的Makefile.am文件

    在顶级目录、src目录和dohello目录下分别建立三个Makefile.am文件。内容分别如下:

1. 顶级目录Makefile.am:

#----------------开始------------------------------------------     

AUTOMAKE_OPTIONS=foreign
SUBDIRS=src   #本目录的直接下级目录src需要编译
EXTRA_DIST=doc/userguide #doc/userguide不需要编译,但要发布该文件。如果有多个文件,则用空格分开。
#----------------结束------------------------------------------

2. src目录下的Makefile.am:

#----------------开始------------------------------------------ 
AUTOMAKE_OPTIONS=foreign
SUBDIRS=dohello #本目录的直接下级目录dohello需要编译
bin_PROGRAMS=hello #本目录的文件编译成可执行文件hello。如有多个,用空格分开。然后在下面分别写它们的SOURCE和LDADD。
hello_SOURCES=test.cpp #编译hello需要的源文件列表,如有多个,用空格分开。
hello_LDADD=dohello/libhello.a #编译hello需要的库文件列表。如有多个,用空格分开。
#----------------结束------------------------------------------

3. dohello目录下的Makefile.am

#----------------开始------------------------------------------    

AUTOMAKE_OPTIONS=foreign
noinst_LIBRARIES=libhello.a   #本目录下的代码编译成libhello.a库。不需要发布。如果需要发布,则写成bin_LIBRARIES。注意,库的名称格式必需为libxxx.a。因为编译静态库,configure.in需要定义AC_PROG_RANLIB宏。
libhello_a_SOURCES=dohello.h dohello.cpp #编译libhello.a需要的源文件。注意将库名称中的'.'号改成'_'号。 
#----------------结束------------------------------------------

6 .5 运行automake

以上几个Makefile.am都书写完毕后,运行automake --add-missing。

6 .6 运行./configure和make

上面步骤完成后,先后运行./configure和make完成编译。如果编译成功,运行make dist可以将所有文件打包成hello-1.0.tar.gz。

7  例解 autoconf 和 automake 生成 Makefile 文件

本文介绍了在 linux 系统中,通过 Gnu autoconf 和 automake 生成 Makefile 的方法。主要探讨了生成 Makefile 的来龙去脉及其机理,接着详细介绍了配置 Configure.in 的方法及其规则。

7 .1 引子

无论是在Linux还是在Unix环境中,make都是一个非常重要的编译命令。不管是自己进行项目开发还是安装应用软件,我们都经常要用到make或 make install。利用make工具,我们可以将大型的开发项目分解成为多个更易于管理的模块,对于一个包括几百个源文件的应用程序,使用make和 makefile工具就可以轻而易举的理顺各个源文件之间纷繁复杂的相互关系。

但是如果通过查阅make的帮助文档来手工编写Makefile,对任何程序员都是一场挑战。幸而有GNU 提供的Autoconf及Automake这两套工具使得编写makefile不再是一个难题。

本文将介绍如何利用 GNU Autoconf 及 Automake 这两套工具来协助我们自动产生 Makefile文件,并且让开发出来的软件可以像大多数源码包那样,只需"./configure", "make","make install" 就可以把程序安装到系统中。

7 .2 模拟需求

假设源文件按如下目录存放,如图1所示,运用autoconf和automake生成makefile文件。

图 1文件目录结构

7 .3 工具简介

所必须的软件:autoconf/automake/m4/perl/libtool(其中libtool非必须)。

autoconf是一个用于生成可以自动地配置软件源码包,用以适应多种UNIX类系统的shell脚本工具,其中autoconf需要用到 m4,便于生成脚本。automake是一个从Makefile.am文件自动生成Makefile.in的工具。为了生成Makefile.in,automake还需用到perl,由于automake创建的发布完全遵循GNU标准,所以在创建中不需要perl。libtool是一款方便生成各种程序库的工具。

目前automake支持三种目录层次:flat、shallow和deep。

1) flat指的是所有文件都位于同一个目录中。

就是所有源文件、头文件以及其他库文件都位于当前目录中,且没有子目录。Termutils就是这一类。

2) shallow指的是主要的源代码都储存在顶层目录,其他各个部分则储存在子目录中。

就是主要源文件在当前目录中,而其它一些实现各部分功能的源文件位于各自不同的目录。automake本身就是这一类。

3) deep指的是所有源代码都被储存在子目录中;顶层目录主要包含配置信息。

就是所有源文件及自己写的头文件位于当前目录的一个子目录中,而当前目录里没有任何源文件。 GNU cpio和GNU tar就是这一类。

flat类型是最简单的,deep类型是最复杂的。不难看出,我们的模拟需求正是基于第三类deep型,也就是说我们要做挑战性的事情:)。注:我们的测试程序是基于多线程的简单程序。

7 .4 生成 Makefile 的来龙去脉

首先进入 project 目录,在该目录下运行一系列命令,创建和修改几个文件,就可以生成符合该平台的Makefile文件,操作过程如下:

1) 运行autoscan命令

2) 将configure.scan 文件重命名为configure.in,并修改configure.in文件

3) 在project目录下新建Makefile.am文件,并在core和shell目录下也新建makefile.am文件

4) 在project目录下新建NEWS、 README、 ChangeLog 、AUTHORS文件

5) 将/usr/share/automake-1.X/目录下的depcomp和complie文件拷贝到本目录下

6) 运行aclocal命令

7) 运行autoconf命令

8) 运行automake -a命令

9) 运行./confiugre脚本

可以通过图2看出产生Makefile的流程,如图所示:

7 .5 Configure.in的八股文

当我们利用autoscan工具生成confiugre.scan文件时,我们需要将confiugre.scan重命名为confiugre.in文件。confiugre.in调用一系列autoconf宏来测试程序需要的或用到的特性是否存在,以及这些特性的功能。

下面我们就来目睹一下confiugre.scan的庐山真面目:

# Process this file with autoconf to produce a configure script.

AC_PREREQ(2.59)

AC_INIT(FULL-PACKAGE-NAME, VERSION, BUG-REPORT-ADDRESS)

AC_CONFIG_SRCDIR([config.h.in])

AC_CONFIG_HEADER([config.h])

# Checks for programs.

AC_PROG_CC

# Checks for libraries.

# FIXME: Replace `main' with a function in `-lpthread':

AC_CHECK_LIB([pthread], [main])

# Checks for header files.

# Checks for typedefs, structures, and compiler characteristics.

# Checks for library functions.

AC_OUTPUT

每个configure.scan文件都是以AC_INIT开头,以AC_OUTPUT结束。我们不难从文件中看出confiugre.in文件的一般布局:

C_INIT

 测试程序

 测试函数库

 测试头文件

 测试类型定义

 测试结构

 测试编译器特性

 测试库函数

 测试系统调用

AC_OUTPUT

上面的调用次序只是建议性质的,但我们还是强烈建议不要随意改变对宏调用的次序。

现在就开始修改该文件:

$mv configure.scan configure.in

$vim configure.in

修改后的结果如下:

#                                -*- Autoconf -*-

# Process this file with autoconf to produce a configure script.

AC_PREREQ(2.59)

AC_INIT(test, 1.0, normalnotebook@126.com)

AC_CONFIG_SRCDIR([src/ModuleA/apple/core/test.c])

AM_CONFIG_HEADER(config.h)

AM_INIT_AUTOMAKE(test,1.0)

# Checks for programs.

AC_PROG_CC

# Checks for libraries.

# FIXME: Replace `main' with a function in `-lpthread':

AC_CHECK_LIB([pthread], [pthread_rwlock_init])

AC_PROG_RANLIB

# Checks for header files.

# Checks for typedefs, structures, and compiler characteristics.

# Checks for library functions.

AC_OUTPUT([Makefile

src/lib/Makefile

src/ModuleA/apple/core/Makefile

src/ModuleA/apple/shell/Makefile

])

其中要将AC_CONFIG_HEADER([config.h])修改为:AM_CONFIG_HEADER(config.h), 并加入AM_INIT_AUTOMAKE(test,1.0)。由于我们的测试程序是基于多线程的程序,所以要加入AC_PROG_RANLIB,不然运行automake命令时会出错。在AC_OUTPUT输入要创建的Makefile文件名。

由于我们在程序中使用了读写锁,所以需要对库文件进行检查,即AC_CHECK_LIB([pthread], [main]),该宏的含义如下:


其中,LIBS是link的一个选项,详细请参看后续的Makefile文件。由于我们在程序中使用了读写锁,所以我们测试pthread库中是否存在pthread_rwlock_init函数。

由于我们是基于deep类型来创建makefile文件,所以我们需要在四处创建Makefile文件。即:project目录下,lib目录下,core和shell目录下。 

Autoconf提供了很多内置宏来做相关的检测,限于篇幅关系,我们在这里对其他宏不做详细的解释,具体请参看参考文献1和参考文献2,也可参看autoconf信息页。

7 .6 实战Makefile.am

Makefile.am是一种比Makefile更高层次的规则。只需指定要生成什么目标,它由什么源文件生成,要安装到什么目录等构成。

表一列出了可执行文件、静态库、头文件和数据文件,四种书写Makefile.am文件个一般格式。

表 1Makefile.am一般格式


对于可执行文件和静态库类型,如果只想编译,不想安装到系统中,可以用noinst_PROGRAMS代替bin_PROGRAMS,noinst_LIBRARIES代替lib_LIBRARIES。

Makefile.am还提供了一些全局变量供所有的目标体使用:

表 2 Makefile.am中可用的全局变量
 

在Makefile.am中尽量使用相对路径,系统预定义了两个基本路径:

表 3Makefile.am中可用的路径变量
 

在上文中我们提到过安装路径,automake设置了默认的安装路径:

1) 标准安装路径

默认安装路径为:$(prefix) = /usr/local,可以通过./configure --prefix=<new_path>的方法来覆盖。

其它的预定义目录还包括:bindir = $(prefix)/bin, libdir = $(prefix)/lib, datadir = $(prefix)/share, sysconfdir = $(prefix)/etc等等。

2) 定义一个新的安装路径

比如test, 可定义testdir = $(prefix)/test, 然后test_DATA =test1 test2,则test1,test2会作为数据文件安装到$(prefix)/ /test目录下。

我们首先需要在工程顶层目录下(即project/)创建一个Makefile.am来指明包含的子目录:

我们首先需要在工程顶层目录下(即project/)创建一个Makefile.am来指明包含的子目录:

SUBDIRS=src/lib src/ModuleA/apple/shell src/ModuleA/apple/core 

CURRENTPATH=$(shell /bin/pwd)

INCLUDES=-I$(CURRENTPATH)/src/include -I$(CURRENTPATH)/src/ModuleA/apple/include 

export INCLUDES

由于每个源文件都会用到相同的头文件,所以我们在最顶层的Makefile.am中包含了编译源文件时所用到的头文件,并导出,见蓝色部分代码。

我们将lib目录下的swap.c文件编译成libswap.a文件,被apple/shell/apple.c文件调用,那么lib目录下的Makefile.am如下所示:

noinst_LIBRARIES=libswap.a

libswap_a_SOURCES=swap.c

INCLUDES=-I$(top_srcdir)/src/includ

细心的读者可能就会问:怎么表1中给出的是bin_LIBRARIES,而这里是noinst_LIBRARIES?这是因为如果只想编译,而不想安装到系统中,就用noinst_LIBRARIES代替bin_LIBRARIES,对于可执行文件就用noinst_PROGRAMS代替bin_PROGRAMS。对于安装的情况,库将会安装到$(prefix)/lib目录下,可执行文件将会安装到${prefix}/bin。如果想安装该库,则Makefile.am示例如下:

bin_LIBRARIES=libswap.a

libswap_a_SOURCES=swap.c

INCLUDES=-I$(top_srcdir)/src/include

swapincludedir=$(includedir)/swap

swapinclude_HEADERS=$(top_srcdir)/src/include/swap.h

最后两行的意思是将swap.h安装到${prefix}/include /swap目录下。

接下来,对于可执行文件类型的情况,我们将讨论如何写Makefile.am?对于编译apple/core目录下的文件,我们写成的Makefile.am如下所示:

noinst_PROGRAMS=test

test_SOURCES=test.c 

test_LDADD=$(top_srcdir)/src/ModuleA/apple/shell/apple.o $(top_srcdir)/src/lib/libswap.a 

test_LDFLAGS=-D_GNU_SOURCE

DEFS+=-D_GNU_SOURCE

#LIBS=-lpthread

由于我们的test.c文件在链接时,需要apple.o和libswap.a文件,所以我们需要在test_LDADD中包含这两个文件。对于Linux下的信号量/读写锁文件进行编译,需要在编译选项中指明-D_GNU_SOURCE。所以在test_LDFLAGS中指明。而test_LDFLAGS只是链接时的选项,编译时同样需要指明该选项,所以需要DEFS来指明编译选项,由于DEFS已经有初始值,所以这里用+=的形式指明。从这里可以看出,Makefile.am中的语法与Makefile的语法一致,也可以采用条件表达式。如果你的程序还包含其他的库,除了用AC_CHECK_LIB宏来指明外,还可以用LIBS来指明。

如果你只想编译某一个文件,那么Makefile.am如何写呢?这个文件也很简单,写法跟可执行文件的差不多,如下例所示:

noinst_PROGRAMS=apple

apple_SOURCES=apple.c

DEFS+=-D_GNU_SOURCE

我们这里只是欺骗automake,假装要生成apple文件,让它为我们生成依赖关系和执行命令。所以当你运行完automake命令后,然后修改apple/shell/下的Makefile.in文件,直接将LINK语句删除,这里只是欺骗automake,假装要生成apple文件,让它为我们生成依赖关系和执行命令。所以当你运行完automake命令后,然后修改apple/shell/下的Makefile.in文件,直接将LINK语句删除,即:

…….

clean-noinstPROGRAMS:

-test -z "$(noinst_PROGRAMS)" || rm -f $(noinst_PROGRAMS)

apple$(EXEEXT): $(apple_OBJECTS) $(apple_DEPENDENCIES) 

@rm -f apple$(EXEEXT)

#$(LINK) $(apple_LDFLAGS) $(apple_OBJECTS) $(apple_LDADD) $(LIBS)

…….

通过上述处理,就可以达到我们的目的。从图1中不难看出为什么要修改Makefile.in的原因,而不是修改其他的文件。

下载

名字

大小

下载方法

project.rar

HTTP

参考资料 

Kurt Wall,张辉译 《GNU/Linux编程指南》 清华大学出版社 

Robert Mecklenburg,《GNU Make项目管理(第三版)》 东南大学出版社 2006 

http://www.cngnu.org/technology/index.html 

8  Makefile工具的使用

Make程序最初设计是为了维护C程序文件防止不必要的重新编译。在使用命令行编译器的时候,修改了一个工程中的头文件,如何确保包含这个头文件的所有文件都得到编译?现在10机的版本生成是使用批处理程序,编译那些文件依赖于程序的维护者,在模块之间相互引用头文件的情况下,要将所有需要重新编译的文件找出来是一件痛苦的事情;在找到这些文件之后,修改批处理进行编译。实际上这些工作可以让make程序来自动完成,make工具对于维护一些具有相互依赖关系的文件特别有用,它对文件和命令的联系(在文件改变时调用来更新其它文件的程序)提供一套编码方法。Make工具的基本概念类似于Proglog语言,你告诉make需要做什么,提供一些规则,make来完成剩下的工作。 

    1简介 

    make工作自动确定工程的哪部分需要重新编译,执行命令去编译它们。虽然make多用于C程序,然而只要提供命令行的编译器,你可以将其用于任何语言。实际上,make工具的应用范围不仅于编程,你可以描述任和一些文件改变需要自动更新另一些文件的任务来使用它。 

    1.1准备工作 

    如果要使用make,你必须写一个叫做“makefile”的文件,这个文件描述工程中文件之间的关系,提供更新每个文件的命令。典型的工程是这样的:可执行文件靠目标文件来更新,目标文件靠编译源文件来更新。 

    Makefile写好之后,每次更改了源文件后,只要执行make就足够了,所有必要的重新编译将执行。Make程序利用makefile中的数据库和文件的最后修改时间来确定那个文件需要更新;对于需要更新的文件,make执行数据库中记录的命令。 

    可以提供命令行参数给make来控制那个文件需要重新编译。 

    1.2Makefile介绍 

    Makefile文件告诉make做什么,多数情况是怎样编译和链接一个程序。 

    这里有一个简单的makefile,描述如何编译链接由8C文件和3个头文件组成的一个编辑器: 

    edit:main.okbd.ocommand.odisplay.o/ 

    insert.oserach.ofiles.outils.o 

    cc–oeditmain.okbd.ocommand.odisplay.o/ 

    insert.osearch.ofiles.outils.o 

    main.o:main.cdefs.h 

    cc–cmain.c 

    kdb.o:kbd.cdefs.hcommand.h 

    cc–ckbd.c 

    command.o:command.cdefs.hcommand.h 

    cc-ccommand.c 

    display.o:display.cdefs.hbuffer.h 

    cc-cdisplay.c 

    insert.o:insert.cdefs.hbuffer.h 

    cc-cinsert.c 

    search.o:search.cdefs.hbuffer.h 

    cc-csearch.c 

    files.o:files.cdefs.hbuffer.hcommand.h 

    cc-cfiles.c 

    utils.o:utils.cdefs.h 

    cc-cutils.c 

    clean: 

    rmeditmain.okbd.ocommand.odisplay.o/ 

    insert.osearch.ofiles.outils.o 

    将长行用/分开便于阅读,这和使用一个长行的作用是一样的。使用这个makefile创建可执行文件“edit”时运行make就可以了;如果要将可执行文件和目标文件删除,执行makeclean 

    make重新编译这个编辑器时,每个更改的C文件必须重新编译;如果头文件更改了,每个包含头文件的C文件必须重新编译;每次编译产生一个对应于原文件的目标文件。最终,目标文件链接在一起产生新的可执行文件。 

    1.3规则简介 

    makefile中的规则是这样的: 

    TARGET…:DEPENDENCIES… 

    COMMAND 

    … 

    目标(TARGET)程序产生的文件,如可执行文件和目标文件;目标也可以是要执行的动作,如“clean”。 

    依赖(DEPENDENCIES)是用来产生目标的输入文件,一个目标通常依赖于多个文件。 

    命令(COMMAND)是make执行的动作,一个可以有多个命令,每个占一行。注意:每个命令行的起始字符必须为TAB字符! 

    有依赖关系规则中的命令通常在依赖文件变化时负责产生target文件,make执行这些命令更新或产生target。规则可以没有依赖关系,如包含target“clean”的规则。 

    规则解释如何和何时重做该规则中的文件,make根据依赖关系执行产生或更新目标;规则也说明如何和何时执行动作。有的规则看起来很复杂,但都符合上述模式。 

    1.4make工作原理 

    缺省make从第一个target开始(第一个非'.'开始的target),这称作缺省目标。在上述的makefile中,缺省目标是更新执行程序'edit',将这个目标置于最前面。当执行make的时候,make程序从当前目录读入makefile开始处理第一个规则;在例子中,这个规则是重新链接'edit';在make处理这个规则之前,必须处理'edit'所依赖的那些文件的规则,例子中是目标文件。这些文件按照他们自己的规则处理:通过编译源文件来更新每个'.o'文件;当依赖关系中的源文件或头文件比目标文件新,或目标文件不存在时,必须重新编译。 

    其它的规则被处理是因为他们的target是目标的依赖,和目标没有依赖关系的规则不会被处理,除非指定make处理(如makeclean)。 

    在重新编译目标文件之前,make会试图更新它的依赖:源文件和头文件。例子中的makefile对源文件和头文件未指定任何操作:'.c''.h'文件不是任何规则的目标。确认所有的目标文件都是最新的之后,make决定是否重新链接'edit':如果'edit'不存在,或者任何一个目标文件都比它新,则链接工作将进行。 

    这样,如果我们改变insert.c运行makemake会编译这个文件来更新'insert.o',然后链接'edit';如果修改了'command.h'运行make'kbd.o''command.o''files.o'会重新生成,链接'edit'。 

    1.5使用变量 

    在例子中,在规则'edit'中,目标文件被列出来两次: 

    edit:main.okbd.ocommand.odisplay.o/ 

    insert.osearch.ofiles.outils.o 

    cc-oeditmain.okbd.ocommand.odisplay.o/ 

    insert.osearch.ofiles.outils.o 

    这样的重复容易出错:假设工程中加入了一个新的目标文件,可能只将其加入了一个列表中;通过使用变量可以消除这种风险:变量允许一个预定义的字符串在多个地方被替换。 

    makefile中,可以写这样一行来定义'object'变量: 

    objects=main.okbd.ocommand.odisplay.o/ 

    insert.osearch.ofiles.outils.o 

    于是在需要目标文件名列表的地方,使用$(object)来代替变量的值。以下是使用了变量以后的makefile: 

    objects=main.okbd.ocommand.odisplay.o/ 

    insert.osearch.ofiles.outils.o 

    edit:$(objects) 

    cc-oedit$(objects) 

    main.o:main.cdefs.h 

    cc-cmain.c 

    kbd.o:kbd.cdefs.hcommand.h 

    cc-ckbd.c 

    command.o:command.cdefs.hcommand.h 

    cc-ccommand.c 

    display.o:display.cdefs.hbuffer.h 

    cc-cdisplay.c 

    insert.o:insert.cdefs.hbuffer.h 

    cc-cinsert.c 

    search.o:search.cdefs.hbuffer.h 

    cc-csearch.c 

    files.o:files.cdefs.hbuffer.hcommand.h 

    cc-cfiles.c 

    utils.o:utils.cdefs.h 

    cc-cutils.c 

    clean: 

    rmedit$(objects) 

    1.6简化命令 

    为每个文件写出编译命令不是必要的,因为make可以自己来做;以'.c'文件更新'.o'文件有一个隐含的规则,使用'cc-c'命令。Make将利用'cc–cmain.c–omain.o'来将main.c编译为main.o,因此在生成目标文件的规则中,可以省略命令。 

    '.c'文件以这样的方式使用时,将自动加入到依赖关系中;由是在省略命令的前提下,可以将'.c'文件从依赖关系中省略。以下是简化过的makefile: 

    objects=main.okbd.ocommand.odisplay.o/ 

    insert.osearch.ofiles.outils.o 

    edit:$(objects) 

    cc-oedit$(objects)

    main.o:defs.h 

    kbd.o:defs.hcommand.h 

    command.o:defs.hcommand.h 

    display.o:defs.hbuffer.h 

    insert.o:defs.hbuffer.h 

    search.o:defs.hbuffer.h 

    files.o:defs.hbuffer.hcommand.h 

    utils.o:defs.h 

    .PHONY:clean 

    clean: 

    -rmedit$(objects) 

    1.7另一种风格 

    如果makefile中的目标都是以隐含规则生成,可以将规则按照依赖关系分组: 

    objects=main.okbd.ocommand.odisplay.o/ 

    insert.osearch.ofiles.outils.o 

    edit:$(objects) 

    cc-oedit$(objects) 

    $(objects):defs.h 

    kbd.ocommand.ofiles.o:command.h 

    display.oinsert.osearch.ofiles.o:buffer.h 

    这里'defs.h'作为所有目标文件的依赖。这种风格是好是坏取决于个人喜好,它非常紧凑,但是将每个目标的依赖信息放在一起看起来更清楚一些。 

    1.8清理 

    编写规则不至于编译程序。Makefile通常描述如何做其它事情:比如删除目录中的目标文件和可执行文件来清理目录。例子中是这样写的: 

    clean: 

    rmedit$(objects) 

    实际情况是,我们需要处理一些意外事件:存在一个叫做'clean'的文件;如果rm出错,并不希望make过程停止下来,修改过的版本如下: 

    .PHONY:clean 

    clean: 

    -rmedit$(objects) 

    这样的规则当然不能放在makefile的开始,因为这并不是我们缺省要做的工作。由于'clean'并不是'edit'的依赖,在运行make时没有参数时,这条规则不会执行;要执行这个规则,必须运行'makeclean'。 

    2Makefile 

    Makefile中包含五种内容:显式规则,隐式规则,变量定义,指令(directive)和注释。 

    ?;显式规则描述如何生成规则的目标,它列出了目标依赖的文件,指定了产生或更新目标的命令 

    ?;隐式规则描述如何生成基于文件名的一类文件,说明目标可能依赖于和其文件名类似的文件,指定了相应的命令。 

    ?;指令类似与编译器的伪指令,包含: 

    ?;指示make读入另一个makefile 

    ?;决定是否忽略makefile中的一部分 

    ?;定义一个变量 

    ?;一行中‘#'开始是注释,直到行末,除非遇到续行符号。在'define'和命令中不能有注释,其它情况下注释可出现在任何地方。 

    2.1makefile名字 

    缺省情况下,make以下列名字查找makefile'GNUmakefile''makefile''Makefile'(注意大小写)。通常你的makefile应叫做'makefile''Makefile''GNUmakefile'不推荐,除非你的makefile是为GNUmake定制的,其它的make不认为该名字是一个makefile的名字。 

    如果你使用非标准命名的makefile,必须用命令开关'-f''—file'。参数'–fNAME''—fileNAME'告诉make读入NAME作为makefile。如果使用多个该开关,所有的文件将按顺序连接起来。如果使用该选项,标准的makefile名字不会自动检测。 

    2.2包含 

    ‘include'指令告诉make暂停处理余下的内容,读入其它makefile。语法如下: 

    includeFILENAMES… 

    这一行起始可以有空格,但TAB字符不允许。如果文件名包含变量或函数,这些将被扩展。 

    2.3‘MAKEFILE'变量 

    如果环境变量'MAKEFILE'已定义,make认为它的值是一系列空格隔开的文件名,这些文件在处理其它makefile前被make程序读入。这类似于include指令;这些文件中的目标不会影响缺省目标,而且如果文件未找到的话,make并不认为是错误。 

    这个变量的主要用途是递归引用make程序时通讯 

    2.4如何重新生成makefile 

    有时候makefile是从其它文件生成的,比如RCSSCCS文件。如果makefile是由其它文件生成的,需要make读入最新版本的makefile。 

    在读入所有makefile之后,make认为每个makefile是一个目标,试图去更新它;如果makefile中有一条如何更新它的规则,或者有适用的隐式规则,需要的更新会进行。所有的makefile检查完之后,如果有的改变了,make重新开始再读入(make会试图再做更新,但通常不会再改变了,因为已经是最新的了)。 

    如果一个文件使用双冒号规则,提供了命令但没有依赖关系,文件始终会被更新。在makefile的情况下,如果makefile双冒号规则,提供了命令但没有依赖关系,这样makefile始终会重新生成,这会导致循环:make只是在不断更新makefile,却不干活。为避免这种情况,make不会重新生成那些只有命令没有依赖关系的双冒号规则的makefile。 

    如果没有使用'-f''--file'选项,make会尝试缺省的makefile文件名。和指明'-f''--file'选项不同,make不能确定这些文件是否应当存在。然而,如果缺省makefile不存在但可以通过运行make规则生成,你可能希望这些规则被运行使得makefile可以使用。 

    因此,如果没有缺省makefilemake试图按照makefile名查找的顺序生成它,直到成功或名字用完。注意如果make不能找到或生成makefile,这并不是错误;makefile不总是必需的。 

    当使用'-t''--touch'选项时,不希望使用过时的makefile来决定那个目标来touch。所以'-t'选项对makefile更新不起作用;类似'-q'or‘—question')和'-n'or'—just-print')不阻止makefile的更新,因为过时的makefile会产生错误的输出。这样'make–fmfile–nfoo'会更新'mfile',读入它,打印出更新'foo'需要执行的命令但不运行这些命令。与'foo'有关的命令是更新过的'mfile'中的内容。 

    但是有时不希望更新makefile,可以将makefile作为命令行的目标,当makefile被显式指定为目标时,'-t'选项也适用于它们。 

    这样'make–fmfile–nmfilefoo'会读入'mfile',打印出更新执行的命令,'foo'的命令是当前的'mfile'中的内容。 

    2.5重载makefile 

    可以使用'include'指令来包含其它makefile,增加目标的变量定义。然而,make不允许同一个目标有不同的命令,有其它的途径可以达到目的。 

    假设有'makefile''mfile''makfile'要包含'mfile',但都有对于目标'foo'的规则。这是可以在'makefile'中写一条匹配任意模式的规则,指明当make'makefile'中未找到目标时,搜索'mfile': 

    foo: 

    frobnicate>foo 

    %:force 

    @$(MAKE)-fmfile$@ 

    force:; 

    当执行'makefoo'时,make找到'makefile',执行命令'frobnicate>foo';执行'makebar'时,在'makefile'中未找到相应的规则,这时模式规则适用,执行命令'make–fmfilebar''makefile'中未提及的其它目标也是类似的。 

    这种方法之所有工作是因为模式规则的模式是'%',可以匹配任何的目标;这条规则的依赖是'force',保证即使目标存在命令也会执行;'force'规则的命令为空防止'make'为其搜索隐式规则-这样会导致依赖循环。 

    3规则 

    makefile中的规则描述如何生成特定的文件,即规则的目标。规则列出了目标的依赖文件,指定生成或更新目标的命令。 

    规则的次序是不重要的,除非是确定缺省目标:缺省目标是第一个makefile中的第一个规则;如果第一个规则有多个目标,第一个目标是缺省的。有两个例外:以'.'开头的目标不是缺省目标;模式规则对缺省目标没有影响。 

    通常我们所写的地一个规则是编译整个或makefile中指定的所有程序。 

    3.1例子 

    foo.o:foo.cdefs.h#modulefortwiddlingthefrobs 

    cc-c-gfoo.c 

    它的目标是'foo.o',依赖于'foo.c''defs.h',有一个命令'cc–c–gfoo.c'。命令行以TAB字符开始标识它是一个命令。 

    这条规则说明两件事: 

    ?;如何决定'foo.o'是旧的:如果它不存在,或者'foo.c'或者'defs.h'比它新。 

    ?;如何更新'foo.o'文件:通过运行'cc'程序。命令未提及'defs.h',担可以猜想'foo.c'包含了它,这是'defs.h'被置于依赖关系中的理由。 

    3.2规则的语法 

    语法如下: 

    TARGETS:DEPENDENCIES 

    COMMAND 

    ... 

    或者 

    TARGETS:DEPENDENCIES;COMMAND 

    COMMAND 

    ... 

    TARGETS是以空格隔开的文件名,统配符可以使用。通常一个规则只有一个目标,偶尔也有多个。 

    命令行以TAB键开始。第一条命令可在依赖关系的下一行;或者在同一行,在分号后面;两种方式效果相同。 

    因为'$'符号被用做变量引用,如果要在规则中使用'$'符号,必须写两个:'$$'。可以用'/'符号来分割一个长行,这不是必须的,因为make对行的长度没有限制。 

    3.3通配符 

    规则中的文件名可以包含统配符,如'*''?'。 

    文件名前的字符'~'有特殊的含义。单独使用,或跟随一个'/',代表用户的home目录,比如'~/bin'扩展为/home/you/bin';如果'~'跟随一个单词,表示单词指示的那个用户的home目录,如'~john/bin'扩展为'/home/john/bin'。 

    通配符在目标,依赖关系,命令中自动扩展,其它情况下,统配符的扩展除非显式使用'wildcard'函数。通配符的特殊意义可以使用'/'符号关闭。 

    例子: 

    clean: 

    rm-f*.o 

    和 

    print:*.c 

    lpr-p$? 

    touchprint 

    通配符在定义变量时并不扩展,例如: 

    objects=*.o 

    objects的值是字符串'*.o';但是如果你将objects用于目标,依赖或命令中,扩展会进行。要将objects设置成扩展过的内容,使用: 

    objects:=$(wildcard*.o) 

    3.3.1通配符的缺陷 

    这是一个使用通配符的例子,但结果不是你所期望的。假设可执行文件'foo'是从当前目录中的所有'.o'文件生成的: 

    objects=*.o 

    foo:$(objects) 

    cc-ofoo$(CFLAGS)$(objects) 

    objects变量的值是字符串'*.o'。通配符扩展在规则'foo'中进行,于是所有存在的'.o'文件成为'foo'的依赖而且在需要时重新编译。 

    但如果删除了所有的'.o'文件呢?当通配符不匹配任何文件时,一切都保持原样:则'foo'依赖于一个叫做'*.o'的文件;由于这个文件不大可能存在,'make'程序会报告一个无法生成'*.o'文件的错误,这不是期待的结果。 

    实际上可以用通配符获得期望结果,但是需要复杂的技术,包括'wildcard'函数和字符串替换函数。 

    3.3.2wildcard函数 

    通配符自动在规则中进行。但是在变量赋值的和函数的参数中通配符不会扩展,如果在这些情况下需要通配符扩展,必须使用'wildcard'函数。语法如下: 

    $(wildcardPATTERN...) 

    这个在makefile任何地方出现的字符串,会被匹配任何一个文件名格式的以空格隔开的现有文件列表替换。如果没有任何文件匹配一个模式,这个模式从'wildcard'的输出中忽略,注意,这和上述的通配符的处理是不一样的。 

    ‘wildcard'函数的一个功能是找出目录中所有的'.c'文件: 

    $(wildcard*.c) 

    可以通过替换后缀'.c''.o'C文件列表得到目标文件的列表: 

    $(patsubst%.c,%.o,$(wildcard*.c)) 

    这样,上节中的makefile改写为: 

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

    foo:$(objects) 

    cc-ofoo$(objects) 

    这个makefile利用了编译C程序的隐含规则,所以不需要对编译写出显式的规则。(':=''='的一个变体) 

    注意:'PATTERN'是大小写敏感的。 

    3.4目录搜索 

    对于大的系统,通常将源文件和目标文件放在不同的目录中。目录搜索功能可以让make自动在多个目录中搜寻依赖文件,当你将文件重新分布是,不需要改变规则,更改搜索路径即可。 

    3.4.1‘VPATH' 

    make变量'VPATH'列出make应当搜索的目录列表。很多情况下,当前目录不包含依赖文件,'VPATH'描述一个对所有文件的搜索列表,包含那些是规则的目标的文件。 

    如果一个目标或者依赖文件在当前目录没找到的话,'make''VPATH'中列出的目录中查找同名的文件。如果找到的话,那个文件成为依赖文件;规则可以象这些文件在当前目录中一样来使用他们。 

    'VPATH'变量中,目录名以冒号或空格隔开;目录列出的顺序决定make查找的顺序。(注:在pSOSystem2.5移植到Win32GNUmake目录名必须使用分号隔开,以下均简称Win32GNUmake)。举例说明: 

    VPATH=src:../headers则规则 

    foo.o:foo.c 

    被解释为 

    foo.o:src/foo.c 

    假设'foo.c'在当前目录不存在,在'src'目录中可以找到。 

    3.4.2选择性搜索 

    'VPATH'变量相似但更具选择性的是'vpath'指令(注意是小写),可以指定对于符合特定模式文件的查找路径。这样可以为不同类型的文件指定不同的搜索路径。 

    ‘vpath'指令共有三中形式: 

    ?;‘vpathPATTERNDIRECTORIES' 

    为匹配PATTERN的文件名指定搜索路径DIRECTORIES,目录的分隔和'VPATH'的相同 

    ?;‘vpathPATTERN' 

    清除为匹配PATTERN的文件名指定的搜索路径 

    ?;‘vpath' 

    清除所有以前用'vpath'指定的搜索路径 

    ‘vpath'的模式是包含'%'的字符串:这个字符串必须匹配需要搜索的依赖文件名,'%'字符匹配0个或多个任意字符。例如:'%.h'匹配任何以'.h'结尾的文件(如果没有%,则PATTERN必须和依赖文件完全一致,这种用法不太多)。 

    当当前目录中不存在依赖文件时,如果'vpath'中的PATTERN匹配依赖文件名,则指令中DIRECTORIES列出的目录和'VPATH'中同样处理。举例: 

    vpath%.h../headers 

    告诉make在当前目录中未找到的'.h'文件在../headers目录中查找。 

    如果多个'vapth'的模式匹配依赖文件名,make将逐一处理,在所有指定的目录中搜索。Make按照'vapth'makefile中的次序;来处理它们,多个相同模式的'vapth'是相互独立的。 

    vpath%.cfoo 

    vpath%blish 

    vpath%.cbar 

    将按照'foo'‘blish''bar'的次序查找'.c'文件。而 

    vpath%.cfoo:bar 

    vpath%blish 

    按照'foo''bar''blish'的顺序搜索。 

    3.4.3使用自动变量 

    目录搜索的结果并不改变规则中的命令:命令按原样被执行。因此,必须写出与目录搜索功相适应的命令。这可以通过使用'$^'这样的自动变量来完成。'$^'表示规则中的所有依赖文件,包含它们所在的目录名(参见目录搜索);'$@'表示目标。例如: 

    foo.o:foo.c 

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

    通常情况下,依赖文件也包含头文件,但命令中并不提及这些文件:变量'$<'表示第一个依赖文件: 

    VPATH=src:../headers 

    foo.o:foo.cdefs.hhack.h 

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

    3.4.4目录搜索和隐含规则 

    使用'VPATH''vpath'指定目录搜索也会影响隐含规则。例如:文件'foo.o'没有显式规则,make会考虑隐式规则:如果'foo.c'存在则编译它;如果这个文件不存在,则在相应的目录中查找;如果'foo.c'在任一的目录中存在,则C编译的隐式规则被应用。 

    隐式规则的命令使用自动变量通常是必要的,这样无需其它努力即可以使用目录搜索得到的文件名。 

    3.5PHONY目标 

    Phony目标并非实际的文件名:只是在显式请求时执行命令的名字。有两种理由需要使用phony目标:避免和同名文件冲突,改善性能。 

    如果编写一个规则,并不产生目标文件,则其命令在每次make该目标时都执行。例如: 

    clean: 

    rm*.otemp 

    因为'rm'命令并不产生'clean'文件,则每次执行'makeclean'的时候,该命令都会执行。如果目录中出现了'clean'文件,则规则失效了:没有依赖文件,文件'clean'始终是最新的,命令永远不会执行;为避免这个问题,可使用'.PHONY'指明该目标。如: 

    .PHONY:clean 

    这样执行'makeclean'会无视'clean'文件存在与否。 

    已知phony目标并非是由其它文件生成的实际文件,make会跳过隐含规则搜索。这就是声明phony目标会改善性能的原因,即使你并不担心实际文件存在与否。完整的例子如下: 

    .PHONY:clean 

    clean: 

    rm*.otemp 

    phony目标不应是真正目标文件的依赖。如果这样,每次make在更新此文件时,命令都会执行。只要phony目标不是真正目标的依赖,规则的命令只有在指定此目标时才执行。 

    Phony目标可以有依赖关系。当一个目录中有多个程序是,将其放在一个makefile中会更方便。因为缺省目标是makefile中的第一个目标,通常将这个phony目标叫做'all',其依赖文件为各个程序: 

    all:prog1prog2prog3 

    .PHONY:all 

    prog1:prog1.outils.o 

    cc-oprog1prog1.outils.o 

    prog2:prog2.o 

    cc-oprog2prog2.o 

    prog3:prog3.osort.outils.o 

    cc-oprog3prog3.osort.outils.o 

    这样,使用'make'将可以将三个程序都生成了。 

    当一个phony目标是另一个的依赖,其作用相当于子程序,例如: 

    .PHONY:cleanallcleanobjcleandiff 

    cleanall:cleanobjcleandiff 

    rmprogram 

    cleanobj: 

    rm*.o 

    cleandiff: 

    rm*.diff 

    3.6FORCE目标 

    当规则没有依赖关系也没有命令,而且其目标不是存在的文件名,make认为此规则运行时这个目标总是被更新。这意味着如果规则依赖于此目标,其命令总是被执行。 

    clean:FORCE 

    rm$(objects) 

    FORCE: 

    例中目标'FORCE'满足这种特殊条件,这样依赖于它的目标'clean'被强制执行其命令。名字'FORCE'没有特殊含义,只不过通常这样用而已。这种方式使用'FORCE''.PHONY:clean'效果相同。使用'.PHONY'更加明确高效,担不是所有的'make'都支持;这样许多makefile中使用了'FORCE'。 

    3.7空目标 

    空目标(emptytarget)是phony目标的变种:用来执行显式请求的一个动作。和phony目标不同的是:这个目标文件可以真实存在,担文件的内容无关紧要,通常是空的。空目标文件的目的是利用其最后修改时间来记录命令最近一次执行的时间,这是通过使用'touch'命令更新目标文件来达到的。 

    print:foo.cbar.c 

    lpr-p$? 

    touchprint 

    利用这条规则,执行'makeprint'时如果自上次'makeprint'之后任一文件改变了,'lpr'命令会执行。自动变量'$?'是为了只打印出那些变化了的文件。 

    3.8内建的特殊目标 

    某些名字作为目标存在时有特殊含义。 

    .PHONY该目标的依赖被认为是phony目标,处理这些目标时,命令无条件被执行,不管文件名是否存在及其最后修改时间 

    .SUFFIXES该目标的依赖被认为是一个后缀列表,在检查后缀规则时使用 

    .DEFAULT该目标的规则被使用在没有规则(显式的或隐含的)的目标上。如果'DEFAULT'命令定义了,则对所有不是规则目标的依赖文件都会执行该组命令 

    .PRECIOUS该目标的依赖文件会受到特别对待:如果makekill或命令的执行被中止,这些目标并不删除;而且如果该目标是中间文件,在不需要时不会被删除。可以将隐含规则的目标模式(如%.o)做为'.PRECIOUS'的依赖文件,这样可以保存这些规则产生的中间文件。 

    .INTERMEDIATE该目标的依赖文件被当作中间文件;如果该目标没有依赖文件,则makefile中所有的目标文件均被认为是中间文件。 

    .IGNORE在执行该目标的依赖规则的命令时,make会忽略错误,此规则本身的命令没有意义。如果该规则没有依赖关系,表示忽略所有命令执行的错误,这种用法只是为了向后兼容;由于会影响到所有的命令,所以不是特别有用,推荐使用其它更有选择性忽略错误的方法。 

    .SILENT在执行该目标的依赖规则的命令时,make并不打印命令本身。该规则的命令没有意义。在'.SILIENT'没有依赖关系时,表示执行makefile中的所有命令都不会打印,该规则只是为了向后兼容提供的。 

    .EXPORT_ALL_VARIABLES只是作为一个目标存在,指示make将所有变量输出到子进程中。 

    定义的隐含规则的后缀作为目标时,也认为它是特殊目标;两个后缀的连接也是一样,比如'.c.o'。这些目标是后缀规则,一中定义隐式规则的过时方法(但仍然广泛使用)。后缀通常以'.'开始,所以特殊目标也以'.'开始。 

    3.9一个规则多个目标 

    一条有多个目标的规则和写多条规则,每条一个目标作用是等同的。同样的命令应用于所有目标,但其效用会因将实际目标以'$@'代替而不同。规则中所有目标的依赖关系是一样的。 

    这在两种情况下有用: 

    只有依赖关系,不需要命令。例如: 

    kbd.ocommand.ofiles.o:command.h 

    所有的目标同样的命令。命令不需要完全相同,因为在命令中可以使用'$@': 

    bigoutputlittleoutput:text.g 

    generatetext.g-$(substoutput,,$@)>$@ 

    和 

    bigoutput:text.g 

    generatetext.g-big>bigoutput 

    littleoutput:text.g 

    generatetext.g-little>littleoutput 

    等同。这里假设程序'generate'产生两种输出:一种使用'-big'选项,一种使用'-little'选项。如果想象使用'$@'变化命令那样来变化依赖关系,不能通过多目标的普通规则实现,但是可以通过模式规则来实现。 

    3.10一个目标多条规则 

    一个文件可以是多条规则的目标,所有规则的依赖关系被合并。如果目标比任一个依赖文件旧,命令被执行。 

    一个文件只能有一组命令执行。如果多个规则对于同一个文件都给出了命令,make使用最后一组并打印错误信息(特殊情况:如果文件名以'.'开始,并不打印错误信息,这一点是为了和其它make兼容)。没有任何理由需要将makefile写成这样,这是make给出错误信息的理由。 

    一条只有依赖关系的附加规则可以一次给出许多文件的附加依赖文件。例如'objects'变量表示系统中编译器的所有输出.,说明当'config.h'更改时所有文件必须重做的简单方法如下: 

    objects=foo.obar.o 

    foo.o:defs.h 

    bar.o:defs.htest.h 

    $(objects):config.h 

    不用改变实际目标文件生成的规则,这条规则可以在需要增删附加的依赖关系时插入或提出。另一个诀窍是附加的依赖关系可以用变量表示,在make执行时,可以给变量赋值: 

    extradeps= 

    $(objects):$(extradeps) 

    当命令`makeextradeps=foo.h'执行时会认为'foo.h'是每个目标文件的依赖文件,但简单的'make'命令不是这样。 

    3.11静态模式规则 

    静态模式规则(staticpatternrules)可以指定多个目标,并且使用目标名字来建议依赖文件的名字;比普通多目标规则更通用因为不需要依赖关系是相同的:依赖关系必须类似但不需要相同。 

    3.11.1语法 

    TARGETS...:TARGET-PATTERN:DEP-PATTERNS... 

    COMMANDS 

    ... 

    TARGETS列表指出规则应用的目标,可以包含通配符,于普通规则的目标相同。TARGET-PATTERNDEP-PATTERNS来表明目标的依赖关系如何计算:匹配TARGET-PATTERN的目标从名字中抽出一部分,叫做词干(stem),词干被替换到DEP-PATTERNS来形成依赖文件名。 

    每个模式通常包含一个'%'字符。当TARGET-PATTERN匹配一个目标时,'%'字符可以匹配目标名中的任何部分;这部分即是词干,模式的其余部分必须完全匹配。例如'foo.o'匹配'%.o''foo'是词干;目标'foo.c''foo.out'并不匹配这个模式。 

    目标的依赖文件名通过将DEP-PATTERNS中的'%'替换为词干形成:如果依赖模式为'%.c',在替换词干'foo'可以得到'foo.c'。依赖模式中不包含'%'也是合法的,此依赖文件对所有的目标均有效。 

    如果需要在模式规则中使用'%'字符,必须在其前面加'/'字符,如果'%'前的'/'字符是有实际意义的,必须在其前面加'/',其它的'/'不必如此处理。如'the/%weird//%pattern//'在有效的'%'前是'the%weird/',其后是'pattern//'。最后的'//'保持原样是因为其并不影响'%'字符。 

    以下例子从相应的'.c'文件编译'foo.o''bar.o': 

    objects=foo.obar.o 

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

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

    每个目标必须匹配目标模式,对于不匹配的目标会给出警告。如果列表中只有部分文件匹配模式,可以使用filter函数移去不匹配的文件名: 

    files=foo.elcbar.olose.o 

    $(filter%.o,$(files)):%.o:%.c 

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

    $(filter%.elc,$(files)):%.elc:%.el 

    emacs-fbatch-byte-compile$< 

    例子中`$(filter%.o,$(files))'结果是`bar.olose.o'`$(filter%.elc,$(files))'的结果是`foo.elc'。以下例子说明'$*'的使用: 

    bigoutputlittleoutput:%output:text.g 

    generatetext.g-$*>$@ 

    命令`generate'执行时,'$*'扩展为词干'big''little'。 

    3.11.2静态模式规则和隐式规则 

    静态模式规则和隐式规则在作为模式规则是具有很多共同点,都有目标模式和构造依赖文件名的模式,不同之处在于make决定何时应用规则的方法。 

    隐式规则可应用于匹配其模式的任何目标,但只限于没有指定命令的目标,如果有多条可应用的隐式规则,只有一条被使用,取决于规则的顺序。 

    反之,静态模式规则适用于规则中明确目标列表,不适用于其它目标且总是适用于指定的每个目标。如果有两条冲突的规则,且都有命令,这是一个错误。 

    静态模式规则比隐式规则优越之处如下: 

    可为一些不能按句法分类,但可以显式列出的文件重载隐式规则 

    不能判定目录中的精确内容,一些无关的文件可能导致make适用错误的隐式规则;最终结果可能依赖于隐式规则的次序。适用静态模式规则时,这种不确定性是不存在的:规则适用于明确指定的目标。 

    3.12双冒号规则 

    双冒号规则(Double-colonrules)的目标后是'::'而不是':',当一个目标出现在多条规则中时,其处理和普通规则的处理不同。 

    当一个目标出现在多条规则中时,所有规则必须是相同类型的:都是普通的或者都是双冒号的。如果是双冒号,规则之间相互独立;如果目标需要更新,则规则的命令被执行;结果可能是没有执行,或者执行了其中一些,或者所有的规则都执行了。 

    同一目标的双冒号规则事实是完全孤立的,每条规则被被单独处理,就象不同目标的规则一样;规则按照在makefile中出现的次序被处理,此类规则真正有意义的是那些于命令执行次序无关的。 

    这种规则有时比较晦涩不是特别有用;它提供了一种机制:通过不同依赖文件的更新来对目标进行不同的处理,这种情形很罕见。每个这种规则应当提供命令,如果没有,适用的隐式规则将使用。 

    3.13自动生成依赖关系 

    makefile中,许多规则都是一些目标文件依赖于一些头文件。例如:'main.c'通过'#include'使用'defs.h',这样规则: 

    main.o:defs.h 

    告诉make'defs.h'变化时更新'main.o'。在程序比较大时,需要写许多这样的规则;而且当每次增删'#include'时,必须小心的更新makefile。许多现代的编译器可以帮你写这些规则,通常这是通过编译器的'-M'选项,例如命令: 

    cc–Mmain.c 

    输出以下内容: 

    main.o:main.cdefs.h 

    这样就不必写这些规则,有编译器代劳了。 

    注意这样的依赖关系中提及'main.o',不会被隐式规则认为是中间文件,这意味这make在使用过它之后不会将其删除。使用老的'make'程序时,习惯做法是使用'makedepend'命令利用编译器的功能产生依赖关系,该命令会产生一个'depend'文件包含所有自动产生的依赖关系,然后在makefile中使用'include'将其读入。 

    使用GNUmake时,重新生成makefile的功能使得这种做法变得过时:从不需要显式请求更新依赖关系,因为它总是重新生成任何过时的makefile。 

    自动依赖关系生成推荐的做法是对每个源文件做一个makefile。对每个源文件'NAME.c',有一个makefile'NAME.d',其中列出了目标文件'NAME.o'依赖的所有文件,这样在源文件更新时,需要扫描来产生新的依赖关系。例子是一个从'NAME.c'产生依赖关系文件'NAME.d'的模式规则: 

    %.d:%.c 

    $(SHELL)-ec'$(CC)-M$(CPPFLAGS)$</ 

    |sed'/''s//($*/)/.o[:]*//1$@/g'/''>$@' 

    -e选项是当$(CC)命令失败时(exit状态非0),shell立刻退出。通常shell的返回值是管道中最后一条命令(sed)的返回值,这样make不会注意到编译器出错。 

    使用GNUC编译器时(gcc),可以用'-MM'选项来代替'-M'选项,这样省略系统头文件的依赖关系。'sed'命令的目的是将 

    main.o:main.cdefs.h 

    转换为 

    main.omain.d:main.cdefs.h 

    这样使得每个'.d'文件依赖于'.o'文件相应源文件和头文件,make则可以在原文间或头文件变化时更新依赖关系文件。 

    如果定义了生成'.d'文件的规则,可以使用'include'指令来读入所有的文件: 

    sources=foo.cbar.c 

    include$(sources:.c=.d) 

    例中使用替换变量来将源文件列表'foo.cbar.c'转换为依赖关系文件的列表。因为'.d'文件和其它文件一样,不需要更多工作,make会在需要时重新生成它们。 

    4编写命令 

    规则的命令是由一一执行的shell命令组成。除了以分号隔开写在依赖关系后的命令,每个命令行必须以tab字符开始空行和注释行可以出现在命令行中,处理时被忽略(注意:以tab字符开始的空行不是''行,是一条空命令)。 

    可以在命令中使用任何程序,但这些程序是由$(SHELL)来执行的。 

    4.1回显 

    通常make打印出要执行的命令,称之为回显,这和亲自敲命令的现象是一样的。当行之前有'@'字符时,命令不再回显,字符'@'在传递给shell前丢弃。典型的用法是只对打印命令有效,比如'echo'命令: 

    @echoAbouttomakedistributionfiles 

    make使用'-n''—just-print'选项时,显示要发生的一切,但不执行命令。只有在这种情况下,即使命令以'@'开始,命令行仍然显示出来。这个选项对查看make实际要执行的动作很有用。 

    ‘-s''—silent'选项阻止make所有回显,就象所有命令以'@'开始一样;一条没有依赖关系的'.SILENT'规则有相同的作用,但是'@'更加灵活。 

    4.2执行 

    在需要执行命令更新目标时,make为每一行创建一个子shell来执行。这意味着诸如为进程设置局部变量的shell命令'cd'(改变进程的当前目录)不会影响以后的命令。如果需要'cd'影响下一个命令,将它们放在一行上用分号隔开,这样make认为是一条命令传递给shell程序(注意:这需要shell支持): 

    foo:bar/lose 

    cdbar;gobblelose>../foo 

    另一个形式使用续行符: 

    foo:bar/lose 

    cdbar;/ 

    gobblelose>../foo 

    shell程序的名字是通过'SHELL'变量来取得的。 

    *UNIX)不象大多数变量,'SHELL'变量不是通过环境来设置的(即需要在makefile中设置),因为'SHELL'环境是个人选择的,如果不同人的选择会影响makefile的功能的话,这样很糟糕。 

    4.3并行执行 

    GNUmake可以一次执行几条命令。通常make一次执行一条命令,等待其返回,再执行下一条。使用'-j''—jobs'可以同时执行多条命令。如果'-j'后梗一个正数,表示一次可以执行的命令条数;如果'-j'之后没有参数,则不限制可执行的命令数。缺省的数量是一。 

    一个讨厌的问题是如果同时执行多条命令,它们的输出会混在一起;另一个问题是两个进程不能从同一个设备获得输入。 

    4.4错误 

    每条shell命令返回时,make会检查其返回状态。如果命令执行成功,则下一条命令被执行,最后一条命令执行完后,规则执行结束。 

    如果有错误(返回非0状态),make放弃当前规则,也可能是所有规则。 

    有时候命令执行错误并不是问题,比如使用'mkdir'命令确保目录存在:如果目录一存在,则'mkdir'会报告错误,但仍希望make继续。 

    要忽略命令的错误,在命令之前使用'-‘字符,'-‘字符在传递给shell之前被丢弃: 

    clean: 

    -rm-f*.o 

    如果使用'-i''—ignore-errors'选项,make会忽略所有命令产生的错误;一条没有依赖关系的'.IGNORE'规则有相同的作用,但'-‘更灵活。 

    在忽略错误时,make将错误也认为是成功,只是通知你命令的退出状态和和错误被忽略。如果make并未告知忽略错误,在错误发生时,表明该目标不能成功更新,直接或间接依赖于此的目标当然也不能成功;这些目标的命令不会被执行,因为其先决条件不满足。 

    通常make会立即以非0状态退出。然而,如果给定'-k''—keep-going'选项,make在退出前会处理其它的依赖关系,进行必要的更新。例如,在编译一个目标文件遇到错误,'make-k'会继续编译其它的目标文件。 

    通常认为你的目的是更新指定的目标,当make知道这是不可能时,会立即报告失败;'-k'选项指示真正目的是测试更新程序的更多可能性:在编译之前找出更多不相关的问题。 

    如果命令失败了,假设它更新的目标文件,这个文件是不完整的不能使用-至少不是完全更新的。但文件的最后修改时间表明停已经是最新的,下一次make运行时,不会再更新这个文件。这种情况和命令被kill相同;则通常情况下在命令失败时将目标删除是正确的;当'.DELETE_ON_ERROR'是目标时make帮你做这件事。虽然你总是希望make这么做,但这不是过去的习惯;所以必须显式要求make这样做(其它的make自动这样做)。 

    4.5中断make 

    如果make执行命令时遇到错误,可能会删除命令更新的目标文件:make检查文件的修改时间是否变化。删除目标的目的是确保make下次执行时重新生成它。为什么这样做?假设在编译器运行时按了'Ctrl-c',此时编译器写生成目标文件'foo.o''Ctrl-c'kill了编译器,留下一个不完整的文件,但它的修改时间比源文件'foo.c'新;此时make也受到'Ctrl-c'信号删除这个不完整的文件,如果make不这样做,下次make运行时认为'foo.o'不需要更新,会在链接时出现奇怪的错误。 

    可以使用'.PRECIOUS'规则来防止目标文件被删除。在make更新目标时,会检测其是否为'.PRECIOUS'的依赖,决定在命令出错或中断时是否删除该目标。如果你希望目标的更新是原子操作,或是用来记录修改时间,或必须一直存在防止其它类型的错误,这些理由使得你必须这样做。 

    4.6递归使用 

    递归使用make就是在makefile中使用make命令。这种技术在你将一个大系统分解为几个子系统,为每个自系统提供一个makefile时有用处。比如有一个子目录'subdir'中有自己的makefile,希望make在自目录中运行,可以这样做: 

    subsystem: 

    cdsubdir;$(MAKE) 

    或者 

    subsystem: 

    $(MAKE)-Csubdir 

    可以照抄这个;例子来递归使用make 

    4.6.1‘MAKE'变量 

    递归的make必须使用'MAKE'变量,不是显式的make命令: 

    subsystem: 

    cdsubdir;$(MAKE) 

    该变量的值是被调用的make的名字。在命令中使用'MAKE'有特殊的功能:它改变了`-t'(`--touch')`-n'(`--just-print')`-q'(`--question')选项的含义。使用上例来考虑'make–t'命令('-t'选项将目标标记为最新但不运行命令),更加'-t'选项的功能,该命令将创建一个'subsystem'文件,实际希望的操作是运行'cdsubdir;make–t';但这会执行命令,与'-t'的原意不符。 

    这个特殊功能做了期望的工作。当命令行包含变量'MAKE'时,选项'-t''-n''-q'并不适用。不管这些导致不会执行命令的标志,包含'MAKE'变量的命令始终会执行。正常的'MAKEFLAGS'机制将这些标志传递到子make,这样打印命令的请求被传播到子系统中。 

    4.6.2传递变量到子make 

    上级(top-levelmake中的变量可以显式通过环境传递到子make中。在子make中,这些变量被缺省定义,但不会重载子makefile中的定义除非使用'-e'选项。 

    为向下传递,或输出变量,make在运行命令时将其加入到环境变量中;子make,可以使用环境变量来初始化变量表。除非显式要求,make只输出初始环境中或命令行设置的变量而且变量名只由字母,数字和下划线组成。一些shell不能处理有其它字符的环境变量。 

    特殊变量'SHELL''MAKEFLAGS'总是输出,如果'MAKEFILE'变量有值,也会输出。Make自动通过'MAKEFLAGS'来输出命令行定义的变量。 

    如果想要输出特定变量,使用'export'指令: 

    exportVARIABLE... 

    如果要阻止输出一个变量,使用'unexport'指令: 

    unexportVARIABLE... 

    为方便起见,可以在定义变量时输出它: 

    exportVARIABLE=value 

    和 

    VARIABLE=value 

    exportVARIABLE 

    作用相同。 

    如果要输出所有的变量,使用'export'指令本身就可以了。 

    变量'MAKELEVEL'在一级一级传递时会改变,这个变量的值是表示嵌套层数的字符串,顶级'make'是,变量的值为'0';子make的值为'1';子子make的值为'2',依此类推。 

    ‘MAKELEVEL'的用途是在条件指令中测试它,这样写出在递归运行时和直接运行时表现不同的makefile



原创粉丝点击