本文章将对make与makefile进行一些基础的讲解。
假设我们要建造一座房子,建造过程涉及很多步骤,比如打地基、砌墙、安装门窗、粉刷墙壁等。每个步骤都有先后顺序,并且有些步骤可能依赖于其他步骤的完成。比如,你必须先打好地基才能砌墙,必须先砌好墙才能安装门窗。这时候,你需要一个详细的施工流程表,告诉工人每一步该做什么,以及在什么条件下可以进行下一步。make 和 makefile 就像是这个施工流程表。
一、什么是 make 和 makefile
-
makefile:是一个文本文件,里面包含了一系列的规则,这些规则定义了如何从源文件生成目标文件,以及各个目标文件之间的依赖关系。 -
make:是一个构建自动化工具,它会读取makefile中的规则,并根据这些规则来决定哪些文件需要重新编译或构建。
二、makefile 的基本结构
一个典型的 makefile 包含以下几个部分:
- 目标(Target):你想要生成的东西,比如一个可执行文件或一个目标文件。
- 依赖(Dependency):生成目标所需要的文件。
- 命令(Command):为了生成目标需要执行的命令。
一个简单的 makefile 示例:
# 目标:生成可执行文件 main
main: main.o add.o sub.ogcc main.o add.o sub.o -o main# 目标:生成 main.o
main.o: main.cgcc -c main.c# 目标:生成 add.o
add.o: add.cgcc -c add.c# 目标:生成 sub.o
sub.o: sub.cgcc -c sub.c# 清理生成的文件
clean:rm -f main main.o add.o sub.o
解释
-
目标
main:- 依赖:
main.o、add.o和sub.o。 - 命令:
gcc main.o add.o sub.o -o main。这个命令表示将main.o、add.o和sub.o链接成一个可执行文件main。
- 依赖:
-
目标
main.o:- 依赖:
main.c。 - 命令:
gcc -c main.c。这个命令表示将main.c编译成目标文件main.o。
- 依赖:
-
目标
add.o和sub.o:- 类似于
main.o,分别将add.c和sub.c编译成目标文件。
- 类似于
-
目标
clean:- 命令:
rm -f main main.o add.o sub.o。这个命令用于清理生成的可执行文件和目标文件。
- 命令:
三、make 的工作原理
当你运行 make 命令时,make 会读取 makefile 文件,并按照以下步骤工作:
- 读取
makefile:make会解析makefile中的规则,构建一个依赖图。 - 确定目标:默认情况下,
make会尝试生成makefile中第一个目标(在这个例子中是main)。 - 检查依赖:
make会检查目标的依赖文件是否存在,以及这些依赖文件是否有更新。如果依赖文件不存在或比目标文件新,make会执行相应的命令来更新目标文件。(如何检查的呢?) - 执行命令:
make会按照依赖图的顺序执行命令,生成最终的目标。
四、示例运行
假设你有以下文件:
main.cadd.csub.c
运行 make 命令:
make 会根据 makefile 中的规则,依次编译 main.c、add.c 和 sub.c,然后将它们链接成可执行文件 main。
如果你修改了 add.c 文件,再次运行 make:
make 会检测到 add.c 文件有更新,只重新编译 add.c 成 add.o,然后重新链接生成 main。
如果你想清理生成的文件,可以运行:
make clean
make 会执行 clean 目标中的命令,删除 main、main.o、add.o 和 sub.o 文件。
五、 .PHONY
在 makefile 中,.PHONY 是一个特殊的目标,用于声明某些目标是“伪目标”。伪目标不是实际的文件,而是用于执行特定的命令或任务。使用 .PHONY 可以避免与同名文件冲突,并且可以提高 make 的执行效率。
1.为什么需要 .PHONY
- 避免与文件名冲突:如果有一个目标名与文件名相同,
make会误认为该目标是文件,而不是一个任务。使用.PHONY可以避免这种冲突。 - 提高执行效率:
make会检查文件的时间戳来决定是否需要重新生成目标文件。对于伪目标,make不需要检查时间戳,可以直接执行相应的命令,从而提高执行效率。 - 明确意图:使用
.PHONY可以明确告诉其他开发者,这个目标是一个虚拟的任务,而不是一个实际的文件。
2.如何使用 .PHONY
以下是一个简单的 makefile 示例,展示了如何使用 .PHONY:
# 声明伪目标
.PHONY: clean test# 默认目标
all: main# 生成可执行文件 main
main: main.o add.o sub.ogcc main.o add.o sub.o -o main# 生成 main.o
main.o: main.cgcc -c main.c# 生成 add.o
add.o: add.cgcc -c add.c# 生成 sub.o
sub.o: sub.cgcc -c sub.c# 清理生成的文件
clean:rm -f main main.o add.o sub.o# 运行测试
test:./mainecho "All tests passed!"
在这个示例中:
.PHONY: clean test声明了clean和test是伪目标。clean目标用于清理生成的文件,它不生成任何实际的文件。test目标用于运行测试,它也不生成任何实际的文件。
示例解释
1.声明伪目标
.PHONY: clean test
这行代码告诉 make,clean 和 test 是伪目标,而不是实际的文件。即使当前目录下存在名为 clean 或 test 的文件,make 也会执行相应的命令。
2. 使用伪目标
-
清理文件:
make clean这条命令会执行
clean目标中的命令,删除main、main.o、add.o和sub.o文件。 -
运行测试:
make test这条命令会执行
test目标中的命令,运行main可执行文件,并输出 "All tests passed!"。
3. 避免冲突
假设当前目录下有一个名为 clean 的文件,如果没有使用 .PHONY 声明 clean 为伪目标,make 会认为 clean 是一个文件,而不是一个任务。此时,运行 make clean 不会执行任何命令,因为 clean 文件已经存在,且没有依赖关系需要更新。
通过使用 .PHONY,可以避免这种冲突,确保 make clean 总是执行清理命令。
.PHONY 不仅可以用于常见的清理和测试任务,还可以用于其他任何不需要生成实际文件的任务。
-
.PHONY的作用:声明伪目标,避免与文件名冲突,提高执行效率,明确意图。 - 如何使用:在
makefile中使用.PHONY关键字,后跟伪目标的名称列表。 - 常见用途:清理文件、运行测试、生成文档、打包发布、代码风格检查等。
- 伪目标总是会被执行,不会被拦截。(比如我们重复make会提示已经make了不让我们make,但是如果我们把他搞成伪目标,那么就不会受此限制了)。
六、优点
- 自动化:
make可以自动处理文件之间的依赖关系,只重新编译需要更新的文件,节省时间和资源。 - 可维护性:通过
makefile,你可以清晰地定义项目的构建流程,方便团队协作和项目管理。 - 灵活性:
makefile支持复杂的规则和条件判断,可以适应各种构建需求。
七、缺点
- 维护成本:随着项目规模的增大,
makefile可能会变得非常复杂,维护起来比较困难。
八、总结
-
makefile:是一个包含构建规则的文本文件,定义了如何从源文件生成目标文件以及各个目标文件之间的依赖关系。 -
make:是一个构建自动化工具,读取makefile中的规则,并根据这些规则决定哪些文件需要重新编译或构建。
通过 make 和 makefile,你可以高效地管理和构建项目,确保每次构建都是基于最新的文件状态。