【Linux】项目自动化构建工具 —— make/Makefile

简介: 【Linux】项目自动化构建工具 —— make/Makefile

前言:

在上一期的博文中,我们对 Linux 下的编译器 - gcc/g++的使用进行了详细的讲解,今天我将给大家讲解的是关于 【Linux】项目自动化构建工具 —— make/Makefile 的详细使用教程!!


(一)前情摘要

在上一期我们将 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 工具可以十分简单的整理好各个源文件之间的关系。

💥 优势:

  1. 包含多个源文件的项目工程在编译时有很多复杂且指令较长的命令行,这时我们可以通过makefile 保存这些命令行来简化该工作;
  2. 其次,make 也可以减少重新编译所需要的时间,因为 make 可以精准的识别出项目中的文件有哪些是经过修改变化的;
  3. make 维护了当前项目中各文件的相关关系,从而可以在编译前检查是否可以找到所有的文件

接下来,我们就对 【make/Makefile】 进行详细的讲解!!!


(二)背景介绍

在正式应用之前,我们先对这两个进行概念上的理解!!!

1、Makefile 的基本认识

定义:

  • MakeFile 是一个GNU推出的 编译开发工具,能为一些编译过程提供服务。
  • Makefile 的本质其实是一个文件。它是一个工程文件的编译规则,它记录了原始信息如何编译的详细信息、描述了整个工程的编译链接等规则。

功能:

  • 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的 规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂 的功能操作;
  • 举个例子,我们上述提到的文件翻译过程形成的那么多的文件,怎么把他们编译成最后能够执行的可执行程序呢?
  • 我们就可以用 MakeFile 来辅助你编译,用了MakeFile除了能提升效率,还能避免人为操作导致错误。

makefile基于两个重要关系:

  • 依赖关系:定义了如果你想要建立一个项目或者目标,有什么是必须要做的;
  • 时间关系:根据文件的属性时间。make 程序判断哪些文件在项目过程中发生了修改。然后根据上述的依赖关系,把依赖于这些修改文件的目标文件重新确立一份关系。

紧接着我们来看一下【MakeFile】的语法介绍:

<target1 > <target2>.... : <prerequisites1> <prerequisites2>...
[TAB] <command1>
[TAB] <command2>
...

命令解析:

  1. 【target】:可以是一个object file(目标文件),也可以是一个执行文件,还可以是一个标签(label);
  2. <prerequisites>:就是要生成的那个 target 所需要的文件或者目标;
  3. <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】时间而定的。

以上便是本文的全部知识了,感谢大家的阅读!!!

 

相关文章
|
11天前
|
运维 监控 Ubuntu
怎样配置Linux分析工具:atop篇
在管理Linux系统时,了解系统级监控工具是至关重要的。其中,atop是一种功能强大的工具,它允许运维人员以实时的方式监控系统运行状态,包括进程活动、内存使用、磁盘I/O以及网络负载等。atop提供了一种简洁而全面的方式来追踪系统表现和资源消耗情况,使得性能分析变得简单而直观。
|
10天前
|
Shell Linux C语言
|
16天前
|
数据挖掘 大数据 Linux
探索Linux中的snice命令:一个虚构但启发性的数据分析工具
`snice`是一个想象中的Linux命令,用于低优先级地从大数据集中抽样数据。它结合`nice`和`sampling`,支持多种抽样策略,如随机和分层。参数包括指定样本数、策略、输入输出文件和进程优先级。示例:`snice -n 1000 -s random -i large_log.txt -o sample_log.txt`。使用时注意资源管理、数据完整性及权限,并与其它工具结合使用。虽然虚构,但体现了Linux工具在数据分析中的潜力。
|
2天前
|
Linux 持续交付 Apache
在Linux中通过ansible自动化部署apache服务
【7月更文挑战第11天】Linux中用Ansible自动化部署Apache服务:1. 确保Ansible已安装;2. 在`/etc/ansible/hosts`配置目标主机,如\[webservers\] server1 server2;3. 编写Playbook `apache_deploy.yml`更新系统并安装、启动Apache;4. 执行`ansible-playbook apache_deploy.yml`。适用于快速部署至多台服务器,减少配置错误和成本。
|
9天前
|
存储 缓存 安全
systemd-ask-password:Linux中的安全密码获取工具
`systemd-ask-password`是Linux的密码获取工具,安全收集服务或应用所需的密码。它支持TTY和密码代理输入,有隐藏输入、密码缓存功能。参数如`--no-tty`、`--id`、`--timeout`等可定制交互方式。示例包括直接在TTY请求或通过代理。注意事项包括安全环境、权限管理和密码管理。最佳实践涉及定期更新和使用强密码,以及日志审计。
|
10天前
|
存储 运维 监控
怎样配置Linux分析工具:kdump篇
在运维的世界里,服务器的稳定运行是生命的灯塔,一旦遭遇异常重启,便是暴风雨来临的预兆。作为一名运维工程师,深知在这场与故障斗争的战役中,武器的锋利至关重要。今天,我要介绍的主角/工具——kdump,正是这样一款能在风雨来临之际,为我们捕获那一闪而过的真相的工具。
|
15天前
|
Java Linux C++
【Linux】Make和Makefile快速入门
【Linux】Make和Makefile快速入门
15 0
|
22天前
|
数据采集 存储 API
在信息时代,Python爬虫用于自动化网络数据采集,提高效率。
【7月更文挑战第5天】在信息时代,Python爬虫用于自动化网络数据采集,提高效率。基本概念包括发送HTTP请求、解析HTML、存储数据及异常处理。常用库有requests(发送请求)和BeautifulSoup(解析HTML)。基本流程:导入库,发送GET请求,解析网页提取数据,存储结果,并处理异常。应用案例涉及抓取新闻、商品信息等。
51 2
|
12天前
|
监控 jenkins 持续交付
Python进行自动化
【7月更文挑战第9天】 Python在自动化部署中发挥关键作用,提供如Fabric、Ansible、Docker SDK和Kubernetes Client等工具。自动化部署提高效率、减少错误,确保部署一致性和可控性。例如,Fabric库简化了远程服务器的部署任务,如在多台服务器上执行Git拉取和Docker容器启动。持续集成/部署(CI/CD)结合Jenkins和Fabric,实现代码变更自动构建、测试和部署。监控和持续改进是确保应用稳定性和质量的关键,通过定期回顾、度量分析以及有效监控系统来优化流程。
18 1
|
17天前
|
数据采集 存储 监控
python 10个自动化脚本
【7月更文挑战第10天】
37 3