目标和前提条件
本质上,makefile包含一组规则用于创建应用程序。make所看到的第一条规则被称作缺省规则。一条规则包含三个部分:目标,前提和命令:
target: prereq1 prereq2
commands
target (目标)是文件或需要创建的东西。 prerequisites (前提条件即 prereq1 , prereq2 )和 dependents (依赖)是要成功创建 target 必须事先存在的。 commands (命令)是从前提条件创建目标所用到的 shell 命令。
比如编译C文件foo.c为目标文件foo.o:
foo.o: foo.c foo.h
gcc -c foo.c
出现在冒号之前的是目标文件foo.o。出现在冒号后面的是前提条件文件foo.c和foo.h。命令脚本通常在接下来的一行出现并且前面有一个制表符(<TAB>)。
当要求make执行一条规则时,它首先找到目标和前提条件指出的文件。如果没有任何前提条件都有一条关联规则,那么make将试图先更新性前提条件。接着,更性目标文件。如果任何前提条件都比目标要新,目标将通过执行命令得以更新。每条命令都传给shell并在它的子shell中执行。如果任何一条命令产生错误,目标的创建将被终止,随后make退出。如果一个文件最近被修改,那就认为它比其他的文件新。
下面的程序记录了输入中单词 “fee”,“fie”, “foe” , “fum”出现的次数。它使用flex扫描器驱动:
#include <stdio.h>
extern int fee_count, fie_count, foe_count, fum_count;
extern int yylex(void);
int main(int argc, char **argv)
{
yylex();
printf(“%d %d %d %d\n”, fee_count, fie_count, foe_count, fun_count);
exit(0);
}
扫描器非常简单:
<span style="white-space:pre"> </span>int fee_count = 0;
<span style="white-space:pre"> </span>int fie_count = 0;
<span style="white-space:pre"> </span>int foe_count = 0;
<span style="white-space:pre"> </span>int fum_count = 0;
%%
fee fee_count++;
fie fie_count++;
foe foe_count++;
fum fum_count++;
这个程序的makefile文件同样十分简单:
count_words: count_words.o lexer.o -lfl
gcc count_words.o lexer.o -lfl -o count_words
count_words.o: count_words.c
gcc -c count_words.c
lexer.o: lexer.c
gcc -c lexer.c
lexer.c: lexer.l
flex -t lexer.l > lexer.c
当这个文件第一次被执行时,可以看到:
$ <strong>make</strong>
gcc -c count_words.c
flex -t lexer.l > lexer.c
gcc -c lexer.c
gcc count_words.o lexer.o -lfl -o count_words
现在我们有一个可执行程序了。当然,现实中的程序通常包含比这个程序更多的模块。同样,在之后你将会看到,这个makefile并没有使用到很多make的特性,这是比必要的更为详细。这是一个功能性和有用的makefile。举例来说,在这个范例编写期间,为了测试程序,我执行了这个makefile不少于10次。
当你执行这个makefile时,你可能会发现命令执行的顺序几乎和它们在makefile中出现的顺序正好相反。这种自上而下的风格在makefile中是很常见的。一般来说,通用的形式是目标会先在makefeile中指定,随后给出具体的细节。make程序有多种方式支持这种风格。其中以make两段执行模型和递归变量最为重要。我们将在后面的章节具体介绍。
依赖性检查
make程序如何知道该做什么?让我们继续讨论前一个范例。
make首先注意到命令行没有包含目标,因此它决定建立默认目标count_words。它检查前提条件看到三个依赖项:count_words.o,lexer.o和-lfl。make现在考虑如何编译count_words.o并看到其相应的规则。接着,make会检查count_words.o的前提条件并注意到count_words.c没有相应的规则,同时该文件存在,因此,make通过执行以下命令将count_words.c编译为count_words.o:
gcc-c count_words.c
接下来,make会处理lexer.o前提条件。规则链会将make导向lexer.c,但是这次这个文件不存在。make发现了生成lexer.c的规则,因此会运行flex程序。现在,lexer.c文件存在了,make接着执行gcc命令。
最后,make会测试-lfl。其中gcc的-l选项用来要求将指定的系统程序库链接到应用程序中。事实上,此处指定的参数fl表明了库的名称为libfl.a。GUNmake对这个语法提供了特别的支持。当-l<NAME>这种形式的前提条件被发现时,make会查找libNAME.so形式的文件;如果没有找到,make会接着查找libNAME.a形式的文件。在此例中,make会找到/user/lib/libfl.a文件,并进行最后的动作——链接。
最小化重编译
运行程序时,我们发现在输出fees,fies,foes和fums出现的次数,它同时也输出了输入的文本。这并不是我们想要的。这个问题是我么忽略了lexical分析器的一些规则,而且flex会将未被识别的文本送往输出。为了解决这个问题,我们只要加入一条“anycharacter”规则和换行规则:
<span style="white-space:pre"> </span>int fee_count = 0;
int fie_count = 0;
int foe_count = 0;
int fum_count = 0;
%%
fee fee_count++;
fie fie_count++;
foe foe_count++;
fum fum_count++;
.
\n
在修改完这个文件之后,我们需要重新编译应用程序以便测试所做的修正:
$ <strong>make</strong>
flex -t lexer.l > lexer.c
gcc -c lexer.c
gcc count_words.o lexer.o -flf -o count_words
注意到这次count_words.c文件并没有重新编译。当make分析规则时,它发现count_words.o文件已经存在,并且比前提条件中count_words.c文件要新,所以不需要采取任何更新措施。然而,在分析lexer.c文件时,make发现前提条件中lexer.l文件比目标文件lexer.c文件要新,所以make必须更新lexer.c。反过来,这也导致了lexer.o和后续count_words的更新。现在,我们的单词计数程序被修正了:
$ <strong>count_words < lexer.l</strong>
3 3 3 3
调用make
前面的范例做了如下假设:
-
所有的源代码和make描述文件全都存放在单一目录中。
-
make描述文件命名为makefile,Makefile或者GUNMakefile。
-
当执行make命令时,makefile与用户当前目录相同。
当在上面三种条件下调用make,make会自动创建它看到的第一个目标。为了更新不同的目标(或者是更新不止一个目标),可以在命令行上包含目标的名词:
$ <strong>make lexer.c</strong>
当 make 执行时,它会读取描述文件并识别需要更新的目标。如果目标或者它的前提条件中任何一个过时了(或不存在),则会执行相应规则的命令脚本的 shell 命令(一次执行一条命令)。这些命令执行后, make 假设这个目标已经更新,于是移向下一个目标活在退出。
如果你指定的目标已经是最新的,make告诉你这个情况并立即退出,不会做任何其他的事情:
$ <strong>make lexer.c</strong>
make:`lexer.c' is up to date.
如果你指定的目标不在 makefile 中,也不存在与之相应的隐含规则, make 会做以下响应:
$ <strong>make non-existent-target</strong>
make:*** No rule to make target `non-existent-target'. Stop.
make 拥有许多命令行选项。其中最有用的是— just-print (或者 -n ),该选项通知 make 显示将要执行的特定命令,但不实际执行它。当你编写 makefe 时,这个功能非常有用。你甚至可以在命令行上设置 makefile 变量用来重写默认变量或者 makefile 中设定的变量。
基本的Makefile语法
对make有了基本的认识之后,现在你差不多可以编写自己的makefile文件了。这一节,我们将会介绍makefile的基本语法和结构,让你开始使用make。
Makefile文件通常是自上而下组织的,所有通常会更新上层的目标(通常叫做all)。更多更为详细的紧随程序维护目标之后,例如clean目标用来清除不必要的临时文件。正如你猜测的一样,目标的名称不一定是真实的文件,你可以使用任何名称。
在上面的范例中,我们看到的是经过简化的规则。更为完整的规则(但依然不是足够完整)形如是:
target1target2 target3: prerequisite1 prerequisite2
<span style="white-space:pre"> </span>command1
<span style="white-space:pre"> </span>command2
<span style="white-space:pre"> </span>command3
在冒号的左边可以有一个或多个目标,冒号的右边可以有零个或多个前提条件。如果冒号右边没有指定前提条件,那么只有不存在的目标才会被更新。用于更新目标的一组命名有时被叫做命令行脚本,但是通常只是叫做命令。
每个命令必须以制表符(<TAB>)开始。这个语法告诉make将制表符后面的字符传给子shell执行。如果你不经意地在非命令行前加入了制表符,make会将后面的文本解释为命令。如果你很幸运,这个误入的制表符会被认为是语法错误,你将收到下面的信息:
$<strong> make</strong>
Makefile:6:*** commands commence before first target. Stop.
我们将在后面讨论错综复杂的制表符。
make的注释符是#,本行所有在#后面的文本都将被忽略。注释可以缩进,其前面的空白字符被忽略。注释符#不会将命令的文本注释引入make。整行包括#和子字符串被传递给shell执行。怎样处理取决于你的shell。
你可以使用Unix标准的转义字符(\)来延续过长的行。它是用于延长命令的方式,同时也可用于延长前提条件。后面,我们将介绍其他的方式来处理过长的前提条件。
现在你有足够的背景编写简单的makefile文件了。接下来将讨论规则的细节,make的变量,命令。现在,你应该避免使用变量,宏以及多行命令。