make 命令概览

make 命令可以自动确认一个大型工程中需要重新编译的源码部分,然后执行命令来进行重新编译。

make 命令通常被用于 C 语言程序,但实际上它可以被用于任何支持使用 shell 命令来编译的编程语言。事实上,make 命令甚至不被受限于编程领域,它还可以被用于来执行这样类型的任务:当一个文件被更改时,需要自动更新其它文件。

makefile 介绍

make 命令通过一个 makefile 文件来得知具体的执行步骤,通常 makefile 文件会告知 make 命令如何具体地执行编译和链接一个程序。

rule 介绍

一个简单的 makefile 文件中会包含一些 rule,格式如下:

  1. target ... : prerequisites ...
  2. [tab] recipe
  3. [tab] ...
  4. [tab] ...
  • target 可以是由程序生成的文件名称,例如一个 executable 文件或者 object 文件的名称。target 也可以是一个待执行的动作名称,例如 clean;
  • prerequisite 是在创建 target 时的输入文件,一个 target 通常会依赖多个文件;
  • recipe 是 make 命令要具体执行的动作,一个 recipe 可以有多个命令,可以位于一行或者多行。注意,每个 recipe 前面需要加上一个制表符(可以通过 .RECIPEPREFIX 变量来修改这个特殊符号)。

通常来说,recipe 会位于带有 prerequisite 的 rule 中,在 prerequisite 更改时会被用于创建 target。然而,指定了 recipe 的 rule 其实不必非得需要配置 prerequisite。例如,一个用于删除操作的名为 clean 的 target 就不需要配置 prerequisite

使用 make 命令来处理 makefile

默认情况下,make 命令会读取当前目录中的 makefile 文件,然后处理 makefile 文件中的第一个 rule。

在 make 命令可以完全处理某个 rule 之前,会先处理这个 rule 所依赖的 prerequisite

编写 makefile

makefile 文件内容

makefile 文件中包含了五种类型的内容:显式 rule、隐式 rule、变量定义、指令和注释。

  • 显式 rule 说明了何时以及如何重新 make 一个或多个文件,这些文件被称为 rule 的 target。显式 rule 会列出 target 所依赖的一些输入文件,这些文件被称为 prerequisite,同时也会给定一些用于创建或者更新 targetrecipe
  • 隐式 rule 说明了何时以及如何基于文件名称来重新 make 一个或多个文件;
  • 变量定义是将一个文本字符串值指定为一个变量的一行,变量在后续内容中可以被替换为文本字符串;
  • 指令是使 make 命令在读取 makefile 时所执行的一些特殊操作的指令,包括了:
    • 读取其它 makefile 文件;
    • 根据变量的值来决定使用或者忽略部分 makefile 内容;
    • 将包含多行的字符串指定为一个变量;
  • 在 makefile 中的注释以 # 开头。

makefile 文件名称

默认情况下,make 命令会按以下名称顺序来查找 makefile 文件:GNUmakefilemakefileMakefile

通常会将 makefile 文件命名为 makefile 或者 Makefile。(更加推荐后者的方式,因为在列出目录内容时,Makefile 会像 README 一样出现在列表中更靠前的位置。)

可以通过 -f 或者 --file 选项来指定具体的 makefile 文件。

编写 rule

rule 语法

通常来说,rule 的格式如下:

  1. targets : prerequisites
  2. [tab] recipe
  3. [tab] ...

或者如下:

  1. targets : prerequisites ; recipe
  2. [tab] recipe
  3. [tab] ...

targets 是文件名称,以空格分隔,可以使用通配符。通常只会有一个 target,但偶尔也会因为某些原因而有多个 target

recipe 需要以一个制表符为开头。第一个 recipe 可以位于 prerequisite 的下一行,也可以位于 prerequisite 的同一行(需要使用分号来分隔)。

由于 $ 符号用于标记变量引用的开始,因此需要使用 $$ 来代表实际的 $ 符号。

一个 rule 会告知 make 命令两件事情:target 何时会过期,以及如何在必要时候更新 target

  • target 的过期是根据 prerequisite 来判断的,prerequisite 是一组以空格分隔的文件名称。当 target 不存在或者早于任何一个 prerequisite 文件时(通过比较文件的 last-modification 时间),这个 target 就是已经过期了的;
  • target 的更新是通过 recipe 来实现的,recipe 是一行或者多行可以执行的 shell 脚本。

在文件名称中使用通配符

一个文件名可以使用通配符来指定多个文件。

在 make 命令中可以使用的通配符是 *?[...]~ 则代表用户目录,这些符号的用法和在 Bourne shell 中的用法类似。

phony target

phony target 并不是一个真实文件的名称,它只是通过明确的 make 命令(例如 make clean)来代表一组待执行的 recipe 的名称。

以如下的 rule 为例,这个 rule 只会简单地执行 rm *.o temp 命令,不会创建 target 对应的名为 clean 的文件,同时由于在工程中是大概率不会存在名为 clean 的文件,因此在每次调用 make clean 命令时,rule 中的 recipe (即 rm *.o temp 命令)都会被执行。

  1. clean:
  2. [tab] rm *.o temp

但是如果工程中恰好存在名为 clean 的文件的话,此时由于这个 rule 不存在 prerequisite,同时 target 对应的名为 clean 的文件始终会被认为是最新的(即没有被更改过),因此 make clean 命令便不会再被执行。

为了避免上述的问题,可以显式地将这个 target 声明为 phony 的。

  1. .PHONY: clean
  2. clean:
  3. [tab] rm *.o temp

编写 rule 中的 recipe

recipe 语法

makefile 文件中的大部分内容是使用 make 命令的语法,然而 recipe 是由 shell 来解释执行的,因此在 recipe 中使用的是 shell 语法。make 命令不会尝试去解释 shell 语法,它仅会在将 recipe 传递给 shell 执行之前,执行一些少量的特定翻译工作。

除了和 targetprerequisite 位于同一行的特殊 recipe,其它的每行 recipe 必须以制表符开头(可以通过 .RECIPEPREFIX 变量来修改这个特殊符号)。

recipe 回声

通常来说,make 命令在执行每行 recipe 之前会打印它,这被称为回声(echoing)。

当行是以 @ 为开头时,该行的 recipe 的回声功能会被禁用。make 命令将 recipe 传递给 shell 之前,会删除 @

可以通过 -n 或者 --just-print 选项来使 make 命令仅打印而不会执行 recipe

可以通过 -s 或者 --silent 选项来使 make 命令禁用所有 recipe 的回声功能。

recipe 执行

除了在 .ONESHELL 这个特殊的 target 生效的时候,每行 recipe 都会在一个子 shell 环境中执行。

注意,这也意味着定义 shell 变量和调用类似 cd 之类的 shell 命令——这种为每个进程设置本地上下文的操作不会对下一行 recipe 生效。如果期望 cd 命令对下一行 recipe 生效,则可以将两行 recipe 的 shell 语句合并到同一行中。

参考资料