make 命令概览
make 命令可以自动确认一个大型工程中需要重新编译的源码部分,然后执行命令来进行重新编译。
make 命令通常被用于 C 语言程序,但实际上它可以被用于任何支持使用 shell 命令来编译的编程语言。事实上,make 命令甚至不被受限于编程领域,它还可以被用于来执行这样类型的任务:当一个文件被更改时,需要自动更新其它文件。
makefile 介绍
make 命令通过一个 makefile 文件来得知具体的执行步骤,通常 makefile 文件会告知 make 命令如何具体地执行编译和链接一个程序。
rule 介绍
一个简单的 makefile 文件中会包含一些 rule,格式如下:
target ... : prerequisites ...
[tab] recipe
[tab] ...
[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
,同时也会给定一些用于创建或者更新target
的recipe
; - 隐式 rule 说明了何时以及如何基于文件名称来重新 make 一个或多个文件;
- 变量定义是将一个文本字符串值指定为一个变量的一行,变量在后续内容中可以被替换为文本字符串;
- 指令是使 make 命令在读取 makefile 时所执行的一些特殊操作的指令,包括了:
- 读取其它 makefile 文件;
- 根据变量的值来决定使用或者忽略部分 makefile 内容;
- 将包含多行的字符串指定为一个变量;
- 在 makefile 中的注释以
#
开头。
makefile 文件名称
默认情况下,make 命令会按以下名称顺序来查找 makefile 文件:GNUmakefile
、makefile
、Makefile
。
通常会将 makefile 文件命名为 makefile
或者 Makefile
。(更加推荐后者的方式,因为在列出目录内容时,Makefile
会像 README
一样出现在列表中更靠前的位置。)
可以通过 -f
或者 --file
选项来指定具体的 makefile 文件。
编写 rule
rule 语法
通常来说,rule 的格式如下:
targets : prerequisites
[tab] recipe
[tab] ...
或者如下:
targets : prerequisites ; recipe
[tab] recipe
[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
命令)都会被执行。
clean:
[tab] rm *.o temp
但是如果工程中恰好存在名为 clean 的文件的话,此时由于这个 rule 不存在 prerequisite
,同时 target
对应的名为 clean 的文件始终会被认为是最新的(即没有被更改过),因此 make clean 命令便不会再被执行。
为了避免上述的问题,可以显式地将这个 target
声明为 phony 的。
.PHONY: clean
clean:
[tab] rm *.o temp
编写 rule 中的 recipe
recipe 语法
makefile 文件中的大部分内容是使用 make 命令的语法,然而 recipe
是由 shell 来解释执行的,因此在 recipe
中使用的是 shell 语法。make 命令不会尝试去解释 shell 语法,它仅会在将 recipe
传递给 shell 执行之前,执行一些少量的特定翻译工作。
除了和 target
和 prerequisite
位于同一行的特殊 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 语句合并到同一行中。