【Linux】Linux 项目自动化构建工具 -- make/makefile

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

👑作者主页@进击的安度因
🏠学习社区进击的安度因个人社区
📖专栏链接Linux

文章目录

一、前言

请添加图片描述

上篇博客我们学习了 gcc 编译器。学会了如何在 Linux 上编译 C语言 代码。

对于我们平常练习是没问题的但是如果有上百个源文件该怎么办难道还是一个个都用 gcc 编译为 .o 文件最后将它们一起链接起来

这肯定是不实际的这使得编译成为了一个很麻烦的事情。

之前我们在 vs 中写代码时使用快捷键就可以很快地进行程序的编译或者直接执行程序那么在 Linux 下能否也能实现这个功能

能否减少编译代码时的风险使编译更加快捷一定程度实现自动化编译

当然有这就是我们 今天的目标之一使用 make/makefile 构建一个简单的自动化工具。

另外 a n d u i n anduin anduin 还会讲解 make/makefile 的概念、原理和规则从多层面理解透彻它们。

二、概念

makefile

makefile 是一个文件。它是一个工程文件的编译规则描述了整个工程的编译链接等规则。

好的 makefile 文件可以使用一行命令来完成 “自动化编译” 一旦写好 makefile 就只要使用在 shell 提示符下输入 make 命令从而完成对工程的编译极大提高效率。

make

make 是一个命令工具用来一个解释 makefile 中文件中的指令。

当已经编写好 makefile 文件后只需要使用 make 就可以执行 makefile 中的内容。

会不会写makefile也从侧面说明了一个程序员是否具备完成大型工程的能力。

一个工程中的源文件不计数其按类型、功能、模块分别放在若干个目录中makefile定义了一系列的规则来指定哪些文件需要先编译哪些文件需要后编译哪些文件需要重新编译甚至于进行更复杂的功能操作。所以使用好 make/makefile 可以使得开发更加得心应手。

一句话总结make 是一条命令makefile 是一个文件两个搭配使用完成项目自动化构建。

三、demo 实现

在讲解 make/makefile 之前我们先写一个小 demo 以这个 demo 为基准对其进行讲解。

makefile 文件需要创建在当前工程的目录下makefile 文件的名称可以为 makefile 或 Makefile

假设当前工程下已经有了一个 test.c 我们直接开始 demo 的编写

image-20230110135212483

这样 makefile 就编写好了就两句话这时编写的 makefile 可以完成对程序的编译。

我们返回终端使用 make 就可以对 test.c 进行编译

image-20230110135419371

使用 make 指令后makefile 的第二行内容被打印在终端并且生成了可执行程序 test test 程序也是可以执行的。

四、原理与规则

上面写了一个小 demo 那么这个 demo 实现的原理我们还不清楚所以接下来 a n d u i n anduin anduin 来讲一下这中间的原理和规则。

1、依赖关系和依赖方法

在 myfile 文件中有这样一句话

test:test.c

刚刚测试过我们知道 test 是目标文件而 test.c 则是原始文件。

而 test.c 经过 gcc test.c -o test 生成 test 文件。

它们之间的关系

  • test 依赖 test.c 生成所以 test.c 是 test 的依赖文件 。它们之间的关系被称为 依赖关系
  • test.c 生成 test 需要通过 gcc test.c -o test 指令这条指令就是 依赖方法

① 感性理解

那么 依赖关系依赖方法 如何理解呢我来举个例子来帮助大家 感性理解

例子1

假设你在上大学到月底了你的生活费没了。那么这时你就打电话给你的老爹来一个亲切的问候(要生活费)。

当你拨通电话你说“爸我是你儿子” 然后把电话挂掉。

这种情况能要到生活费吗肯定不行因为你只是向你的老爹 表明了你是谁(依赖关系) 但是并没有告诉他 你要干什么(依赖方法) 所以你的老爹一般是不会想着给你打钱而是担心你的安全。

所以只有依赖关系没用还需要有依赖方法依赖关系和依赖方法需要同时具备。

例子2

如果你在上大学马上要考试了。你决定又给你的老爹打电话说“爸我是你儿子能不能帮我期中考” 听完这句话你的老爹思考了很久。于是决定连夜赶到你的学校并请你体验了一顿 “七匹狼” 。

综上我们可以发现你有依赖关系(表明了身份) 你也有依赖方法(叫你老爹考试)但是 依赖方法错误 也没有达到目的反而造成错误结果。

这就是典型的有正确的依赖关系但是依赖方法错误。

所以正确的依赖关系必须具有正确的依赖方法。

例子3

如果你有一天又到了月底你随意拨通了一串号码和陌生人说"爸我是你儿子我没生活费了给我打钱。"再以迅雷不及掩耳之势挂掉了电话。电话那头的人很懵。于是当天晚上你的梦里一直出现"功德-1功德-1 … " 。

这个例子说明你有了 正确的依赖方法(要生活费) 但是你 没有正确的依赖关系(陌生人) 这也办不成事。

所以正确的依赖方法也需要正确的依赖关系。

通过以上三个例子我们得出结论依赖关系和依赖方法必须同时具备并正确缺一不可

② 深层理解

我们基于上层的感性理解再通过一个 makefile 深层理解一下

image-20230110150254788

(实际上我们编译代码并不需要进行这里只是为了理解 … 平常就写成 demo 那样就可以)

在 makefile 文件中共有四组依赖关系和依赖方法 当使用 make 调用 makefile 文件中内容时便开始执行 makefile 中的内容

  • test 依赖于 test.o 但是 test.o 并不存在跳转到下一组依赖关系
  • test.o 依赖于 test.s 但是 test.s 并不存在跳转到下一组依赖关系
  • test.s 依赖于 test.i 但是 test.i 不存在跳转到下一组依赖关系
  • test.i 依赖于 test.c test.c 存在这时开始执行依赖方法
  • 由此开始逐渐执行上面的依赖方法一层层回退逐渐生成 test.i 、test.s 、test.o 最后生成可执行程序make 执行完毕

我们发现这一过程就像 数据结构的栈

当目标文件所依赖的文件不存在时就会将依赖方法入栈知道依赖关系匹配了再执行相应的依赖方法在按照栈的规则逐渐将栈中的元素出栈规则满足后进先出。

为了验证这些步骤是否都被执行我们 make 一下看看

image-20230110151502695

依赖方法对应的文件都产生了这也说明我们讲解的步骤是正确无误的。

2、清理

平时写代码时经常需要反复编译执行代码。

而在下一次重新编译之前需要清理一下上次生成的可执行程序。但是清理的时候可能清理错误不小心把源文件删了这时又造成了问题。

而上面的步骤我们也生成了很多附加文件(如 test.i 等)。

所以我们基于 demo 增加一个清理功能

image-20230110152700565

使用 make 测试一下

image-20230110152825515

文件也都删除了。

这是为什么新增语句是什么意思继续讲解原理。

① .PHONY 伪目标

.PHONY 修饰的对象是伪目标伪目标的特性是总是被执行的。

.PHONY 修饰的一定能被反复执行但是能被反复执行的不一定被 .PHONY 修饰。

多次执行 make 和 make clean 试试

image-20230110153324568

(注makefile 默认从上到下扫描只会执行第一组的依赖关系和依赖方法所以默认执行第一组这时使用 make 就可以而 clean 为第二组所以需要 make clean 加上对应的关系。同理对于第一组使用 make test 也能执行。)

发现第一组关系没有被 .PHONY 修饰而不能重复执行。但是第二组 clean 可以重复执行

但是怎么证明 .PHONY 修饰对象之后对象能被反复执行口说无凭所以我们再验证一下

给 test 加上修饰

image-20230110153906181

image-20230110153930676

加上 .PHONY 修饰后make 可以执行多次了证明了 .PHONY 的作用。

但是能被反复执行的不一定被 .PHONY 修饰就比如 clean

image-20230110163656271

image-20230110163725704

当 clean 去掉修饰之后依然能被反复执行。

② .PHONY 的取舍

一般对于编译来说是不加 .PHONY 修饰的。

因为编译是十分耗时间特别是当工程量很大的时候编译一两小时都不为过。所以防止对未修改的程序反复编译 一般编译时不加修饰。

但是 清理clean 是可以多次执行的因为删除不太浪费时间且可以反复清理确认是否清理完毕。并且为了肯定清理可以被多次执行所以通常用 .PHONY 修饰。

3、make 确定是否编译的方法

上面我们测试 make 时发现当编译过一次后继续使用 make 就无法继续编译了。但是 clean 是可以不加修饰反复执行的。原因我们也探讨过但是 make 是如何确定是否要编译

是这样的对于程序来说时间有两条线。第一条是源代码时间的一条线第二条是形成的可执行程序的时间的一条线

而对于它们之间的次序是先有源代码再有可执行程序。

所以只要可执行程序的最近修改时间比源文件的修改时间晚就认为当前可执行程序是最新的为了减少时间和其他开销于是不执行编译否则执行编译

我们再重新生成可执行程序并重复 make 观察它们的时间

观察时间这里就要用到 stat 指令它的 modify 就是最近修改时间 如果对 stat 指令不了解的小伙伴可以看这一篇基本指令(一) 在 ls 指令部分。

image-20230110165532255

可执行程序 test 的时间明显比 源代码 test.c 晚所以 make 并不能起作用。

那么基于对这个概念的理解我们能否钻空子来 欺骗一下 make

补充

touch 指令为创建一个文件。若文件不存在则会创建一个文件若文件存在则会把文件时间更新到最新。

使用 touch 更新一下 test.c 的时间用 stat 观察时间并反复 make 试试

image-20230110170016287

image-20230110170131542

由此我们发现可以使用 touch 来 “欺骗” make 来反复编译。这也侧面证明了 make 对于是否编译的决策是基于修改时间而并不是基于文件内容是否修改

4、完整代码

test:test.c
	gcc test.c -o test 
.PHONY:clean                                                      
clean:
	rm -f test.i test.s test.o test

5、规则总结

  • 对于依赖关系而言: 左边为目标文件: 右边为依赖文件
  • 依赖方法前需要有一个 tab 为固定格式
  • : 右边可以有多个依赖文件 : 右边通常被称为依赖文件列表
  • 对于 : 右边目标文件对应的依赖文件列表可以为空 (例如 clean)
  • makefile 默认执行第一组的依赖关系和依赖方法对于第一组可以直接使用 make 执行后面则需要 make + 目标文件
  • .PHONY 修饰的 伪目标可反复执行 但反复执行的不一定是伪目标

五、结语

到这里本篇博客就到此结束了。

实际开发中好的 make/makefile 可以让项目开发事半功倍。所以构建自动化工具还是蛮重要的可以大大提高开发的效率。

但是博主能力有限对于博主当前构建这么一个简单的工具就足够了。我们今天的重点是放在 make/makefile 的规则上。

感兴趣的小伙伴也可以往 makefile 中添砖加瓦构建出自己开发的利器。

如果觉得 a n d u i n anduin anduin 写的不错的话可以 点赞 + 收藏 + 评论 支持一下哦我们下期见~

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: linux