2、Makefile核心规则:目标、依赖、命令的语法、规则格式详解、伪目标.PHONY
2.1 一条规则,三个要素
Makefile 的规则,说白了就是三样东西:目标(target)、依赖(prerequisites)、命令(recipe)。
我刚开始学 Makefile 时,总觉得这东西很玄乎。后来发现,它就是个「如果依赖变了,就重新生成目标」的简单逻辑。你想想看,这不就是我们日常编译代码的流程吗?
规则的标准格式长这样:
目标: 依赖1 依赖2 依赖3
命令1
命令2
注意了,命令前面必须是一个 Tab 键,不是空格。我见过太多新手在这里栽跟头——用空格缩进,然后 make 报错「missing separator」。嗯,这个坑我当年也踩过。
2.2 目标:你想生成什么
目标通常是一个文件名,比如 main.o、app。但也可以是伪目标,比如 clean、all。
Make 会检查目标文件是否存在。如果不存在,就执行命令去生成它。如果存在,就检查依赖有没有更新——依赖比目标新,那就重新生成。
举个例子:
main.o: main.c
gcc -c main.c -o main.o
这里 main.o 是目标,main.c 是依赖。如果 main.c 被修改了,make 就会重新编译 main.o。
all,让它依赖所有最终产物。这样执行 make 时,默认就会构建整个项目。
2.3 依赖:触发重建的条件
依赖可以是文件,也可以是其他目标。Make 会递归地检查所有依赖。
比如:
app: main.o utils.o
gcc main.o utils.o -o app
main.o: main.c
gcc -c main.c -o main.o
utils.o: utils.c utils.h
gcc -c utils.c -o utils.o
执行 make app 时,make 会先检查 main.o 和 utils.o 是否需要更新。如果 utils.h 改了,utils.o 就会重新编译,然后 app 也会重新链接。
这就是 Makefile 的「依赖链」——一层层往下推,直到找到需要重新编译的源头。
2.4 命令:如何生成目标
命令就是 shell 命令。每条命令都在独立的 shell 进程中执行。这一点很重要。
比如:
target:
cd /tmp
pwd
你以为会输出 /tmp?错了。因为 cd /tmp 在子 shell 里执行,不影响后面的 pwd。如果你想让它们在一个 shell 里执行,可以用 ; 或者 && 连接:
target:
cd /tmp && pwd
或者用反斜线换行:
target:
cd /tmp; \
pwd
&& 或者把多行命令写在一行里。
2.5 伪目标 .PHONY
伪目标,就是那些不代表实际文件的目标。最典型的就是 clean。
你想想看,如果当前目录下恰好有一个叫 clean 的文件,那 make clean 会怎样?
Make 会检查 clean 文件是否存在。如果存在,而且没有依赖,它就认为目标已经是最新的了——什么都不做!
这就是为什么我们需要 .PHONY:
.PHONY: clean
clean:
rm -f *.o app
声明为 .PHONY 后,make 就不会去检查文件是否存在,每次都老老实实执行命令。
常见的伪目标有:
all—— 构建所有目标clean—— 清理中间文件install—— 安装程序test—— 运行测试
.PHONY。这是 Makefile 的「最佳实践」,没有例外。
2.6 规则格式的细节
规则还有一些变体,我简单列一下:
| 格式 | 说明 | 示例 |
|---|---|---|
| 单目标多依赖 | 最常见的形式 | app: main.o utils.o |
| 多目标单规则 | 多个目标共享同一组命令 | a.o b.o: common.h |
| 无依赖规则 | 只有命令,没有依赖 | clean: rm -f *.o |
| 无命令规则 | 只声明依赖关系 | app: main.o |
多目标规则我用的不多,但偶尔有用。比如当多个 .o 文件都依赖同一个头文件时,可以写成一行:
main.o utils.o: config.h
这样 config.h 改了,两个 .o 都会重新编译。
2.7 知识体系总览
下面这张图,把本章的核心逻辑串起来了:
2.8 实战中的避坑指南
最后,分享几个我实际项目中遇到的教训:
- 不要用空格代替 Tab —— 这是 Makefile 最常见的错误。我建议你在编辑器里设置「显示空白字符」,一眼就能看出来。
- 伪目标一定要声明 —— 我见过有人没加
.PHONY,结果目录里多了个clean文件,make clean 再也不工作了。排查了半天才发现。 - 命令中的变量展开时机 —— 命令里的
$会被 make 解释。如果你想传$给 shell,得写成$$。比如echo $$HOME才能输出环境变量。
make -n。这个选项只会「模拟」执行,打印出要运行的命令,但不会真的运行。调试时特别好用。
好了,Makefile 的核心规则就这些。说白了就是「目标、依赖、命令」这三个东西,再加上伪目标这个特殊用法。你只要记住这个框架,后面学变量、函数、条件判断都会轻松很多。