2. Makefile基础语法:规则的定义、变量赋值、注释、换行与续行
好,咱们正式开始动手写 Makefile 了。
这一章是地基中的地基。你想想看,盖房子如果地基没打好,后面装修得再漂亮也白搭。Makefile 的语法其实不复杂,但有几个细节特别容易踩坑。我当年刚入行时,就因为一个空格问题,排查了整整一个下午。
嗯,咱们一个一个来。
2.1 规则的定义:Makefile 的灵魂
Makefile 最核心的东西,就是「规则」。说白了,就是告诉 make 工具:你要生成什么文件,它依赖哪些文件,以及怎么生成它。
一条规则的格式长这样:
目标文件: 依赖文件列表
命令
注意了,命令前面那个 Tab 键,不是空格。这是新手最容易犯的错误。我曾经见过一个同事,把 Tab 换成了四个空格,结果 make 报错说「missing separator」。他盯着屏幕看了半小时,最后发现是这个问题。
来看个实际例子:
main.o: main.c
gcc -c main.c -o main.o
这条规则的意思是:main.o 依赖于 main.c。如果 main.c 比 main.o 新,或者 main.o 不存在,就执行下面的命令去编译它。
核心要点:
- 目标可以是一个文件,也可以是一个「伪目标」(比如
clean) - 依赖可以有多个,用空格隔开
- 命令前面必须是 Tab,不能是空格
- 命令可以有多条,每条一行
我个人的习惯是,把规则写得尽量清晰。哪怕多写几行注释,也比以后自己看不懂强。
2.2 变量赋值:四种方式,各有千秋
变量在 Makefile 里太常用了。你总不想每次用到编译器路径时都敲一遍 /usr/local/gcc-arm-none-eabi/bin/arm-none-eabi-gcc 吧?
Makefile 支持四种赋值方式,我分别说说它们的区别。
2.2.1 简单赋值 :=
这个我用的最多。:= 是「立即展开」的。什么意思呢?就是变量在定义时,右边的值就立刻被计算好了。
CC := gcc
CFLAGS := -Wall -O2
你之后再怎么改 CFLAGS,前面用 := 赋值的变量也不会受影响。这种确定性让我很放心。
2.2.2 递归赋值 =
= 是「延迟展开」的。变量在真正被用到的时候,才会去计算它的值。
CC = gcc
CFLAGS = $(CC) -Wall
如果你在后面改了 CC,那 CFLAGS 的值也会跟着变。这有时候是好事,有时候是灾难。我建议你除非有明确需求,否则尽量用 :=。
2.2.3 条件赋值 ?=
这个很实用。?= 的意思是:如果这个变量还没定义过,就给它赋值;如果已经定义了,就跳过。
CC ?= gcc
这常用于让用户可以在命令行覆盖默认值。比如:
make CC=clang
这样就会用 clang 而不是 gcc。我在做跨平台项目时经常用这个技巧。
2.2.4 追加赋值 +=
这个好理解,就是在原有值后面追加内容。
CFLAGS := -Wall
CFLAGS += -O2
# 现在 CFLAGS 的值是 "-Wall -O2"
注意,+= 会自动在中间加一个空格。这个细节我当年没注意,结果链接时死活找不到库文件路径,因为路径被粘在一起了。
我的建议:
- 默认用
:=,行为可预测 - 需要延迟求值时用
= - 允许用户覆盖时用
?= - 追加内容时用
+=
2.3 注释:用 # 说清楚你的意图
Makefile 的注释用 # 开头。从 # 到行尾的内容都会被忽略。
# 这是注释
CC := gcc # 这也是注释,跟在代码后面
我个人强烈建议:每个变量、每条规则,都写上注释。你想想看,三个月后的你,看到自己写的 Makefile,如果没有注释,你可能完全想不起来当初为什么要这么写。
我曾经在一个项目里看到这样的注释:
# 不要删掉下面这行,删了会编译不过
LDFLAGS += -lm
虽然有点搞笑,但至少说明了原因。比没有注释强一百倍。
2.4 换行与续行:长命令怎么优雅地写
有时候一条命令特别长,比如链接时要把几十个 .o 文件列出来。一行写不下怎么办?
用反斜杠 \ 来续行。
OBJS := main.o utils.o \
network.o database.o \
ui.o config.o
$(TARGET): $(OBJS)
gcc -o $@ $^ \
-L/usr/local/lib \
-lssl -lcrypto \
-lpthread
注意几个要点:
- 反斜杠后面不能有空格或任何字符,必须是行尾
- 续行后的内容,前面可以加空格或 Tab 来对齐,make 会忽略这些空白
- 续行在变量定义和命令中都可以用
踩坑提醒:
我曾经在反斜杠后面不小心加了一个空格,结果 make 把反斜杠当成了普通字符,整个变量定义就乱了。排查了半小时才发现。所以我的习惯是:写完反斜杠后,立刻回车,绝不犹豫。
2.5 知识体系总览
下面这张图,把本章的核心知识点串起来了。你可以把它当作一个快速索引。
2.6 实战小练习
光看不练可不行。我给你留个小任务:
- 创建一个 Makefile,定义三个变量:
CC、CFLAGS、TARGET - 写一条规则,把
hello.c编译成hello - 用
?=让用户可以在命令行覆盖编译器 - 加上注释,说明每个变量的用途
试试看,写完后用 make 跑一下。如果报错,先检查 Tab 键。
嗯,这一章的内容就这些。语法本身不难,难的是养成好习惯。记住:清晰的 Makefile,比聪明的 Makefile 更有价值。