自动化构建 - make / Makefile
背景
-
工程中的源文件不计其数,按类型、功能、模块分别放在若干个目录中。Makefile 定义了一系列规则来指定:
- 哪些文件需要先编译
- 哪些文件需要后编译
- 哪些文件需要重新编译
- 甚至进行更复杂的功能操作
-
Makefile 带来的好处就是------"自动化编译" 。一旦写好,只需要一个
make命令,整个工程完全自动编译,极大地提高了软件开发的效率。 -
make 是一个命令工具,是一个解释 Makefile 中指令的命令工具。一般来说,大多数的 IDE 都有这个命令,例如:
- Delphi 的 make
- Visual C++ 的 nmake
- Linux 下 GNU 的 make
-
核心关系:
make是一条命令Makefile是一个文件- 两者搭配使用,完成项目自动化构建
Makefile 示例与详解
完整 Makefile 代码
makefile
SRC = $(wildcard *.c)
OBJ = $(SRC:.c=.o)
BIN = process.exe
CC = gcc
$(BIN): $(OBJ)
@gcc -o $@ $^ -g
@echo "link $^ and $@ "
%.o: %.c
@gcc -c $< -g
@echo "compline $< to $@ "
.PHONY: clean test
clean:
rm -f $(OBJ) $(BIN)
test:
@echo "OBJ:$(OBJ)"
@echo "BIN:$(BIN)"
变量定义说明
| 变量 | 说明 |
|---|---|
SRC = $(wildcard *.c) |
使用 wildcard 函数获取当前目录下所有 .c 文件 |
OBJ = $(SRC:.c=.o) |
将 SRC 中所有 .c 后缀替换为 .o,得到目标文件列表 |
BIN = process.exe |
定义最终生成的可执行文件名 |
CC = gcc |
指定编译器为 gcc |
规则详解
主规则:生成可执行文件
makefile
$(BIN): $(OBJ)
@gcc -o $@ $^ -g
@echo "link $^ and $@ "
| 符号 | 含义 |
|---|---|
$@ |
目标文件(即 $(BIN),也就是 process.exe) |
$^ |
所有依赖文件(即 $(OBJ),所有 .o 文件) |
-g |
生成调试信息,供 gdb 使用 |
@(行首) |
静默符号,执行时不显示该命令本身 |
模式规则:编译 .c 为 .o
makefile
%.o: %.c
@gcc -c $< -g
@echo "compline $< to $@ "
| 符号 | 含义 |
|---|---|
% |
通配符,匹配任意相同的前缀 |
$< |
第一个依赖文件(即对应的 .c 文件) |
-c |
只编译不链接,生成目标文件 |
说明 :所有同名 .o 文件依赖于同名的 .c 文件,逐个编译成对应的 .o 文件。
伪目标
makefile
.PHONY: clean test
声明 clean 和 test 为伪目标,防止与同名文件冲突。如果使用make clean/test时,只会在makefile中寻找代码,而不是在当前目录下寻找同名文件
clean - 清理编译产物
makefile
clean:
rm -f $(OBJ) $(BIN)
- 删除所有
.o文件和可执行文件
test - 调试变量
makefile
test:
@echo "OBJ:$(OBJ)"
@echo "BIN:$(BIN)"
- 打印
OBJ和BIN变量的值,用于调试 Makefile
自动化变量速查表
| 变量 | 含义 |
|---|---|
$@ |
当前规则的目标文件 |
$^ |
当前规则的所有依赖文件 |
$< |
当前规则的第一个依赖文件 |
$? |
所有比目标新的依赖文件 |
$* |
目标文件名中 % 匹配的部分 |
执行 make 命令后的工作流程
假设当前目录下有 main.c 和 utils.c 两个源文件。
第 1 步:查找 Makefile 文件
make 在当前目录下查找名字为 Makefile 或 makefile 的文件,找到了当前这个文件。
第 2 步:确定最终目标
make 找到文件中的第一个目标文件 ,即 $(BIN) 展开后的 process.exe,将其作为最终目标。
makefile
$(BIN): $(OBJ) # 第一个目标,即 process.exe
第 3 步:检查是否需要重新生成
make 检查 process.exe 是否存在,以及它的依赖文件($(OBJ) 即 main.o 和 utils.o)是否比它更新(通过文件的时间属性来判断)。
| 情况 | make 的行为 |
|---|---|
process.exe 不存在 |
需要生成 |
某个 .o 文件比 process.exe 新 |
需要重新链接 |
process.exe 存在且比所有 .o 文件都新 |
什么都不做 |
假设 process.exe 不存在,make 将执行链接命令:
bash
gcc -o process.exe main.o utils.o -g
echo "link main.o utils.o and process.exe "
第 4 步:递归处理依赖(关键步骤)
在执行链接之前,make 检查依赖文件 main.o 和 utils.o 是否存在:
- 如果
.o文件不存在 ,或者.o文件比对应的.c文件旧,make 会查找生成该.o文件的规则
make 找到了模式规则:
makefile
%.o: %.c # 例如 main.o 依赖 main.c,utils.o 依赖 utils.c
这类似于一个堆栈过程:
process.exe 依赖 main.o 和 utils.o
↓
main.o 不存在,查找生成 main.o 的规则
↓
找到 %.o: %.c,执行 gcc -c main.c -g
↓
utils.o 不存在,查找生成 utils.o 的规则
↓
找到 %.o: %.c,执行 gcc -c utils.c -g
↓
所有 .o 文件生成完毕,返回上一层
↓
执行链接,生成 process.exe
第 5 步:编译源文件
make 执行编译命令,生成 .o 文件:
bash
gcc -c main.c -g
echo "compline main.c to main.o "
gcc -c utils.c -g
echo "compline utils.c to utils.o "
第 6 步:完成最终目标
所有依赖准备就绪,make 执行链接命令生成 process.exe。
第 7 步:依赖链结束
make 完成了第一个目标 process.exe 的生成,整个过程结束。
错误处理示例
| 错误场景 | make 的行为 |
|---|---|
某个 .c 文件不存在(如 main.c 被删除) |
找不到 main.c,直接退出并报错 |
编译命令写错了(如 gcc -c main.c -wrong) |
make 不管命令执行是否成功,只管执行命令 |
依赖关系缺失(如没有 %.o: %.c 规则) |
找不到生成 .o 的方法,直接退出并报错 |
核心总结
make 只管文件的依赖性:
- 它会一层一层地检查依赖关系
- 如果依赖关系链完整,就执行对应命令
- 如果某个依赖文件找不到且没有生成它的规则,make 就报错退出
- make 不关心命令执行是否成功,只负责执行命令
Makefile 设计的核心规则:最终目标是第一个目标,所以要把最终要生成的文件写在最前面。
其他命令示例
bash
make clean # 执行伪目标 clean,删除 .o 和 .exe 文件
make test # 执行伪目标 test,显示变量值
执行 make test 的输出示例:
OBJ:main.o utils.o
BIN:process.exe