一、基本原理
Makefile 是由工具 make 使用的配置文件。make 会读取 Makefile 中的内容,根据定义的规则和依赖关系来构建目标文件。
make 工作方式:
1.查找目标文件是否存在。
2.如果目标文件不存在,或者其依赖文件的修改时间比目标文件更新,则执行相应的命令生成目标文件。
二、基本语法
target: dependencies commandtarget:生成的目标文件或目标名称
dependencies:生成目标文件所依赖的文件名称
command:生成目标文件的命令,必须以Tab开头
三、实践出真知
以此目录结构下的文件做测试,将逐步深入介绍。
1、自定义变量
使用自定义变量:(优先使用海象运算符:=。2025.09.03更新了内容)
# 使用 = VAR1 = hello VAR2 = $(VAR1) VAR1 = world # 最终 VAR2 的值是 world,因为 VAR1 在使用时才展开 # 使用 := VAR1 := hello VAR2 := $(VAR1) VAR1 := world # 最终 VAR2 的值是 hello,因为 VAR1 在定义 VAR2 时已经展开了# 自定义变量 target = main dep = add.o sub.o main.o # 使用$()获取变量 $(target):$(dep) g++ $(dep) -o $(target) add.o: g++ -c add.cpp -o add.o sub.o: g++ -c sub.cpp -o sub.o main.o: g++ -c main.cpp -o main.o clean: rm $(dep) $(target)1.执行 make 时不指定目标名称,默认执行第一个目标。
2.当依赖文件不存在,make 会根据已有规则尝试构建。make 找不到规则,则执行错误。
3.一般目标名称不能为空,它指引着 make 的操作。
4.依赖名称可以为空,命令中指定着所需要的依赖文件。
运行情况如下:
2、内置变量
CXX 默认 g++,指定c++编译器。
CC 默认 cc,指定c编译器。
RM 默认 rm -f, 删除文件命令。
...
使用内置变量:
target = main dep = add.o sub.o main.o $(target):$(dep) $(CXX) $(dep) -o $(target) add.o: $(CXX) -c add.cpp -o add.o sub.o: $(CXX) -c sub.cpp -o sub.o main.o: $(CXX) -c main.cpp -o main.o clean: $(RM) $(dep) $(target)如果自定义变量名与内置变量名重复,优先使用自定义变量。
3、自动变量
$@:表示目标文件名称,包含文件扩展名。
$^:依赖项中,所有不重复的依赖文件。
$<:依赖项中第一个依赖文件名称。
...
使用自动变量:
# 自定义变量 target = main dep = add.o sub.o main.o # 自定义变量覆盖内置变量 CXX = g++ $(target):$(dep) $(CXX) $^ -o $@ #使用自动变量 add.o: $(CXX) -c add.cpp -o add.o sub.o: $(CXX) -c sub.cpp -o sub.o main.o: $(CXX) -c main.cpp -o main.o clean: $(RM) $(dep) $(target)4、模式匹配
%:通配符。
使用模式匹配:
# 自定义变量 target = main dep = add.o sub.o main.o # 自定义变量覆盖内置变量 CXX = g++ $(target):$(dep) $(CXX) $^ -o $@ # 使用自动变量 %.o:%.cpp # 使用模式匹配,将所有.cpp文件编译为.o文件 $(CXX) -c $< -o $@ clean: $(RM) $(dep) $(target)1. * 通常用于文件名匹配或 wildcard 函数中,表示任意数量的字符。
2. % 用于模式规则中,表示一个模式中的任意字符串,用于定义通用的构建规则。
5、函数
使用方式:$(函数名 参数1,参数2...),多参数使用逗号隔开。
wildcard <pattern...>:只有一个参数,指定某个或多个目录下的某种类型文件,多个目录使用空格隔开。返回所匹配的文件列表。
patsubst <pattern>,<replacement>,<text>:将 <text> 中匹配 <pattern> 的部分替换为<replacement>。
...
使用函数:
# 自定义变量 target = main obj = $(wildcard *.cpp) # 使用wildcard函数找到匹配的文件 dep = $(patsubst %.cpp, %.o, $(obj)) # 使用patsubst函数对内容替换 # 自定义变量覆盖内置变量 CXX = g++ $(target):$(dep) $(CXX) $^ -o $@ # 使用自动变量 %.o:%.cpp # 使用模式匹配 $(CXX) -c $< -o $@ clean: $(RM) $(dep) $(target)结果展示:
6、使用.PHONY
假设在项目文件夹内有一个文件名称和 Makefile 中的目标名称一致,在使用 make 构建项目时,make 会认为这个目标文件已被构建完成而不继续执行命令。对于一些目标名称只是表示一些动作或任务时,这并不会产生目标文件。那么这时候就会有冲突。
此时我们就可以使用 .PHONY 来告诉 make 这些目标文件是虚拟的,不应该被视为文件。
此时我们自己创建了一个文件名为 clean 的文件,可以发现当我们使用 make clean 指令时,并没有按预期执行命令,而是提示"clean"已是最新。
使用 .PHONY:
# 自定义变量 target = main obj = $(wildcard *.cpp) # 使用wildcard函数找到匹配的文件 dep = $(patsubst %.cpp, %.o, $(obj)) # 使用patsubst函数对内容替换 # 自定义变量覆盖内置变量 CXX = g++ $(target):$(dep) $(CXX) $^ -o $@ # 使用自动变量 %.o:%.cpp # 使用模式匹配 $(CXX) -c $< -o $@ .PHONY:clean clean: $(RM) $(dep) $(target)结果展示:
7、Tips
make -j4-j:表示 jobs;
4:表示同时运行 4 个编译任务。
make 会分析 Makefile 中的依赖关系,若多个目标文件无相互依赖,则同时编译。数字参数可依据 CPU 核心改变,使得编译任务并行提高效率。
四、深入Makefile
1、目标名称和依赖项说明
target: dependencies command我们知道真正的命令执行在于 command 部分,即使目标名称 target 和 依赖项 dependencies 发生变化,只要 command 正确。依旧可以正确执行。
但并不能忽视target : dependencies,因为目标名称和依赖项会被 make 监控,一旦这两项中的文件发生改变,make 会监控到并做出改变。
例如,当依赖文件发生改变时我们可以继续执行 make 命令编译出新文件。如果没有改变,则会提示目标文件已是最新,不会执行命令。再者,目标名称,在实际开发中我们应该保持命令中最终生成的可执行文件名称与目标名称 target 一致。这是因为如果不一致,make 命令的重复执行会被允许,且没有提示。因为 Makefile 监控不到目标文件认为它没有生成。
2、Makefile的自动推导
在之前的 Makefile文件中,我们自己编写了生成.o文件的命令。但是 Makefile 也可以自动推导。
这里直接指定了依赖项的.o文件,它会自动推导生成。
另外 clean 命令下的- 表示忽略错误,因为这个命令的错误(如rm无法删除,不存在xxx文件)这对我们没有影响,所以忽略。
target := file_io_test dep := file_io_test.o $(target):$(dep) gcc $(dep) -o $(target) .PHONY:clean clean: -rm $(target) $(dep)