【Linux】自动化构建,有Make就够了

前言

在上一讲中,我们介绍了Linux下的编译器 - gcc/g++的使用,本节我们来介绍一下如何使用make/Makefile实现项目的自动化构建

  • 知道了如何在Linux上编译C语言代码,而且清楚了可执行文件a.out的由来,是从
  • test.c经过预编译到test.i
  • test.i经过编译到test.s
  • test.s经过汇编到test.o
  • test.o经过链接到a.out

可是看着这些操作,如果说我们只编译一个文件还可以说过去,如果说要编译100个,1000个,1w个呢?那我们就要修改100次,1000次,1w次,那不是太麻烦了吗?这就要用到我们的make自动构建编译了。

Make和Makefile背景介绍

Makefile是什么

  • Makefile 是一个文件。它是一个工程文件的编译规则,它记录了原始码如何编译的详细信息、描述了整个工程的编译链接等规则。
  • Makefile 带来的好处就是——“自动化编译"。一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率🚀
  • 先来看一下Makefile的【语法】:
target(目标文件):文件1 文件2(依赖文件列表)		//依赖关系

<Tab>gcc -o 欲建立的执行文件 目标文件1 目标文件2		///依赖方法
	 command
	 ...
	 ...

  • target就是我们想要建立的信息,一般称作目标文件。而后面的依赖文件列表就是具有相关性的 object files,也就是目标文件所依赖的文件(可以是一个或多个,也可以没有)
  • 然后看一下Makefile的【规则】:
  • 目标文件和依赖关系之间用冒号隔开
  • 目标文件可以是一个源文件,可执行文件,甚至可以是一个标签【后面会提到的伪目标】
  • 依赖方法前面必须加Tab空格键

make又是什么?

  • make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,Makefile都成为了一种在工程方面的编译方法

【总结一下】:make是一条命令,makefile是一个文件,两个搭配使用,完成项目自动化构建👈

依赖关系和依赖方法

  • 提出一个问题:如果我想把test.c编译成一个可执行程序,那么我的编译过程是怎么样的,或者说这个可执行程序是通过谁编译过来的,这是有一个关系的。
  • 比如就需要使用到下面的这句gcc指令gcc -o mytest test.c 👉它叫做【依赖方法】

在这里插入图片描述

感性理解【父与子👨】

  • 今天呢,是这个月的28号的了,家里面在月初给你的2000块钱差不多也花完了,于是这两天只能吃土,此时你打开微信后看到和老爸的聊天框,于是就想着和老爸要点钱【毕竟儿子向父亲要钱天经地义😀】
  • 这里的老爸和儿子指的就是[依赖关系]
  • 儿子向老爸要钱指的就是[依赖方法]

下面辨析几种依赖关系与依赖方法
❌错误的依赖方法

  • 此时你打电话和你老爸说:“我是你儿子,你帮我写作业。”
  • 以上这句话就是依赖关系正确,但是依赖方法不正确。想啥呢,还写想让老爸帮你写作业,老爸帮儿子写作业无法执行

❌错误的依赖关系

  • 此时你拿起室友的手机和他爸爸打电话说:“我是你儿子,你给我点零花钱。”
  • 以上这句话就是依赖方法正确,但是依赖关系不正确。因为别人的老爸没义务给你钱

✔ 正确的依赖关系与依赖方法

  • 经历了种种挫折后,你重新拿起手机说:“老爸,我是你儿子,可以给我点零花钱吗?”
  • 上面这种说法就是完全正确的依赖关系与依赖方法

深层理解【程序的翻译环境 + 栈的原理】

  • 看完了【依赖关系】与【依赖方法】的感性理解,相信你对它们有了一定程度的认识,接下去深入地来了解一下它们之间的关系
  • 在前一篇文章中的gcc工具中我们已经详细讲解了一个.C文件编译成可执行程序的过程Linux下的gcc/g++编译器与动静态库
  • 所以我们现在的可执行程序的依赖关系就是.o文件,依赖方法就是生成-o文件的指令,依次类推下面的依赖关系和依赖方法
  1 mytest:test.o
  2     gcc test.o -o mytest

  • 开头提到过源程序是如何经过一步步的编译来形成可执行文件的吗,因为可执行文件mytest是依赖于汇编后的目标文件test.o的,但是现在我们没有这个文件,因此就要去倒推一下如何获取这个test.o
  • 对于test.o来说,它依赖于test.s这个经过编译之后文件,可是【test.s】不存在,所以跳转到下一条依赖关系
  3 test.o:test.s
  4     gcc -c test.s -o test.o

  • 对于test.s来说,它依赖于test.i这个经过预编译之后的文件,可是【test.i】不存在,所以跳转到下一条依赖关系
  5 test.s:test.i
  6     gcc -S test.i -o test.s

  • 对于test.i来说,它依赖于test.c这个源文件,查找后发现源文件存在,于是开始执行gcc命令
  7 test.i:test.c
  8     gcc -E test.c -o test.i

  • 以下就是我们需要在Makefile中修改的【依赖关系】与【依赖方法】
    在这里插入图片描述
  • 最后来到命令行中执行一下【make】命令,便完成了所有的编译,对于之前一步步地写这个编译的过程,真的是来得方便很多

在这里插入图片描述

  • 可以看到,如果我们想获取最终的可执行程序,需要先找到.o文件,如果需要找到.o文件,就需要找到.s文件如果需要找到.s文件,就需要找到.i文件。这种先执行的语句(从上往下执行),要先找到后面的语句执行才能执行前面的语句,这就符合栈的先进后出原则
    在这里插入图片描述

