前言:
在上一期的博文中,我们对 Linux 下的编译器 - gcc/g++的使用进行了详细的讲解,今天我将给大家讲解的是关于 【Linux】项目自动化构建工具 —— make/Makefile 的详细使用教程!!
本文目录
(一)前情摘要
(二)背景介绍
1、Makefile 的基本认识
2、make 的基本认识
3、代码实现
(三)依赖关系与依赖方法
1、基本概念
2、深入理解
(四)项目清理
1、代码演示
2、.PHONY原理
(五)三个时间
1、make的判断机制
(六)总结
(一)前情摘要
在上一期我们将 Linux编译器-gcc/g++使用 的使用的时候,我们通过对 C语言翻译的逐过程讲解知道了可执行程序 【a.out】的来历。
整体过程大概划分为以下几个步奏: 👇
第一步,就是进行预处理:文件从【test.c】
经过预编译最终形成【test.i
】第二步,经过预处理之后,文件从【test.i】
经过编译最终形成 【test.s
】第三步,经过编译之后,文件从【test.s】
经过汇编最终形成 【test.o
】第四步,经过汇编之后,文件从【test.o】
经过链接最终形成 【a.out
】
💨 以上经过编译 加上链接的过程,大家有没有觉得看着十分的复杂,对于学习初期拿来练练手了解一下原理还可以,但是若有一天大家到公司中,面对那些大型的工程项目,轻则成千上万行代码,重则十几万甚至更多。
💨 若是一次编译完成之后又修改了源代码,接着又想进行编译,此时便需要重新敲入指令,那会使得工作量变得很大,还显得十分的低效。
因此,基于上述原因,我们就诞生出了本期将要介绍的 ——> 【make/Makefile】
- 利用 make工具,我们可以将大型的开发项目分解成为多个更易于管理的模块,对于一个包括几百个源文件的应用程序,使用 make 和 makefile 工具可以十分简单的整理好各个源文件之间的关系。
💥 优势:
- 包含多个源文件的项目工程在编译时有很多复杂且指令较长的命令行,这时我们可以通过makefile 保存这些命令行来简化该工作;
- 其次,make 也可以减少重新编译所需要的时间,因为 make 可以精准的识别出项目中的文件有哪些是经过修改变化的;
- make 维护了当前项目中各文件的相关关系,从而可以在编译前检查是否可以找到所有的文件
接下来,我们就对 【make/Makefile】 进行详细的讲解!!!
(二)背景介绍
在正式应用之前,我们先对这两个进行概念上的理解!!!
1、Makefile 的基本认识
定义:
- MakeFile 是一个GNU推出的 编译开发工具,能为一些编译过程提供服务。
- Makefile 的本质其实是一个
文件
。它是一个工程文件的编译规则,它记录了原始信息如何编译的详细信息、描述了整个工程的编译链接等规则。
功能:
- 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的 规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂 的功能操作;
- 举个例子,我们上述提到的文件翻译过程形成的那么多的文件,怎么把他们编译成最后能够执行的可执行程序呢?
- 我们就可以用 MakeFile 来辅助你编译,用了MakeFile除了能提升效率,还能避免人为操作导致错误。
makefile基于两个重要关系:
- 依赖关系:定义了如果你想要建立一个项目或者目标,有什么是必须要做的;
- 时间关系:根据文件的属性时间。make 程序判断哪些文件在项目过程中发生了修改。然后根据上述的依赖关系,把依赖于这些修改文件的目标文件重新确立一份关系。
紧接着我们来看一下【MakeFile】的语法介绍:
<target1 > <target2>.... : <prerequisites1> <prerequisites2>...
[TAB] <command1>
[TAB] <command2>
...
命令解析:
- 【target】:可以是一个object file(目标文件),也可以是一个执行文件,还可以是一个标签(label);
- <prerequisites>:就是要生成的那个 target 所需要的文件或者目标;
- <command>:也就是 make 需要执行的命令;
很明显我们可以发现这是一个文件的依赖关系,
<target>
这一个或多个的目标文件依赖于<prerequisites>
中的文件,其生成规则定义在<command>
中。说白一点就是说,
<prerequisites>
中如果有一个以上的文件比<target>
文件要新的话,<command>
所定义的命令就会被执行。这就是makefile的规则。也就是makefile中最核心的内容。
小结:
- makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编 译,极大的提高了软件开发的效率。因此,会不会写 makefile,从一个侧面说明了一个人是否具备完成大型工程的能力
2、make 的基本认识
定义:
- make是一个 命令工具,是一个解释 makefile 中指令的命令工具,一般来说,大多数的IDE都有这个命 令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一 种在工程方面的编译方法。
3、代码实现
有了以上概念的了解,接下来我们就手动的去实现一下这两个基本工具:
- 第一步:上述我们可以知道 make 命令是用来解释 Makefile 中指令的,所以我们需要创建一个文件进行编辑,具体如下;
- 第二步:我们再创建一个【myfile.c】的文件,作为源文件,然后开始往里面写内容
- 第三步:回到命令行,我们执行一下
make
指令,它就是自动在当前源文件的所在路径下搜寻Makefile,并解释里面命令,最后生成myfile
- 第四步:此时当我们想删除应该怎么办呢?还是跟之前一样一条条的去【rm】吗?答案是不用,此时我们在【Makefile】中在写入一段即可帮助我们完成这项工作:
综上所述:make是一条命令,makefile 是一个文件,两个搭配使用,完成项目自动化构建。
(三)依赖关系与依赖方法
在上述我们讲【Makefile】时,我提到过关于依赖关系这个话题,可能许多小伙伴就是满头雾水,什么是依赖关系呢?依赖关系在这里怎么体会的呢?接下来,我们就在仔细讲讲这个 “依赖关系”
1、基本概念
在上述我编写的 【Makefile】文件中,对于 myfile : myfile.c
- 对于冒号左侧:我们看做是目标文件;
- 对于冒号右侧:我们可以看做是它的依赖文件;
因此我们就可以说它们之间存在一种【依赖关系】只有【myfile.c】存在才有【myfile】
2、深入理解
上述,我们在【Makefile】中写入的是可以直接对文件进行编译,按照上节课我们所讲的在代码翻译为可执行程序的期间,经历好几个过程。
- 因此,接下来我们在通过对其代码翻译的几个过程来深入理解——》【依赖关系和依赖方法】
因为可执行文件【myfile】是依赖于汇编后形成的目标文件 【myfile.o】,但是现在我们并没有这个文件,因此就要去倒推一下如何获取这个 【myfile.o】
- 💨 对于【myfile.o】来说,它依赖于【
myfile.s
】这个经过编译之后文件,可是【myfile.s
】此时也不不存在,所以跳转到下一条依赖关系
myfile.o:myfile.s
gcc -c -o myfile.o myfile.s
- 💨 对于【myfile.s】来说,它依赖于【
myfile.i
】这个经过编译之后文件,可是【myfile.i
】此时也不不存在,所以跳转到下一条依赖关系
myfile.s:myfile.i
gcc -S -o myfile.s myfile.i
- 💨 对于【myfile.i】来说,它依赖于【
myfile.c
】这个经过编译之后文件,此时源文件存在,执行gcc指令
myfile.i:myfile.c
gcc -E -o myfile.i myfile.c
修改后如下图所示:
- 💨最后,我们回到命令行,直接输入【make】指令即可完成所有的工作
小结:
- 在【依赖关系】中,若是目标文件所依赖的文件不存在,就将这个【依赖方法】放入栈中,紧接着转到下一组 【依赖关系】,以此类推,直到找到当前目标文件所依赖的文件,此时就进行出栈操作,开始执行 【依赖方法】,最后获取得到的便是我们最初的目标文件!!!
(四)项目清理
1、代码演示
在上面的内容中我们已经对这个知识点进行应用了,这里我们在详细的展开说说
- 当我们的【Makefile】文件中写入 删除【myfile】时,此时我们返回命令行,紧接着再去执行一下【make clean】之后,我们可以发现目录下的【myfile】文件已经被删除了
- 当我们想删除代码翻译过程的形成的文件时,一样的,我们可以这样去做:
-
.PHONY:clean clean: rm -f myfile.i myfile.s myfile.o myfile
- 像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行, 不过,我们可以显示要make执行。即命令——“make clean”,以此来清除所有的目标文件,以便重编译。
2、.PHONY原理
但是一般我们这种clean的目标文件,我们将它设置为伪目标,用【 .PHONY】 修饰,伪目标的特性是,总是被执行的。
- 首先回答一下什么叫做总是被执行呢?接下来,我举个例子来说明:
- 此时上述这种现象即为不能总是被执行
- 那么我们想总是被执行有没有什么方法呢?此时我们只需在前面加上【.PHONY】即可实现这样的操作,具体如下:
- 此时,当我们再去执行上述操作时,我们就可以发现此时系统不会在提示了,可以编过:
小结:
- 【.PHONY】后面跟的目标都被称为伪目标,也就是说我们 make 命令后面跟的参数如果出现在.PHONY 定义的伪目标中,那就直接在Makefile中就执行伪目标的依赖和命令。
- 不管Makefile同级目录下是否有该伪目标同名的文件,即使有也不会产生冲突。另一个就是提高执行makefile时的效率。
(五)三个时间
首先,我们先来看看以下现象,有了这个现象的产生,紧接着我在带大家理解一下 几个时间的概念!!!
- 在上述删除操作中,我们可以发现,我们是把删除操作放在最后面的,那么此时我们放在最前面会发生什么呢?我们一起看看:
- 可以看到,在调换了位置之后我们直接【make】的话获取的就是clean对象了,想要去使用gcc编译源文件生成可执行文件就需要用到【make myfile】。
- 不过也并不是第一组依赖关系和依赖方法就一定要直接【make】,我们使用【make clean】也是可以用的
1、make的判断机制
此时,很多小伙伴就会好奇,它是怎么知道的呢?make究竟是如何知道我们的可执行文件是否需要重新编译呢?
接下来,我再给小伙伴们看一个现象:
- 上述我们已经得知,当我们不加伪目标时,当执行完一次【make】获取 【myfile】之后,当我们再次执行【make】系统是会提示 “此时已经是最新文件”;
- 但是,同样的,当我们执行完第一次【make】之后,此时去打开一下源文件,即【myfile.c】之后,我们在【make】一下,此时就又可以正常的编译通过了
解析如下:
- 我们可以将源文件和可执行文件当做是一条时间轴。对于
可执行文件
来说,它生成的时间一定是晚于源文件
的【因为中间要经过一系列编译 + 链接的过程】
接下来我们可以通过【stat】这个指令来查看源文件和可执行文件的所有属性,具体如下:
- Access time:表示最后一次访问(仅仅是访问,没有改动)文件的时间。
-
Change time:表示最后一次对文件属性改变的时间,包括权限,大小,属性等
- Modify time:表示最后一次修改文件的时间(我们比较的是这个)
从上我们可以看出 【20:43:00】是比【20:43:01】要早的。因此【make】指令才会不起作用
- 因此我们得出结论它就是通过这个Modify时间来进行对比才能判断出是否需要重新编译
那我们能否可以去欺骗一下make呢?
答案是可以的,此处我们可以使用这个【touch】指令,它除了创建文件之外还有其他功能哟!
- 若是要创建的这个文件存在,那就修改它的时间为最新的时间
- 若是要创建的这个文件不存在,那就直接创建该文件即可
接下来我们来看看现象是怎么回事:
小结:
1)当仅读取或访问文件时,access time 改变,而modify time ,change time 不会改变。
2)当修改文件内容时,modify time ,change time 会改变,access time 不一定改变。
3)当修改文件权限属性时,change time 改变,而access time ,modify time 不会改变。
这就是我们刚开始时将【Makefile】定义时提到的关于 时间关系 的详细解答!!!
(六)总结
到此,关于 make/Makefile 的知识点便全部讲解结束了。在后面的学习中,我们还将进一步的对其进行讲解!!!
接下来,我们来回顾一下本文都有哪些知识点:
- 1、首先我们简单的回顾了一下代码翻译的过程,紧接着根据其在执行过程中存在的弊端,我们引出了 make/Makefile ;
- 2、在正式介绍之前,我们了解了关于二者的背景知识,知道了【Makefile】它是一个文件,【make】它是一个命令;
- 3、接着我们简单的实践了一下,再次基础上引出了 【依赖关系】和【依赖方法】,并对其进行了讲解;
- 4、接下来,掌握了如何去清理项目中的文件的 小妙招,并知道了【
.PHONY
】修饰的文件叫做【伪目标文件】; - 5、最后我们知道了【make】判断一个文件是否需要重新编译的原理,是基于比较源文件与
目标文件
的【Modify】时间而定的。
以上便是本文的全部知识了,感谢大家的阅读!!!