makefile 文件是 Linux 和类 Unix 开发环境里面专门配合 make 命令使用的文本配置文件,核心作用是记录源码编译的依赖关系编译规则和执行命令,让 make 工具可以自动判断哪些源文件发生修改,只重新编译变动部分,不用每次手动输入一长串 gcc 编译命令。之所以需要这个文件,是因为大型项目源码文件数量很多,源文件头文件库文件之间互相依赖,如果每次全部重新编译会耗费大量时间,makefile 把整套构建逻辑写清楚之后,make 读取这个文件就能按照规则自动执行编译链接清理等操作,提升项目构建效率。使用的时候第一步在项目根目录创建名称为 Makefile 或者 makefile 的纯文本文件,第二步在文件内编写目标依赖项和对应的命令,第三步在终端执行 make 命令,make 会自动查找当前目录下的 makefile 文件并解析执行,如果需要指定目标可以写成 make 目标名。理解这个文件的时候要抓住三个核心要素,也就是目标依赖文件执行命令,所有规则都是围绕这三部分组织,这也是 makefile 能够实现增量编译的根本原理。
基础核心概念
目标 一般是最终生成的可执行程序或者中间目标文件,也可以是伪目标比如 clean
依赖项 生成这个目标所需要的源文件头文件库文件或者其他已经生成的目标文件
命令 当依赖项相比目标更新的时候,make 需要执行的 shell 命令,注意命令行前面必须使用 Tab 键缩进,不能是普通空格,这是新手最常见报错原因
基础语法结构对照表
| 组成部分 | 说明 | 示例写法 |
|---|---|---|
| 规则行 | 目标 依赖 | app main.o func.o |
| 命令行 | Tab 缩进后的 shell 指令 | gcc main.o func.o o app |
| 变量 | 提前定义编译器参数源文件列表 | CC = gcc |
| 伪目标 | .PHONY 标记,避免和同名真实文件冲突 | .PHONY clean |
增量编译原理讲解
make 在读取 makefile 文件之后,会对比目标文件和所有依赖文件的修改时间,如果依赖文件的修改时间晚于目标文件,就判定目标已经过期,需要执行对应的编译命令。如果依赖没有更新,make 就会跳过这条规则,不会重复编译。这个机制就是增量编译,项目越大收益越明显,比如几百个 c 文件的工程,只修改其中一个 c 文件,就只编译这一个文件再重新链接,不用全部重编。
常用高级特性
变量定义 可以自定义变量保存编译器编译选项源文件集合,方便统一修改,比如 CFLAGS 保存编译警告优化参数
自动变量 比如< 代表第一个依赖文件,$^ 代表全部依赖文件,减少重复书写
模式规则 使用通配符批量定义同类编译规则,比如.o 依赖.c,自动匹配所有 c 文件生成对应 o 文件
伪目标 典型 clean 目标,用来删除编译生成的 o 文件和可执行程序,这个目标本身不会生成同名文件,所以要用.PHONY 声明,防止目录里刚好存在 clean 文件,导致 make clean 失效
多目录项目 支持分目录编写子 makefile,顶层 makefile 可以调用子目录 makefile,实现大型工程分层构建
makefile 执行流程
首先 make 启动,查找当前目录的 makefile 文件,优先识别 Makefile 大写名称
解析文件里面所有变量规则和伪目标声明
按照依赖顺序递归检查时间戳,构建依赖树
按照依赖顺序执行过期目标对应的命令
全部目标完成之后退出,如果中途命令报错,make 默认停止后续构建
【常见问题】
问题 1 makefile 文件名称必须首字母大写吗
回答 1 makefile 文件不是强制要求大写,小写 makefile 也可以被 make 识别,但是 make 查找顺序优先读取大写 Makefile,行业规范一般推荐使用大写 Makefile 作为项目主文件。
问题 2 为什么编写 makefile 文件之后执行 make 提示缺少分隔符
回答 1 这个错误绝大多数是因为 makefile 文件里面命令行使用空格代替了 Tab 缩进,make 语法要求每一行编译命令前面必须是 Tab 制表符,改成 Tab 就可以解决。
问题 3 makefile 文件里面 clean 伪目标有什么作用
回答 1 makefile 文件里面 clean 伪目标用来执行清理操作,一般写删除所有中间 o 文件和最终可执行程序的命令,加上.PHONY 声明之后,就算目录存在同名 clean 文件,make clean 依然可以正常执行清理。
问题 4 makefile 文件可以实现增量编译是什么意思
回答 1 makefile 文件依靠时间戳对比实现增量编译,只重新编译发生改动的源文件,没有修改的文件保留旧的目标文件,大幅减少大型项目完整编译消耗的时间。
【免责申明】
本文为 makefile 文件基础技术讲解,不同版本 make 工具支持的扩展语法存在差异,部分高级函数和扩展特性属于 GNU make 专属,其他 make 实现不一定兼容,实际项目开发请结合当前环境版本验证语法,本文内容仅作学习参考。


