make 的工作哲学可以完美地用 “原料 → 产物” 的关系来概括。在 Makefile 中,这体现为 “依赖 → 目标” 的关系。
- 目标 (Target):你想要生成的文件,也就是“产物”,比如编译好的可执行文件。
- 依赖 (Prerequisites/Dependencies):生成该目标所需要的文件或条件,也就是“原料”,比如源代码文件 (
.c,.cpp) 或头文件 (.h)。 - 命令 (Commands):从“原料”制作“产物”的具体步骤,比如调用 gcc 或
clang进行编译。
基本语法结构如下:
# 产物: 原料
# <注意> 命令前必须是一个Tab键,而不是空格
目标: 依赖1 依赖2 ...
命令make 的智能之处在于,它会检查文件的时间戳。只有当“原料”(依赖文件)比“产物”(目标文件)更新(或者产物文件不存在)时,它才会执行相应的命令来重新生成产物。这极大地提高了编译效率,避免了不必要的重复工作。
.PHONY:定义“伪目标”
1. 什么是伪目标?
Makefile 中的目标(Target)通常对应一个具体的文件。但有时,我们需要定义一个目标,它本身并不代表一个文件,而只是一个动作的标签,比如 clean(清理)、install(安装)、all(构建所有)等。这种不代表实际文件名的目标,就称为“伪目标”(Phony Target)。
2. 为什么需要 .PHONY?
使用 .PHONY 来声明一个伪目标主要有两个原因:
a) 避免与同名文件冲突(最主要原因)
这是 .PHONY 最核心的用途。我们回头看 make 的工作逻辑:它会检查目标文件是否存在以及是否需要更新。
假设你的 Makefile 中有这样一个 clean 规则,但没有使用 .PHONY 声明:
Makefile
clean:
rm -rf build/ *.o my_program
当你执行 make clean 时,一切正常。但是,如果某一天,你的项目目录下因为某种原因,出现了一个名为 clean 的文件。这时你再执行 make clean,会发生什么?
make 会检查 clean 目标:
- 它发现一个叫
clean的文件存在。 - 它检查
clean目标的依赖。这里没有依赖。 - 结论:目标
clean已经存在,并且没有比它更新的依赖,所以无需执行任何操作。 结果就是,rm -rf ...这条清理命令完全不会被执行!这显然不是我们想要的。
通过 .PHONY 声明,你就等于告诉 make:“clean 这个目标只是一个动作的代号,它永远不会对应一个真实的文件。所以,不要去检查是否存在一个叫 clean 的文件,只要我调用 make clean,就无条件执行它下面的命令。”
### Clean a single project (remove build/, .o files, etc.)
clean:
rm -rf build/ *.o my_program
# 声明 clean 是一个伪目标
.PHONY: cleanb) 提高执行效率(次要原因)
当 make 在处理一个目标时,如果它不是伪目标,make 会尝试应用各种隐含规则来寻找如何构建它。将目标声明为 .PHONY 可以告诉 make 不用去费力寻找任何与文件名相关的规则,从而略微提高执行效率。
3. 常见的伪目标
除了 clean,还有一些约定俗成的常用伪目标:
all: 通常作为 Makefile 的第一个目标,也是默认目标(直接敲make时执行的目标)。它的依赖通常是项目中所有需要构建的主要目标(比如最终的可执行文件)。install: 用于将编译好的文件安装到系统中指定的位置。uninstall: 与install相反,用于卸载程序。test: 用于执行测试用例。