「Linux 系统编程」Makefile 基本使用方法
1 Makefile 简介
1.1 什么是 Makefile
Makefile 是 Linux/Unix 下的自动化构建工具,用于管理代码编译、安装、清理等流程。常用于编译 C/C++ 项目、自动化测试、软件打包等。
通过定义 目标(target)、依赖(dependencies) 和 执行命令(commands),自动检测文件改动,仅重新构建受影响的部分。
Makefile 包含以下特点:
- 增量编译:只编译修改过的文件
- 依赖管理:自动处理文件间的依赖关系
- 跨平台支持:通过规则抽象编译细节
基本用法:执行 make
自动编译最新改动,make clean
清理生成文件。
1.2 Makefile 文件的命名规则
在项目目录下执行 make
命令,默认会按照 GNUmakefile
→ makefile
→ Makefile
的顺序查找 Makefile 编译文件。
通常推荐以 Makefile
作为项目 Makefile 编译文件的名称。此时,只需要执行如下命令即可对项目进行自动编译:
1 |
|
如果要自定义编译文件的名称,例如自定义名称为 myMakefile
,需要通过 -f
或 --file
参数显式指定:
1 |
|
2 Makefile 基础语法和依赖规则
2.1 基础语法结构
1 |
|
target
:生成的 目标文件(如 app)或操作名(如 clean)dependencies
:生成 target 所需的文件或目标(空格分隔)command
:生成目标的命令(必须以 Tab 开头,不能用空格)
示例:
1 |
|
假设有 utils.cpp
、utils.h
和 main.cpp
文件内容分别如下:
1 |
|
1 |
|
1 |
|
Makefile 文件内容如下:
1 |
|
执行 make
命令进行编译得到可执行程序:
上述过程实现了一个简单的 使用 Makefile 编译项目 的示例。
2.2 依赖规则
在之前 2.1 基础语法结构 的示例中,假如有很多个 .cpp
文件,后续有一个被修改而其它并未被修改,此时再执行 make
对项目进行重新编译,会发现所有的 .cpp
也都会被重新编译。
编译往往需要很多的时间,显然之前 2.1 基础语法结构 中示例的做法是比较耗时的。
我们可以借助 Makefile 中的 依赖规则,使得只有 被修改的文件部分进行目标重建。
-
目标重建条件:
- 当 依赖文件(
dependencies
)的时间晚于 目标文件(target
)的时间时。 - 目标文件不存在 时。
- 当 依赖文件(
-
依赖传递:
根据目标重建条件,可以将 2.1 基础语法结构 的示例 修改如下:
1
2
3
4
5
6
7
8app: main.o utils.o
g++ main.o utils.o -o app
main.o: main.cpp
g++ -c main.cpp -o main.o
utils.o: utils.cpp
g++ -c utils.cpp -o utils.o在上面修改过后的 Makefile 中,
app
依赖的是main.o
和util.o
文件;main.o
依赖的是main.cpp
文件;utils.o
依赖的是utils.cpp
文件。执行上述 Makefile 文件编译项目。在项目目录中也生成了每个
.cpp
对应的.o
目标文件:假设
utils.cpp
被修改了,其它文件均没有修改。此时执行make
命令重新编译文件,发现只执行了g++ -c utils.cpp -o utils.o
和g++ main.o utils.o -o app
命令。根据依赖规则,只有utils.cpp
为起点的一条依赖链上的目标文件重新进行了构建:
2.3 最终目标指定
在 Makefile 中,最终目标(即默认目标)通过以下规则确定:
-
位置优先原则
文件中的 第一个有效目标 会被视为 默认目标。当用户仅输入
make
命令时,将自动构建此目标。1
2
3
4
5
6
7
8app: main.o utils.o # 默认第一个有效目标就是最终目标
g++ main.o utils.o -o app
main.o: main.cpp
g++ -c main.cpp -o main.o
utils.o: utils.cpp
g++ -c utils.cpp -o utils.o -
显式声明最终目标
如果 Makefile 文件中
app
不在第一个有效目标的位置,此时可以通过 显式声明.DEFAULT_GOAL
来指定最终目标。基本语法如下:
1
.DEFAULT_GOAL := 目标名 # 注意变量赋值用 :=,且目标名不需要依赖和命令
示例:
1
2
3
4
5
6
7
8
9
10.DEFAULT_GOAL := app # 显式声明最终目标
main.o: main.cpp
g++ -c main.cpp -o main.o
app: main.o utils.o
g++ main.o utils.o -o app
utils.o: utils.cpp
g++ -c utils.cpp -o utils.o -
使用
all
指定最终目标基本格式如下:
1
2all: 目标1 目标2 ...
[命令]all
后面跟随的是它所依赖的其他目标,当执行make all
时,会先执行这些依赖目标,然后再执行all
自身的命令(如果有的话)。特别的,当
all
放在 Makefile 文件的第一行,执行make
命令进行编译和make all
的效果就一样了。示例:
1
2
3
4
5
6
7
8
9
10all: app
main.o: main.cpp
g++ -c main.cpp -o main.o
app: main.o utils.o
g++ main.o utils.o -o app
utils.o: utils.cpp
g++ -c utils.cpp -o utils.o -
all
结合.DEFAULT_GOAL
指定最终目标可以将
all
结合.DEFAULT_GOAL
来指定最终目标。这样即便all
不放在第一个目标位置,make
命令也会执行all
构建程序。示例:
1
2
3
4
5
6
7
8
9
10
11
12.DEFAULT_GOAL := all
main.o: main.cpp
g++ -c main.cpp -o main.o
all: app
app: main.o utils.o
g++ main.o utils.o -o app
utils.o: utils.cpp
g++ -c utils.cpp -o utils.o在上面的示例中,即便第一个目标为
main.o
,结合all
和.DEFAULT_GOAL
使得执行make
命令构建的最终目标为程序app
。
3 Makefile 两个常用函数和 clean
3.1 两个常用函数 wildcard 和 patsubst
两个常用函数 wildcard
和 patsubst
主要是为了提升 Makefile 文件编译的可扩展性。
-
$(wildcard pattern)
获取当前工作目录下 匹配模式 的 文件列表。
示例:
1
SRC = $(wildcard *.cpp) # 获取所有 .cpp 文件
在上面的示例中,获取所有当前目录下的 .cpp 文件,并将文件名组成列表,赋值给变量
SRC
。 -
$(patsubst pattern, replacement, text)
模式字符串替换。具体为将参数 3
text
中包含参数 1pattern
中的部分,全部替换为参数 2replacement
。示例:
1
OBJ = $(patsubst %.cpp, %.o, $(SRC))
在上面的示例中,将
SRC
文件列表中所有后缀为.cpp
文件的后缀替换为.o
。
3.2 clean 清理项目生成中间件
在 Makefile 中,clean
是一个常用的自定义目标,主要用于 清理项目构建过程中生成的中间文件(如 .o 目标文件)、最终可执行文件、日志 等临时文件,以便重新构建项目或清理工作目录。
基本用法如下:
1 |
|
在清理编译生成的文件时,通常使用 .PHONY
将 clean
声明为伪目标(避免与同名文件冲突):
1 |
|
在工作目录下执行 make clean
即可清理文件。
在清理文件时,通常会先加上 -n
参数选项(模拟执行),即执行 make clean -n
。这样执行之后会显示 clean
目标将要执行的所有命令,但不会实际执行这些命令。这相当于做一次 “预演”,确认清理操作会删除哪些文件,避免误操作。
3.3 常用函数及 clean 使用
由上面的两个常用函数 wildcard
和 patsubst
以及 clean
清理文件,可以将先前示例的 Makefile 文件修改如下:
1 |
|
在上面的 Makefile 文件中,将 all
和 clean
都声明为伪目标,避免目录下有同名文件使得编译崩溃;将 main.o utils.o
都替换为 $(OBJ)
。执行 make clean
以及 make
命令效果如下:
在 Makefile 的 clean
等目标中,常在 rm
命令前加 -
,即 -rm -f $(OBJ) app
。
这样可以 忽略命令执行的错误,如果 rm
命令执行失败(例如要删除的文件不存在),make
不会终止后续操作,而是继续执行其他命令。
4 Makefile 自动变量和模式规则
4.1 三个核心自动变量
-
$@
:在当前规则命令中的 目标文件名称1
2app: $(OBJ)
g++ $^ -o $@ # $@ = "app" -
$<
:在当前规则命令中的 第一个依赖条件。- 在普通规则中,始终是依赖文件列表的第一个文件。
1
2main.o: main.cpp
g++ -c $< -o $@- 在 模式规则 中,自动匹配当前目标的对应文件。
1
2
3
4%.o: %.cpp
g++ -c $< -o $@ # $< 自动匹配当前 .o 对应的 .c 文件
# 当构建 main.o 时:$< = "main.c"
# 当构建 utils.o 时:$< = "utils.c" -
$^
:在当前规则命令中的 所有依赖文件列表。如果这个列表中有重复项,则去重。文件名以空格分隔。
1
2app: $(OBJ)
g++ $^ -o $@ # $^ = "main.o utils.o"
4.2 模式规则
使用 %
通配符定义通用规则:
1 |
|
4.3 静态模式规则
静态模式规则(Static Pattern Rules)是 Makefile 中一种精确控制目标匹配范围的模式规则变体。与普通模式规则不同,它 显式指定应用规则的目标列表,提供更精细的控制。
1 |
|
基本语法示例:
1 |
|
在上面的示例中,指定模式规则给 OBJ
用。当一个项目比较庞大以后,同一种模式规则会有不同生成目标的方式,同一类目标文件也会有不同的依赖(普通模式规则会进行全局匹配),此时就需要指定哪个文件集合用什么模式规则。
示例:
1 |
|
1 |
|
4.3 自动变量和模式规则使用
由上面的三个核心自动变量 $@
、$<
和 $^
以及模式规则,可以进一步优化之前 3.3 常用函数及 clean 使用 中 的示例如下:
1 |
|
执行 make clean
和 make
命令效果如下:
5 Makefile 扩展
可以添加一些自定义变量,例如 ARGS
作为编译过程中添加的参数选项、TARGET
作为最终生成的程序名称等等。
如果 .cpp
文件、.h
文件放置于当前目录的子目录 ./src
、./include
下(或者其它目录),并且生成的 .o
文件需要放置在 ./obj
目录下,可以修改最终的 Makefile 文件如下:
1 |
|
执行程序构建结果如下: