7、Makefile 入门:Makefile 基本语法、目标与依赖、变量使用、伪目标 .PHONY
说实话,很多C语言开发者写代码一把好手,但一提到Makefile就头疼。我当年刚入行时也是这样,总觉得有IDE就够了,干嘛要学这个?直到有一次参与一个大型嵌入式项目,几十个源文件手动编译,改一个文件就要全部重新编译,等得我怀疑人生。嗯,从那以后,我老老实实把Makefile啃了下来。
今天我们就来聊聊Makefile的核心内容。别怕,其实它没那么复杂。
7.1 Makefile 是什么?
Makefile 说白了就是一个自动化编译脚本。它告诉 make 工具:哪些文件需要编译、用什么命令编译、文件之间有什么依赖关系。
你想想看,一个项目少则几十个文件,多则成百上千。如果每次改一行代码都要手动敲 gcc 命令,那效率也太低了。Makefile 就是来解决这个问题的。
核心思想: Makefile 通过比较目标文件和依赖文件的时间戳,决定哪些文件需要重新编译。只有发生变化的文件才会被重新编译,大大节省时间。
7.2 基本语法:目标与依赖
Makefile 的基本规则其实就一句话:
目标(target): 依赖(dependencies)
命令(command)
我来解释一下:
- 目标:通常是要生成的文件名,比如 main.o、app
- 依赖:生成目标所需要的文件
- 命令:如何从依赖生成目标,注意前面必须是一个 Tab 键,不能是空格
举个最简单的例子:
app: main.c utils.c
gcc -o app main.c utils.c
这个 Makefile 的意思是:要生成 app 这个可执行文件,需要 main.c 和 utils.c 这两个源文件。如果它们中任何一个有修改,就执行 gcc 命令重新编译。
注意: 命令前面的缩进必须是 Tab,不能是空格。这是新手最容易犯的错误,我曾经因为这个排查了半小时,最后发现是编辑器把 Tab 自动转成了空格。
7.3 变量使用:让 Makefile 更灵活
硬编码文件名和编译器选项,显然不够优雅。我在项目中习惯用变量来管理这些信息,这样修改起来方便多了。
Makefile 中定义变量很简单:
CC = gcc
CFLAGS = -Wall -O2
TARGET = app
SRCS = main.c utils.c
$(TARGET): $(SRCS)
$(CC) $(CFLAGS) -o $(TARGET) $(SRCS)
使用变量时用 $(变量名) 来引用。这样做的好处很明显:
- 想换编译器?改 CC 变量就行
- 想加编译选项?改 CFLAGS 变量就行
- 想增加源文件?改 SRCS 变量就行
我个人习惯把常用的变量放在 Makefile 开头,一目了然。
7.4 伪目标 .PHONY
这个知识点,我建议你重点理解。伪目标是什么?
先看一个场景:我们经常会在 Makefile 里写一个 clean 目标,用来清理编译产物:
clean:
rm -f *.o app
然后执行 make clean 就能清理文件。看起来没问题对吧?
但假如你的项目目录里恰好有一个叫 clean 的文件呢?
这时候 make 会认为 clean 这个目标已经是最新的了(因为 clean 文件存在且没有依赖),于是就不执行任何命令。这就是个大坑!
解决方案: 使用 .PHONY 声明伪目标,告诉 make 这个目标不代表实际文件,每次都执行命令。
.PHONY: clean
clean:
rm -f *.o app
常见的伪目标还有:
- all:默认目标,通常放在第一个
- clean:清理编译产物
- install:安装程序
- test:运行测试
我曾经在一个项目中忘记加 .PHONY,结果同事在目录下创建了一个叫 clean 的日志文件,导致整个团队的清理脚本都失效了。排查了半天才发现是这个问题。
7.5 一个完整的示例
把上面的知识点串起来,一个典型的 Makefile 长这样:
CC = gcc
CFLAGS = -Wall -O2 -I./include
LDFLAGS = -lm
TARGET = app
SRCS = main.c utils.c sensor.c
OBJS = $(SRCS:.c=.o)
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(LDFLAGS) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
clean:
rm -f $(OBJS) $(TARGET)
这里有几个新东西我解释一下:
$@:表示当前目标的名字$^:表示所有依赖文件$<:表示第一个依赖文件%.o: %.c:模式规则,意思是所有 .o 文件都由对应的 .c 文件编译生成
这些自动化变量能省不少事,我建议你记住它们。
7.6 知识体系图
下面这张图帮你梳理 Makefile 的核心知识点:
7.7 避坑指南
最后分享几个我踩过的坑:
- Tab 和空格:Makefile 的命令行必须用 Tab 缩进,用空格会报错。我建议你在编辑器里设置显示不可见字符,一眼就能看出来。
- .PHONY 别忘了:只要目标不代表实际文件,就加上 .PHONY 声明。养成习惯,避免踩坑。
- 变量引用用括号:单字符变量可以省略括号,比如
$@,但多字符变量必须加括号,比如$(CC)。我统一都加括号,省得记混。 - 命令执行失败:默认情况下,如果某条命令执行失败,make 会停止。可以在命令前加
-忽略错误,比如-rm -f *.o。
小技巧: 调试 Makefile 时可以用 make -p 查看所有变量和规则的展开结果,非常有用。
好了,Makefile 入门就讲到这里。记住,写 Makefile 的核心思路就是:告诉 make 你的项目结构,让它帮你做重复的编译工作。多用变量,少写死路径,加上 .PHONY 保平安。这些习惯养成了,你的项目构建就会越来越顺手。
公众号:蓝海资料掘金营,微信deep3321