本文裁剪汇总自Makefile教程和示例指南。
Makefile 意义
Makefile 用于帮助决定大型程序的哪些部分需要重新编译。在绝大多数情况下,都会编译 C 或 C++ 文件。其他语言通常有自己的与 Make 功能类似的工具。Make 也可以在编译之外使用,即当需要根据已更改的文件运行一系列指令时。本教程将重点介绍 C/C++ 编译用例。
下面是可以使用 Make 构建的依赖关系图示例。如果任何文件的依赖项发生更改,则该文件将被重新编译:
Make 的替代
流行的 C/C++ 替代构建系统有 SCons、CMake、Bazel 和 Ninja。一些代码编辑器(例如 Microsoft Visual Studio)有自己的内置构建工具。对于 Java,有 Ant、Maven 和 Gradle。其他语言(例如 Go、Rust 和 TypeScript)都有自己的构建工具。
Python、Ruby 和 raw Javascript 等解释性语言不需要与 Makefile 类似的东西。 Makefile 的目标是根据已更改的文件来编译需要编译的任何文件。但是,当解释语言中的文件发生更改时,不需要重新编译任何内容。程序运行时,将使用该文件的最新版本。
Make 的版本和类型
Make 有多种实现,但本指南的大部分内容都适用于任一版本。然而,它是专门为 GNU Make 编写的,GNU Make 是 Linux 和 MacOS 上的标准实现。所有示例都适用于 Make 版本 3 和 4,除了一些深奥的差异之外,它们几乎相同。
Make 基本运行
运行首先需要一个终端,并安装 make。对每一个项目,需包含在一个名为Makefile的文件(无后缀)中,并在同级目录下运行命令make。
最简单的 Makefile:
hello:echo "Hello, World"
 
Makefile 必须使用 TAB 键进行缩进,其中不能有空格。
运行如下:
$ make
echo "Hello, World"
Hello, World
 
Makefile 语法
Makefile 的主要部分由规则组成,可有多条规则。
一条规则的基本语法:
targets: prerequisitescommandcommand...
 
- 目标(target),每条规则可有多个,以空格分隔,但一般只有一个,且为文件名(
make将其视为文件名); - 命令(command),一系列步骤,一般用于创建目标文件,开始的缩进必须是tab而非空格;
 - 先决条件(prerequisite),又称依赖(dependency),为文件名,可有多个,以空格分隔,在运行命令前需要存在的文件。在执行规则时,
prerequisites中的每个prerequisite均需满足以下至少一点:- 当前目录下存在同名文件;
 Makefile中存在以此名为target的规则。
 
Makefile 精髓
-  
运行时,在终端输入
make <target>,然后make会执行该target对应的规则(如果同一目标有多条规则,则会警告,并执行最后一条规则);若在终端输入make,则默认执行第一条规则; -  
当在终端输入
make [<target>]时,make对该target对应规则中的每一个prerequisite进行判断:Makefile中是否存在以其为名的target,若有,则先对对应规则进行递归执行;如:
test1: test2echo "test1" > test1 test2: echo "test2" > test2在该例子中,
test1的内容其实与test2无关,所以这个依赖关系只是对make而言的。当执行
make test1或make时,make实质上会先执行make test2 -  
执行规则时,
make会先判断是否要执行命令,需满足以下条件的至少一点才会执行:- 以
target为名的文件不存在; - 存在比
target更新的prerequisite;(prerequisite比target更新,即prerequisite文件内容的修改时间晚于target文件;make会记录并更新该目录下每个文件被更新的时间戳) 
根据第一点,可以构造一种特殊规则,该规则不会创建以
target为名的文件,因而一般情况下在输入make <target>时总是会被执行。常见的有clean和all等。(显然,若该目录下手动创建了对应名称的文件,则该构造就失效了,对应解决方案见后文)例如:若
Makefile内容如下:test1: test2echo "test1" test2: test3echo "test2"-  
若开始时目录下只有该
Makefile文件,然后手动依次创建test3、test2、test1文件(创建文件也是一种手动更新),再执行make,则会显示:$ make make: 'test1' is up to date.即没有规则被执行。
分析如下:
-  
执行
make时,由于未指定target,故默认选择第一个规则,即执行make test1; -  
执行
make test1时,先递归执行其prerequisite,即先执行make test2; -  
对
make test2,进行判断,发现:- 该目录下存在
test2文件; - 其先决条件
test3的更新时间早于test2(这是因为创建时间test3早于test2); 
故不执行对应规则;
 - 该目录下存在
 -  
递归回到
make test1,进行判断,发现:- 该目录下存在
test1文件; - 其先决条件
test2的更新时间早于test1(这是因为创建时间test2早于test1); 
故不执行对应规则;
 - 该目录下存在
 
 -  
 -  
换一种情况,若开始时目录下只有该
Makefile文件,然后手动依次创建test2、test3、test1文件(即调换test2与test3的创建顺序),再执行make,则会显示:$ make echo "test2" test2即
test2对应规则被执行了,test1对应规则未被执行。 
 - 以
 
make clean
clean通常用作删除其他目标的输出的目标,但它在make中并不是一个专有的词。
注:
- 一般而言,
clean不是第一个目标 (默认目标),也不是先决条件。 这意味着除非显式调用make clean,否则它永远不会运行; - 如“Makefile 精髓”中所述,
clean不是一个文件名。 如果该目录下有一个名为 “clean” 的文件,则此目标将无法运行。 解决方案参阅后续教程。 
例:
some_file: touch some_fileclean:rm -f some_file
 
变量
变量只能是字符串,一般通过:=赋值(使用= 也可以,具体见后续部分)。
使用$()或${}引用变量(单字母名称变量也可以不用括号,但不推荐)。
例:
files := file1 file2
some_file: $(files)echo "Look at this variable: " $(files)touch some_filefile1:touch file1
file2:touch file2clean:rm -f file1 file2 some_file