Makefile
Makefile是make构建工具读取的脚本文件,用文本形式描述工程中各文件之间的依赖关系和生成命令。它通常由「目标、先决条件、命令」组成的规则构成,make据此比较文件修改时间,只重建过期的目标,从而实现增量构建。Makefile自1976年随make在贝尔实验室诞生后,成为Unix及类Unix系统上源代码构建的通行描述方式,也是GNU构建系统调用编译器的入口。
定义
Makefile是make程序读取的文本配置文件,其中记录构建某个软件工程所需的规则,说明哪些文件需要生成、由哪些文件生成、用什么命令生成。它的基本单元是规则,规则由目标(targets)、先决条件(prerequisites)和命令(command)3部分组成,目标与依赖之间以冒号分隔,命令另起一行且必须以制表符开头。
一个Makefile通常包含显式规则、隐含规则、变量定义、文件指示和注释5类内容;注释以「#」起始,行尾的「\」用于把长行续接到下一行[1]。除了显式书写的规则,make还内置了一批隐含规则,例如看到一个「.o」文件时会自动推导出对应的「.c」源文件和编译命令。
默认情况下,make依次查找名为GNUmakefile、makefile和Makefile的文件[2]。GNU官方的建议是优先使用「Makefile」,因为它在目录列表中位置靠前,便于和其他重要文件一起被看到;GNUmakefile则留给那些只兼容GNU make、其他make无法解析的文件使用[2]。需要换用其他文件名时,可以通过「-f」或「--file」选项指定,一次指定多个文件时,它们会按给出的顺序合并读取[2]。
原理
make依据文件的修改时间(mtime)决定是否重建目标。若目标文件不存在,或任一先决条件的修改时间晚于目标,就执行该规则的命令;若所有先决条件都比目标旧,则跳过并提示目标已是最新[3]。这种只更新受影响部分的机制即增量构建,也是Makefile能显著减少重复编译的基础[4]。
GNU make的处理分为两个阶段:第一阶段读入全部Makefile、展开变量与函数并建立依赖关系图;第二阶段据此判断哪些目标需要重新生成并调用相应规则[1]。依赖关系在逻辑上是一张有向无环图,make从默认目标出发递归检查其先决条件,逐层判断是否需要重建,最后才执行命令[5]。
规则中的命令由具体的shell解释执行,因此命令部分使用的是shell语法而非Makefile自身的语法[6]。变量用「$(名称)」或「${名称}」引用,常见约定是把编译器、编译选项写在CC、CFLAGS等变量中,以便在命令行临时覆盖[7]。make还预定义了若干自动变量,如「$@」表示目标文件名、「$<」表示第一个先决条件、「$^」表示全部先决条件,配合模式规则「%.o: %.c」可用一条规则处理一批同类文件[6]。
由于clean、all、install这类目标并不对应磁盘上的文件,规则执行后不会产生同名文件,目标因此永远处于「过期」状态,每次调用都会执行命令[5]。GNU make提供了「.PHONY」声明,用于显式标记这类伪目标,避免目录中恰好存在同名文件而导致命令被跳过[3]。
发展历程
make及其配套的Makefile由斯图尔特·费尔德曼于1976年4月在贝尔实验室编写完成[8]。据其本人回忆,同事史蒂夫·约翰逊曾因一个已修好却未重新编译的程序浪费了整个上午调试,而他自己前一晚也遭遇了同样情形,于是产生了用工具自动追踪依赖的想法,并在那个周末写出了初版[8]。1976年同月,他在贝尔实验室的语境中确立了用文本文件而非二进制格式记录构建规则的做法,理由是这样便于打印、调试和理解[8]。
make最早随PWB/UNIX 1.0进入Unix系统,凭借系统自带的依赖追踪能力迅速普及,Makefile也随之成为记录源码到可执行文件构建流程的通用格式[8]。此后Makefile的用途逐渐扩展到中间文件清理、符号交叉引用分析、源码格式化、测试、文档排版和打包归档等维护环节,而不再局限于编译[9]。2003年,费尔德曼因编写该工具获得美国计算机协会颁发的软件系统奖[8]。
早期Makefile难以适应不同平台的编译器名称和选项差异,为此出现了在构建前自动调整Makefile的configure脚本,典型用法演变为先运行configure再运行make[10]。这一流程后来在GNU项目中标准化,GNU编码规范要求每个软件包提供configure脚本和遵循约定的Makefile,Autoconf负责生成configure,Automake则侧重生成Makefile[10]。
应用
Makefile最常见的用法是描述C、C++等编译型语言工程的构建过程:把可执行文件设为目标,把若干目标文件列为先决条件,再逐条写出编译与链接命令。头文件被源文件包含时,还需额外声明目标文件对头文件的依赖,否则修改头文件不会触发重新编译;多数现代C编译器可用「-M」类选项自动生成这类依赖规则[11]。
在GNU构建系统中,用户在源码目录执行configure生成适配本机的Makefile,再执行make完成编译、make install完成安装,构成了类Unix平台上软件分发的通用操作流程[10]。大型工程常把不同子目录的构建拆分到各自的Makefile中,由上层Makefile递归调用;为避免路径与参数传递出错,递归时应使用「$(MAKE)」而不是直接写make[12]。
Makefile的适用面并不限于程序编译。凡是可以描述为「某些文件由另一些文件生成」的任务,都可借助它实现按需更新,例如文档转换、测试执行、数据生成和归档打包[8]。在算法竞赛等场景中,也有用Makefile统一管理题面拷贝、标程编译、数据生成与校验的实践[13]。
局限
Makefile的命令行必须用制表符(Tab)缩进,用空格代替会直接报「missing separator」错误。这一依赖空白字符的语法长期受到批评,费尔德曼本人解释,最初只是用「换行加制表符」这种简单模式快速实现,后因已经有一批早期用户而保留下来以维持向后兼容[8]。
增量判断只比较修改时间,因此存在固有偏差:仅改动编译选项(例如把优化级别从-O2改为-O3)而源码时间戳未变时,make不会触发重新编译。同理,时间戳变化并不必然意味着文件内容改变,版本控制切换分支、复制文件等操作造成的「误触发」也会引起不必要的重建[14]。此外,不同机器间的时钟漂移可能使时间比较失真[15]。
Makefile中的命令直接调用宿主系统的shell工具,跨平台能力依赖这些工具是否存在,在Windows等环境下通常需要额外适配。随着变量、条件、函数和递归调用等特性不断加入,Makefile逐渐复杂,学习与排查成本上升[9]。缺漏的先决条件不会报错,只会导致目标该重建而未重建,这类静默问题往往难以定位[4]。
参见
参考资料
- make [引用日期2026-10-29]
- Makefile-Names [引用日期2026-10-29]
- Linux C开发|彻底搞懂Makefile底层原理(从时间戳、伪目标到模式规则) [引用日期2026-10-29]
- ch02-make-build-automation [引用日期2026-10-29]
- Makefiles [引用日期2026-10-29]
- make [引用日期2026-10-29]
- Linux C基础:Makefile文件管理大师你拜访过吗? - gcc $(CFLAGS) main [引用日期2026-10-29]
- epfl.ch 上的网页 [引用日期2026-10-29]
- make による構築と検査 [引用日期2026-10-29]
- GNU-Build-System [引用日期2026-10-29]
- Automatic-Prerequisites [引用日期2026-10-29]
- Software Engineering on Unix [引用日期2026-10-29]
- 如何造题:Makefile is All You Need - │ ├── subtask-2/ [引用日期2026-10-29]
- Revision 7056516e-ff1c-49dd-ae40-0132a65f9712 [引用日期2026-10-29]
- File Dependencies: make [引用日期2026-10-29]