GUN make 学习笔记之make初探

目标和前提条件

本质上,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.cfoo.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.olexer.o-lflmake现在考虑如何编译count_words.o并看到其相应的规则。接着,make会检查count_words.o的前提条件并注意到count_words.c没有相应的规则,同时该文件存在,因此,make通过执行以下命令将count_words.c编译为count_words.o

gcc-c count_words.c


这种“由目标到前提条件,再由前提条件到目标,再从目标到前提条件”的工作机制就是 make 分析 makefile 文件决定要执行命令的典型做法。


接下来,make会处理lexer.o前提条件。规则链会将make导向lexer.c,但是这次这个文件不存在。make发现了生成lexer.c的规则,因此会运行flex程序。现在,lexer.c文件存在了,make接着执行gcc命令。


最后,make会测试-lfl。其中gcc-l选项用来要求将指定的系统程序库链接到应用程序中。事实上,此处指定的参数fl表明了库的名称为libfl.aGUNmake对这个语法提供了特别的支持。当-l<NAME>这种形式的前提条件被发现时,make会查找libNAME.so形式的文件;如果没有找到,make会接着查找libNAME.a形式的文件。在此例中,make会找到/user/lib/libfl.a文件,并进行最后的动作——链接。


最小化重编译


运行程序时,我们发现在输出fees,fies,foesfums出现的次数,它同时也输出了输入的文本。这并不是我们想要的。这个问题是我么忽略了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描述文件命名为makefileMakefile或者GUNMakefile

  • 当执行make命令时,makefile与用户当前目录相同。

当在上面三种条件下调用makemake会自动创建它看到的第一个目标。为了更新不同的目标(或者是更新不止一个目标),可以在命令行上包含目标的名词:

$ <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的变量,命令。现在,你应该避免使用变量,宏以及多行命令。

