Make构建工具快速入门及应用
Make是工程构建的必要工具。
Make这个词,英语的意思是“制作”。Make命令直接用了这个意思,就是要做出某个文件。比如,要做出文件a.txt,就可以执行下面的命令。
$ make a.txt
但是,如果你真的输入这条命令它并不会起作用。因为Make命令本身并不知道,如何做出a.txt,需要有人告诉它,如何调用其他命令完成这个目标。
make命令需要按给定的“规则(Rules)”来制作目标文件。这个给定的规则来自一个叫“Makefile”的文件。
1 Makefile
Makefile文件由一系列规则(rules)构成。每条规则的形式如下。
target: dependencies …
commands
…
上面第一行冒号前面的部分,叫做目标(target),冒号后面的部分叫做前置条件(prerequisites);第二行必须由一个tab键起首,后面跟着命令(commands)。
“目标”是必需的,不可省略;”前置条件”和”命令”都是可选的,但是两者之中必须至少存在一个。
每条规则就明确两件事:构建目标的前置条件是什么,以及如何构建。下面就详细讲解,每条规则的这三个组成部分。
1.1 target
一个目标(target)就构成一条规则。目标通常是文件名,指明Make命令所要构建的对象,比如上文的a.txt。目标可以是一个文件名也可以是多个文件名,之前用空格分隔。
除了文件名,目标还可以是某个操作的名字,这称为“伪目标”(phony targe)。
clean: rm *.o
上面代码的目标是clean,它不是文件名,而是一个操作的名字,属于“伪目标”,作用是删除对象文件。
$ make clean
但是,如果当前目录中,正好有一个文件叫做clean,那么它不会执行。因为Make发现clean文件已经存在,就认为没有必要重新构建了,就不会执行指定的rm命令。
为了避免这种情况,可以明确声明clean是“伪目标”,写法如下:
.PHONY: clean
clean: rm *.o temp
如果Make命令运行时没有指定目标,默认会执行Makefile文件的第一个目标。
$ make
上面代码执行Makefile文件的每下目标。
1.2 dependencies
依赖(前置条件)通常是一组文件名,也可以是目标,之间用空格分隔。它指定了“目标”是否重新构建的判断标准:只要有一个依赖文件不存在,或者有过更新(依赖文件的last-modification时间戳比目标的时间戳新),“目标”就需要重新构建。
1.3 双冒号规则
GNU make的双冒号规则给我们提供一种根据依赖的更新情况而执行不同的命令来重建同一目标的机制。一般这种需要的情况很少,所以双冒号规则的使用比较罕见。一般双冒号规则都需要定义命令,如果一个双冒号规则没有定义命令,在执行规则时将为其目标自动查找隐含规则。
1.4 Makefile指示符
指示符指明在make程序读取makefile文件过程中所要执行的一个动作。其中包括:
- include 读取一个文件,读取给定文件名的文件。
Makefile中包含其它文件的关键字是“include”,和C语言对头文件的包含方式一致。
“include”指示符告诉make暂停读取当前的Makefile,而转去读取“include”指定的一个或者多个文件,完成以后再继续当前Makefile的读取。Makefile中指示符“include”书写在独立的一行,其形式如下:
include FILENAMES...
FILENAMES
是shell所支持的文件名(可以使用通配符)。
2 make的执行过程(make如何解析makefile文件)
GUN make的执行过程分为两个阶段(摘自GNU Makefile手册)。
- 第一阶段:读取所有的makefile文件(包括“MAKEFILES”变量指定的、指示符“include”指定的、以及命令行选项“-f(–file)”指定的makefile文件),内建所有的变量、明确规则和隐含规则,并建立所有目标和依赖之间的依赖关系结构链表。
- 第二阶段:根据第一阶段已经建立的依赖关系结构链表决定哪些目标需要更新,并使用对应的规则来重建这些目标。
具体的执行过程如下:
- 依次读取变量“MAKEFILES”定义的makefile文件列表
- 读取工作目录下的makefile文件(根据命名的查找顺序“GNUmakefile”,“makefile”,“Makefile”,首先找到哪个就读取哪个)
- 依次读取工作目录makefile文件中使用指示符“include”包含的文件
- 查找重建所有已读取的makefile文件的规则(如果存在一个目标是当前读取的某一个makefile文件,则执行此规则重建此makefile文件,完成以后从第一步开始重新执行)
- 初始化变量值并展开那些需要立即展开的变量和函数并根据预设条件确定执行分支
- 根据“终极目标”以及其他目标的依赖关系建立依赖关系键表
- 执行“终极目标”所有依赖文件所在的规则(如果Makefile中一个规则所描述的目标不是“终极目标”所依赖的,那么这个规则将不会被执行。)
- 执行“终极目标”所在的规则
GNU的make工作时的执行步骤如下:(想来其它的make也是类似)
- 读入所有的Makefile。
- 读入被include的其它Makefile。
- 初始化文件中的变量。
- 推导隐晦规则,并分析所有规则。
- 为所有的目标文件创建依赖关系链。
- 根据依赖关系,决定哪些目标要重新生成。
- 执行生成命令。
1-5步为第一个阶段,6-7为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么,make会把其展开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。
当然,这个工作方式你不一定要清楚,但是知道这个方式你也会对make更为熟悉。有了这个基础,后续部分也就容易看懂了。
2.2 make的退出码
make命令执行后有三个退出码:
0 —— 表示成功执行。
1 —— 如果make运行时出现任何错误,其返回1。
2 —— 如果你使用了make的“-q”选项,并且make使得一些目标不需要更新,那么返回2。
下一篇: 算法-两数之和