跳转到内容

Make 与构建自动化入门

前面提到,把源码变成可执行文件要经过预处理、编译、汇编、链接四个阶段。项目稍微复杂一点,源文件数量变多,每次改动之后手动重复输入这些命令会变得非常繁琐,而且容易漏掉某个该重新编译的文件。Make 是解决这类问题的经典构建自动化工具。

Make 的行为由一个名为 Makefile 的文件定义,其中每一条规则(Rule)遵循固定的结构:

target: dependencies
command
  • 目标(Target):这条规则最终要生成的文件名,或者一个用来触发操作、本身不对应实际文件的名字(称为伪目标)
  • 依赖(Dependencies):生成这个目标之前,需要先确保已经存在或已经是最新状态的文件列表
  • 命令(Command):生成目标实际要执行的 Shell 命令
hello: hello.o
gcc hello.o -o hello
hello.o: hello.c
gcc -c hello.c -o hello.o
clean:
rm -f hello hello.o

这份 Makefile 定义了三条规则:生成 hello 依赖 hello.o,生成 hello.o 依赖 hello.c。执行 make(不带参数时默认执行第一条规则对应的目标)时,Make 会顺着依赖关系往前追溯,发现 hello 依赖 hello.ohello.o 又依赖 hello.c,于是按正确的先后顺序依次执行对应的命令,直到生成最终目标。

clean 这条规则不对应任何实际要生成的文件,纯粹是为了执行“删除构建产物”这个操作而存在,这类规则称为伪目标,执行方式是显式指定目标名:make clean

增量构建:只重新处理变化过的部分

Section titled “增量构建:只重新处理变化过的部分”

Make 判断是否需要重新执行某条规则的依据是文件的修改时间戳:只有当依赖文件的修改时间比目标文件更新(或者目标文件根本不存在)时,对应的命令才会被执行;如果目标文件已经比它的所有依赖都新,说明目标已经是最新状态,Make 会跳过这条规则,不重复执行。

这意味着只改动了 hello.c 之后重新执行 make,Make 会发现 hello.chello.o 新,于是重新执行编译生成新的 hello.o,进而发现新的 hello.o 又比 hello 新,于是重新执行链接——但如果这次改动的是一个和 hello 完全无关的其他文件,相关规则不会被触发,避免了不必要的重复编译。项目文件越多,这种增量构建带来的时间节省就越明显。

  • $@ 当前规则的目标名
  • $< 当前规则依赖列表中的第一个文件
  • $^ 当前规则依赖列表中的全部文件

利用这几个变量,前面的 Makefile 可以写得更简洁:hello.o: hello.c 这条规则的命令可以写成 gcc -c $< -o $@,不需要重复输入具体的文件名,减少改动文件名时需要同步修改多处的麻烦。

这个主题是构建可靠 Linux 开发流程的重要组成部分。对它有清晰的理解,会让后续任务变得更容易,因为你能更快地判断某一步是否缺失或使用不当。

  • 在安全的测试环境中尝试这里展示的命令和配置。
  • 比较这些概念在不同发行版或工具之间的适用差异。
  • 记下一次实践中哪些步骤成功,哪些失败,这样下次遇到问题时更容易定位。
  • 将这篇文章与系列中相关内容结合起来,加深整体理解。
  • 跳过验证步骤,默认系统已经配置正确。
  • 不对命令中的路径、软件包名称或工具版本进行适配,直接复制粘贴执行。
  • 把这个主题当成孤立的技巧,而不是整个开发流程的一部分。