make 是Linux及其他UNIX系统中常用的构建工具,主要用于自动化编译、管理大型项目中的文件依赖关系。它通过读取“Makefile”,根据文件的修改时间自动决定哪些文件需要重新编译,从而大大提高开发效率。
基本概念
- 作用:根据Makefile中的规则自动编译程序或执行其他任务
- 优势:
- 自动检测哪些源文件发生变化,只重新编译变化的部分
- 管理多个目标文件、库和依赖关系
- 简化复杂的构建过程
Makefile的结构
在项目目录下面创建Makefile
cd /path/to/你的项目目录
vim Makefile
或者用你喜欢的文本编辑器
nano Makefile
在项目目录下运行:
make
Makefile中定义了目标(目标文件或操作)、依赖关系和命令,格式为:
<目标>: <依赖1> <依赖2> ...
<命令1>
<命令2>
例:
# 编译所有目标
all: main
# 目标文件
main: main.o utils.o
gcc -o main main.o utils.o
#将两个目标文件(内容已经编译好)合成为一个完整的程序,可以直接运行。
# 依赖文件
main.o: main.c
gcc -c main.c
utils.o: utils.c
gcc -c utils.c
#编译utils.c生成utils.o
# 清理编译产生的文件
clean:
rm -f main *.o
注意:命令行必须以Tab键缩进!
类型 | 作用 | 例子 | 说明 |
---|---|---|---|
目标(目标文件) | 要“生成”的最终文件或中间文件 | main 、main.o 、utils.o | 经过make 规则处理得到的产物 |
源文件(源码文件) | 实现具体逻辑的源码文件 | main.c 、utils.c | 编写程序的原始数据 |
依赖文件 | 影响目标文件生成的文件 | main.c 、utils.h | 变化会引发目标重建 |
- 目标文件:你用make最终想要的文件(通常是可执行文件或目标文件)
- 源文件:你用来“编译”的代码文件(含实现代码)
- 依赖文件:影响目标的文件(源文件和头文件),如果它们修改了,目标就要重建。
常用用法
命令 | 作用 |
---|---|
make | 构建默认目标(第一个目标,通常是all 或第一个定义目标) |
make <目标> | 构建指定目标(如make clean ) |
make -jN | 并行编译(N为同时执行的任务数) |
make -f <文件名> | 使用特定的Makefile文件 |
make clean | 执行clean 目标,通常用于清理临时文件 |
make -n | 显示将要执行的命令,但不实际执行 |
make -B | 强制重新构建所有目标 |
工作流程
- 检查:make会对比目标文件和依赖文件的时间戳
- 文件的时间戳是make判断是否需要重新编译的唯一依据。
- 这大大节省了重复编译的时间,只处理变化部分。
- 决定:如果依赖的源文件比目标文件新,则重新执行相关命令
- 执行:运行相应的命令完成构建
文件的时间戳
-
每个文件(目标文件、源代码文件、依赖文件)在操作系统中都带有“最后修改时间”。
-
这可以用命令ls -l或stat查看:
ls -l main.o
stat main.o
-
目标文件(如main.o或main)的时间戳表示它们最新的修改时间。
-
源文件(如main.c、utils.c)的时间戳表示它们最后被更改的时间。
当你运行make时,它会根据Makefile中的规则,逐个目标检测:
-
a. 目标文件是否存在?
- 如果目标文件不存在,make会执行对应的命令(即重新构建目标)。
-
b. 目标文件存在,依赖文件是否比目标新?
-
比如:main.o依赖main.c和utils.h。
-
如果任何依赖文件的修改时间比目标文件的时间更新(更“新”),说明依赖内容发生了变化。
-
这种情况:make会重新执行规则中的命令以更新目标。
-
-
c. 依赖未变化(目标比所有依赖都旧)
- make会跳过该目标的重建步骤。
例:
假设有以下两个文件:
main.o: main.c utils.h
gcc -c main.c
- 执行make main.o
- make会检查:
- main.o是否存在?存在吗?
- main.c和utils.h的修改时间,是否比main.o的时间新?
案例一:
- main.c在main.o之后被修改(时间更“新”):
- make检测到这一点,重新运行gcc -c main.c。
案例二:
- main.c和utils.h都没有变化,main.o的时间比它们都早:
- make跳过重新编译,用已存在的main.o。
自动化mini说明
假设你有以下规则:
main: main.o utils.o
gcc -o main main.o utils.o
main.o: main.c utils.h
gcc -c main.c
utils.o: utils.c utils.h
gcc -c utils.c
- 只要main.o、utils.o的依赖源文件没有变化,make就不会重建它们。
- 如果源文件有更新(修改时间更晚),make会自动重新执行对应的编译命令。
小结
过程 | 说明 |
---|---|
检查目标文件是否存在 | 不存在则必须编译,否则进入下一步 |
比较“依赖文件”的时间戳 | 如果依赖文件比目标文件“新”,目标文件需要更新 |
执行命令重新生成目标 | 根据规则中的命令行,重新生成目标文件 |
依赖未变化时跳过重新构建 | 以提高效率,避免不必要的编译 |
示例
假设你的项目目录如下:
project/
├── main.c -- 主程序源文件
├── utils.c -- 其他功能实现的源文件
├── utils.h -- 头文件,声明utils.c中的函数或变量
└── Makefile -- 构建规则文件,用于自动化编译
Makefile内容:
# 定义变量
CC = gcc
CFLAGS = -Wall -g
# 默认目标
all: main
# 目标及依赖
main: main.o utils.o
$(CC) -o main main.o utils.o
main.o: main.c utils.h
$(CC) -c main.c $(CFLAGS)
utils.o: utils.c utils.h
$(CC) -c utils.c $(CFLAGS)
# 清理临时文件
clean:
rm -f main *.o
使用:
- 编译全部:
make
- 只重建main:
make main
- 清除所有生成的文件:
make clean
- 并行编译(比如同时编译两个任务):
make -j2
注意事项
-
变量定义:用VAR = value定义变量,方便维护
-
伪目标:clean等不对应文件的目标前加入.PHONY以避免冲突
.PHONY: clean
clean:
rm -f main *.o
-
条件编译:可以写条件语句(较高级用法)
-
自动变量:如@代表目标文件、@代表目标文件、@代表目标文件、^代表所有依赖文件等