1. Makefile基础回顾:目标、依赖、规则、变量、自动变量
说实话,很多工程师写了几年Makefile,其实也就停留在「能编译」的水平。我见过不少项目,编译一次要等十分钟,结果一看Makefile,全是重复代码,变量乱用,依赖关系一塌糊涂。
这一章,咱们把基础打牢。别嫌简单,基础不牢,后面优化无从谈起。
1.1 目标与依赖:Makefile的核心骨架
Makefile最核心的东西,就三个词:目标(target)、依赖(prerequisites)、规则(recipe)。
说白了就是:我想生成什么?它依赖谁?怎么生成?
target: prerequisites
recipe
举个例子,我有个 main.c,想编译成 main.o:
main.o: main.c
gcc -c main.c -o main.o
这里 main.o 是目标,main.c 是依赖,gcc -c ... 就是规则。
关键点:Make 只会在「目标不存在」或「依赖比目标新」时,才执行规则。这是增量编译的基础。
我在项目中遇到过有人把所有源文件都写成一个目标,结果每次改一个文件都要全量编译。嗯,那酸爽,谁用谁知道。
1.2 规则:怎么写才规范?
规则有三种常见写法,我按推荐程度排个序:
| 类型 | 示例 | 说明 |
|---|---|---|
| 显式规则 | main.o: main.c |
最直接,但重复代码多 |
| 模式规则 | %.o: %.c |
用通配符匹配,省事 |
| 静态模式规则 | $(OBJS): %.o: %.c |
只对指定列表生效,更安全 |
我个人习惯用静态模式规则。为什么?因为显式规则太啰嗦,模式规则又可能误匹配。静态模式规则刚好卡在中间——既简洁,又可控。
小技巧:规则里的命令前必须是一个 Tab 键,不是空格。我见过有人因为这个错误排查了一下午。
1.3 变量:别写死,别重复
没有变量的Makefile,就像没有函数的C语言——全是重复代码。
变量定义很简单:
CC = gcc
CFLAGS = -Wall -O2
TARGET = myapp
$(TARGET): main.o utils.o
$(CC) $(CFLAGS) -o $@ $^
变量赋值有几种方式,我列个表:
| 赋值方式 | 语法 | 特点 |
|---|---|---|
| 简单赋值 | := |
立即展开,定义时确定值 |
| 递归赋值 | = |
使用时才展开,可能无限递归 |
| 条件赋值 | ?= |
只有未定义时才赋值 |
| 追加赋值 | += |
在原有值后面追加 |
我曾经在项目里用 = 定义了一个变量,结果它引用了另一个变量,那个变量又引用了它……嗯,Make 直接报错说检测到递归。从那以后,我默认都用 :=,除非有特殊需求。
注意:递归赋值 = 虽然灵活,但容易出问题。建议在99%的场景下使用 :=。
1.4 自动变量:少写重复代码的利器
自动变量是什么?说白了就是Make帮你预定义好的「快捷方式」。你想想看,每次写规则都要写目标名、依赖名,多烦?自动变量就是来解决这个问题的。
最常用的几个:
$@—— 目标文件名$<—— 第一个依赖文件名$^—— 所有依赖文件名(去重)$+—— 所有依赖文件名(不去重)$*—— 模式匹配的「茎」(stem)
看个例子就明白了:
%.o: %.c
gcc -c $< -o $@
myapp: main.o utils.o
gcc -o $@ $^
这里 $< 自动变成 main.c 或 utils.c,$@ 自动变成 main.o 或 utils.o。最后链接时,$^ 自动展开成 main.o utils.o。
是不是很爽?
核心思想:自动变量 + 模式规则 = 写一次规则,处理所有文件。这是Makefile从「能用」到「好用」的第一步。
1.5 知识体系总览
下面这张图,把本章的核心逻辑串起来了:
这张图把Makefile拆成了四个维度。你写任何一个Makefile,都逃不出这个框架。目标决定「做什么」,依赖决定「什么时候做」,规则决定「怎么做」,变量决定「怎么写得少」。四者缺一不可。
1.6 避坑指南
最后,分享几个我踩过的坑:
- Tab 和空格混用:Make 只认 Tab。我曾经从网页复制了一段Makefile,结果全是空格,编译报错「missing separator」。排查了半小时。
- 变量名冲突:别用
CC、CFLAGS这些内置变量做其他用途。我见过有人把CC定义成clean,结果编译时调用了clean命令…… - 伪目标忘了声明:如果目录下有个叫
clean的文件,而你的clean目标没加.PHONY,Make 会认为它已经是最新的,永远不会执行清理。
我的建议:刚开始写Makefile,先写一个能编译的版本。然后逐步引入变量、模式规则、自动变量。别想一步到位,Makefile 是迭代出来的。
好了,基础就这些。记住:目标、依赖、规则、变量、自动变量,这五个东西搞明白了,后面讲性能优化你才能跟上。