这个Makefile这么牛,我们将gcc编译的顺序修改一下,会发生什么呢?
在这里插入图片描述

  • 可是呢,当我再去make的时候,却发现这个执行的顺序还是按照没打乱之前的位置,这说明了一点:make会自动推导Makefile中的依赖关系形成栈式结构

项目清理【伪目标】

  • 平时我们在进行各种操作之后目录中都会出现很多文件,此时当我们不想要这些文件的时候,就得去一一删除,显得尤为麻烦,如果编译可以使用Makefile来自动化构建,那清理项目中的文件可不可以呢,我们来看看
  • 此时我们在Makefile中增加一个【清理】功能
    在这里插入图片描述
  • 来看一下是否可以达到清理的目的
    在这里插入图片描述
  • 当我想使用清理功能的时候,并没有像自动化编译那样直接make,而是在make后面加上了一个clean,这是为什么呢?
  • 新加上的.PHONY是什么?它对clean而言意味着什么?

伪目标的作用

PHONY是一个伪目标,Makefile中将.PHONY放在一个目标前就是指明这个目标是伪文件目标。其作用就是防止在Makefile中定义的执行命令的目标和工作目录下的实际文件出现名字冲突

  • 也就是下面这句,此时的clean被.PHONY修饰了,那么它就可以反复执行它的依赖方法
.PHONY:clean

  • 可以看到对于目标文件clean来说,的依赖文件列表为空它,上面我们也有提到过它可以为空
 11 clean:
 12     rm -f test.o test.s test.i

  • 所以只要你一直使用【make clean】,它便会反复地执行rm -f test.o test.s test.i
  • .PHONY修饰的文件,并不会真正的去生成这个文件,也就是说操作系统并不会去像上面一样真正去找是否有这个文件,这里就是一个标记,我们的目的就是为了执行这个标记的依赖方法
    在这里插入图片描述
  • 可以看到,我们可以通过lean这个标签,一直执行rm xx 这个方法,那问题就来了,不是标签的方法可以一直执行吗?这里我们挖个坑。
  • 现在我们再来做一件事情,我们把.PHONY这个修饰去掉,看看clean能不能一直执行
    在这里插入图片描述
    在这里插入图片描述
  • 可以看到,即使是去掉.PHONY这个修饰,我们依然可以一直执行clean这个标签的依赖方法

那就有同学问:这是为什么呢?为何clean不加.PHONY修饰也可以多次执行

  • >原因就在他的依赖关系为空,回忆一下我们之前生成mytest这个文件就必须要先生成test.o文件,这里我们clean没有和任何文件有关系。t
  • 实在不懂就这么理解:我clean是个孤儿,我没有父亲,那我想什么时候写作业就啥时候写作业,因为我没有父亲这个关系的人物来管理我

.PHONY伪目标的原理

  • 可是呢,对于其他的指令就不行了,例如我们上面说到过的gcc去编译一个文件的过程
    在这里插入图片描述
  • 我们试着在【mytest】前面加上一个.PHONY的修饰试试
    在这里插入图片描述
  • 然后再去试试能不能进行反复使用【这里给读者详细解释一下.PHONY修饰的原理】
  • 首先可以清楚的是,如果我们加了.PHONY修饰,正常情况我们应该能一直使用make命令执行这个方法,但是这里却不行了,可以看到我们执行后is up to date报错(意思说xx是最新的)
  • 因为我们在经过一次完整的编译过程后,生成的test.o文件是最新的,如果这时候我们再次对他进行重新编译,系统为了避免无效的重复编译,会查看他的修改时间,如果说修改了就重新编译,如果说没有修改文件的数据,那么系统将不会重新编译,因为系统编译是需要消耗和时间的,为了避免这种无效的编译,系统会自己判断。
  • 这个时候我们引出一个车ACM时间的概念
  • 用stat + 文件名查看
    在这里插入图片描述
  • 我们之前说过:文件 = 内容 + 属性
  • 这里修改文件内容就会更新Modify时间
  • 修理文件属性就会更新Change时间
  • Access时间可以测试一下,我们cat查看到达一定次数他才会更新,因为如果早期Linux系统一天进行的大量操作就是查看文件,但是如果每次查看不修改内容和属性都更新这个时间,就会造成大量的IO操作,所以为了效率的提升,就设置查看一定次数才能更新Access时间
  • 接下来看一下mian.c的内容
    在这里插入图片描述
  • 修改一下文件内容

在这里插入图片描述
在这里插入图片描述

  • 可以看到就可以重新make编译了

在这里插入图片描述

  • 可以看见我们的ACM时间更新到了最新

其实除了真的去修改想重新编译文件的内容来更新ACM时间之外,如果我们在一个需要不对文件进行修改就更新ACM的场景还有一个方法

  • 此处我们可以使用这个【touch】指令,它除了创建文件之外还有其他功能
  • 若是要创建的这个文件不存在,那就将其创建出来
  • 若是要创建的这个文件不存在,那就将其创建出来

扩展语法

  • 那我编译一个文件可以这么写,那如果现在有100个文件呢?他们的文件名不一样,那我们要写100个makefile文件吗?不行吧
    在这里插入图片描述
  • 其中$就相当于是访问变量的操作符,通过定义变量的方式动态更新文件名,我们就能做到100个不同文件名,1个makfile文件搞定。

end

感谢大家的阅读,希望对你们有帮助,谢谢。

评论 51
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值