Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Make

简介

make是Linux操作系统上常见的较为基础的构建系统(build system)。构建系统的主要工作,是管理 一个较大规模或较为复杂的项目的各个“部件”,以及如何将这些“部件”“组装”成为最终的项目产物。以C 语言项目的编译过程为例。一个最简单的C语言项目,只需要有一个源文件即可,这种简单的项目,确 实对构建系统没有很强的需求,只需要一条很简短的gcc命令即可完成编译。但是,随着项目规模的增 加,项目的源文件也会越来越多,诚然,我们可以把所有源文件的文件名都提交给gcc命令进行编译,但 是这样会导致每次都要输入一个很长的命令。或许有同学会说,命令历史记录或者写一个简单的脚本都 可以简化这个操作。但是构建系统能做的不仅如此。如果每次都把所有源文件提交给gcc让它进行编译, 那么gcc每次都会执行一次完整编译 ,即重新将每一个源文件编译成对象文件,再将他们进行链接。然 而,事实上,在一个规模较大的项目中,我们在更新这个项目时,通常不会修改项目的所有源文件,常 常只有一小部分或者一部分源文件被修改了,那么那些没有被修改的源文件就完全没有必要再重新编 译,只需要将已有的中间产物 (对象文件)与修改过的文件重新编译后的对象文件进行链接即可,也即 所谓的部分编译 。换句话说,如果每次都使用前述的一条完整gcc命令,那么相当一部分编译时间就被 浪费了,因为gcc自身并不理解哪些文件需要重新编译,哪些文件不需要重新编译,而且除非添加特定参 数,gcc也不会保留中间产物或利用现有的中间产物。相比于在一条命令中把所有源文件都提交给gcc, 更好的做法是把整个构建过程拆散,先逐个将源文件编译为对象文件,并且保存这些对象文件作为中间 产物,随后再将这些中间产物链接到一起,产生完整的程序。当然,这么做相较于一条gcc命令会复杂很 多,但对于大型项目可以显著减少编译时间。由于整个流程从一条命令变成了需要以特定顺序执行的许 多条命令,因此出于可维护性的考虑,应当把整个流程以文件形式保存下来,这就是构建系统中常见的 规则文件 。

如前所述,一个比较复杂的项目,其编译流程涉及多条(甚至是大量)需要按照特定顺序执行的命令。 如何确定这些命令的顺序?如何在项目的结构发生变更后重新确定这些命令执行的顺序?这些问题,是 单纯的shell脚本所不能解决的,因此构建系统的规则文件通常并不是简单的shell脚本。一般而言,构建 系统的规则文件都包含几个基本元素(不同系统中的术语可能不同),以make为例:目标(target)、 配方(recipe)、前置条件(prerequisites)。目标指的是构建系统需要负责生成的一个文件或完成的 一项任务,这个文件可以是某个中间产物文件,也可以是最终的产物或任何其他文件;此外,它也可以 是一项抽象的任务,例如本实验中用到的使用qemu运行炸弹程序,也被作为make的一个目标。前置条 件指的是在完成这个目标前需要完成的其他目标,一个目标只有在前置目标都已完成时才能被执行。最 简单的前置目标是构建这个目标所需的源文件,例如用于生成对象文件app.o的目标,其前置目标可以 是其源文件app.c。构建系统可以根据文件系统中app.o和app.c两个文件的修改时间,来判断是否需要 执行这个目标,如果app.c在app.o产生之后又被修改过,则认为app.o需要重新生成,否则就可以直接 使用现有的app.o。配方则是定义如何完成某一个特定的目标,通常包含一系列shell命令等。从上面的描述中不难看到,规则文件的作用实际上是定义了一个依赖图 ,图中每个目标是一个顶点,如果目标A依赖目标B,则目标B有一条边指向目标A。从原理上来说,构建系统会根据规则文件构建出依赖图,随 后依照图的拓扑序执行每个顶点所定义的配方,最终就能生成完整的产物。还需要注意,构建系统是独 立于编译器之外的,构建系统能做的,是给程序员提供自动生成和执行依赖图的能力,但依赖图的结构 本身仍然是规则文件的编写者定义的,例如,项目的构建涉及到多少目标,每个目标的配方使用什么命 令,每个目标的前置条件是什么,这些问题是不可能由构建系统自身来解决的,而是要由规则文件的编 写者“告知”构建系统。

总而言之,使用构建系统而不是直接调用编译器,对于复杂C/C++项目有下列好处:

  • 增强构建流程可维护性
  • 简化执行部分任务(类似于脚本),例如清理构建中间产物等
  • 通过部分编译/增量编译,可以显著减少大规模项目的编译时间
  • 可以并发执行所有前置已被满足的目标(并发编译),有效利用多核CPU,进一步缩短编译时间

扩展阅读

tip

make是一个比较底层的构建系统,在使用上仍有诸多繁琐之处,于是又出现了一些用于替代make或在make更上层,更为简化的构建系统,前者的代表项目之一是ninja,后者的代表项目之一是CMake,感兴趣的同学可以进一步了解。

Last change: 2024-08-17, commit: 5b84259