摘自http://www.linuxsir.org/main/doc/gnumake/GNUmake_v3.80-zh_CN_html 由徐海兵老師翻译整理。 本文比较完整的讲述GNU make工具,涵盖GNU make的用法、语法。同时重点讨论如何为一个工程编写Makefile。作为一个Linux程序员,make工具的使用以及编写Makefile是必需的 目 录 第一章:概述 1.1 概述 1.2 准备知识 第二章:GNU make 介绍 2.1 Makefile简介 2.2 Makefile规则介绍 2.3 简单的示例 2.4 make如何工作 2.5 指定变量 2.6 自动推导规则 2.7 另类风格的makefile 2.8 清除工作目录过程文件 第三章:Makefile 总述 3.1 Makefile的内容 3.2 makefile文件的命名 3.3 包含其它makefile文件 3.4 变量 MAKEFILES 3.5 变量 MAKEFILE_LIST 3.6 其他特殊变量 3.7 makefile文件的重建 3.8 重载另外一个makefile 3.9 make如何解析makefile文件 3.9.1 变量取值 3.9.2 条件语句 3.9.3 规则的定义 3.10 总结 第四章:Makefile的规则 4.1 一个例子 4.2 规则语法 4.3 依赖的类型 4.4 文件名使用通配符 4.4.1 统配符使用举例 4.4.2 通配符存在的缺陷 4.4.3 函数wildcard 4.5 目录搜寻 4.5.1 一般搜索(变量VPATH) 4.5.2 选择性搜索(关键字vpath) 4.5.3 目录搜索的机制 4.5.4 命令行和搜索目录 4.5.5 隐含规则和搜索目录 4.5.6 库文件和搜索目录 4.6 Makefile伪目标 4.7 强制目标(没有命令或依赖的规则) 4.8 空目标文件 4.9 Makefile的特殊目标 4.10 多目标 4.11 多规则目标 4.12 静态模式 4.12.1 静态模式规则的语法 4.12.2 静态模式和隐含规则 4.13 双冒号规则 4.14 自动产生依赖 第五章:规则的命令 5.1 命令回显 5.2 命令的执行 5.3 并发执行命令 5.4 命令执行的错误 5.5 中断make的执行 5.6 make的递归执行 5.6.1 变量MAKE 5.6.2 变量和递归 5.6.3 命令行选项和递归 5.6.4 -w选项 5.7 定义命令包 5.8 空命令 第六章:Makefile中的变量 6.1 变量的引用 6.2 两种变量定义(赋值 ) 6.2.1 递归展开式变量 6.2.2 直接展开式变量 6.2.3 如何定义一个空格 6.2.4 “?=”操作符 6.3 变量的高级用法 6.3.1 变量的替换引用 6.3.2 变量的套嵌引用 6.4 变量取值 6.5 如何设置变量 6.6 追加变量值 6.7 override 指示符 6.8 多行定义 6.9 系统环境变量 6.10 目标指定变量 6.11 模式指定变量 第七章:Makefile的条件执行 7.1 一个例子 7.2 条件判断的基本语法 7.2.1 关键字“ifeq” 7.2.2 关键字“ifneq” 7.2.3 关键字“ifdef” 7.2.4 关键字“ifndef” 7.3 标记测试的条件语句 第八章:make的内嵌函数 8.1 函数的调用语法 8.2 文本处理函数 8.2.1 $(subst FROM,TO,TEXT) 8.2.2 $(patsubst PATTERN,REPLACEMENT,TEXT) 8.2.3 $(strip STRINT) 8.2.4 $(findstring FIND,IN) 8.2.5 $(filter PATTERN…,TEXT) 8.2.6 $(filter-out PATTERN...,TEXT) 8.2.7 $(sort LIST) 8.2.8 $(word N,TEXT) 8.2.9 $(wordlist S,E,TEXT) 8.2.10 $(words TEXT) 8.2.11 $(firstword NAMES…) 8.3 文件名处理函数 8.3.1 $(dir NAMES…) 8.3.2 $(notdir NAMES…) 8.3.3 $(suffix NAMES…) 8.3.4 $(basename NAMES…) 8.3.5 $(addsuffix SUFFIX,NAMES…) 8.3.6 $(addprefix PREFIX,NAMES…) 8.3.7 $(join LIST1,LIST2) 8.3.8 $(wildcard PATTERN) 8.4 foreach 函数 8.5 if 函数 8.6 call函数 8.7 value函数 8.8 eval函数 8.9 origin函数 8.10 shell函数 8.11 make的控制函数 8.11.1 $(error TEXT…) 8.11.2 $(warning TEXT…) 第九章:执行make 9.1 指定makefile文件 9.2 指定终极目标 9.3 替代命令的执行 9.4 防止特定文件重建 9.5 替换变量定义 9.6 使用make进行编译测试 9.7 make的命令行选项 第十章:make的隐含规则 10.1 隐含规则的使用 10.2 make的隐含规则一览 10.3 隐含变量 10.3.1 代表命令的变量 10.3.2 命令参数的变量 10.4 make隐含规则链 10.5 模式规则 10.5.1 模式规则介绍 10.5.2 模式规则示例 10.5.3 自动化变量 10.5.4 模式的匹配 10.5.5 万用规则 10.5.6 重建内嵌隐含规则 10.6 缺省规则 10.7 后缀规则 10.8 隐含规则搜索算法 第十一章:使用make更新静态库文件 11.1 库成员作为目标 11.2 静态库的更新 11.2.1 更新静态库的符号索引表 11.3 make静态库的注意事项 11.4 静态库的后缀规则 第十二章 : GNU make的特点 12.1 源自System v的特点 12.2 源自其他版本的特点 12.3 GNU make自身的特点 第十三章 和其它版本的兼容 第十四章 Makefile的约定 14.1 基本的约定 14.2 规则命令行的约定 14.3 代表命令变量 14.4 安装目录变量 14.5 Makefile的标准目标名 14.6 安装命令分类 第十五章 make的常见错误信息   附录:关键字索引 1. GNU make可识别的指示 符 2. GNU make函数 3. GNU make的自动化变量 4. GNU make环境变量 后序
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值