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