Linux-Linux基础开发工具3-自动化创建make/Makefile

目录

1.背景

2.基本使用

3.理解Makefile的语句

4.原理

5.Makefile的改进

6.用变量的形式编写Makefile


1.背景

make是一个命令工具,是一个解释makefile中指令的命令工具,大多数IDE都有这个命令(⽐如:Delphi的make,Visual C++的nmake,Linux下GNU的make)。可⻅,makefile都成为了⼀种在⼯程⽅⾯的编译⽅法。Makefile有一个好处-"自动化编译",一旦写好,只用一个make命令,整个工程完全自动编译,极大提高了效率

make是一个Linux系统内置的命令,makefile是一个需要工程师自己建立的一个文件(也可以写为Makefile )。而会不会写makefile,从⼀个侧⾯说明了⼀个⼈是否具备完成⼤型⼯程的能⼒⼀个⼯程中的源⽂件不计数,其按类型、功能、模块分别放在若⼲个⽬录中,makefile定义了⼀系列的规则来指定,哪些⽂件需要先编译,哪些⽂件需要后编译,哪些⽂件需要重新编译,甚⾄于进⾏更复杂的功能操作。

我们将make和makefile搭配使用,完成项目的自动化构建。

2.基本使用

上篇博客讲解到如何使用Linux的gcc命令,这期我们就可以用它来作为例子。

以下为演示步骤:

code.c的内容为:

我们先:

bash 复制代码
touch Makefile

此时用vim Makefile写下如下代码:

保存退出,我们使用以下指令:

bash 复制代码
make

有些人可能会出现以下问题:

这是因为我们第二行在编辑时不能用四个空格,而需要用Tab键才行(或者之前在设置vim时把Tab转化为四个空格),可以用以下命令验证:

bash 复制代码
cat -A Makefile

如果为:

就代表不对。

如果此时我们vim Makefile把第二行代码开头用Tab也无法修复,此时我们该考虑vim的问题了。

此时我们:

bash 复制代码
vim .vimrc

会有这一行:

我们需要在最后添加这一行:

bash 复制代码
autocmd FileType make set noexpandtab

变为这样:

此时我们用:

bash 复制代码
cat -A Makefile

出现:

其中^I代表为Tab,此时就是正确的。

这个时候正常make有:

ls -l查看make前后变化有:

如果出现了名字为code的可执行文件,则代表make的操作成功完成。

此时我们:

bash 复制代码
./code

会有:

但是我们发现我们还需要手动删除产生的code文件,很麻烦,所以我们:

bash 复制代码
vim Makefile

把Makefile改成:

保存退出,此时我们再执行:

bash 复制代码
make clean

会有:

也就是说make clean可以直接删除code文件了。

3.理解Makefile的语句

我们可以画出这样的图:

依赖关系+依赖方法才能达到我们编译程序的目的

如何理解?

比如:我们现在应该在上大学,假设生活费是爸爸提供的,现在我们没生活费了,需要给爸打电话找他要生活费,我给爸打电话说:我是你儿子。然后把电话挂掉了。

此时,我们的目的(编译程序的目的)是找爸爸要钱,然后"我是你儿子"这句话的意思是:表明依赖关系。但是我们并没有说我们要干啥(没有提供依赖方法),此时爸爸就不理解我们的意思(没有编译程序),也就无法成功要到生活费(编译程序失败)。因此,只有依赖关系是不足以达到目的的!

再比如:我给其他人的爸爸打电话要生活费,给他爸说:给我生活费。

此时,其他人的爸爸可能会觉得:你是谁啊,我打生活费给你干啥,因为我们没有提供依赖关系(我和他的关系),他也就不可能给生活费,也就是无法成功编译程序。因此,只有依赖方法是不能达到目的的!

只有我们对自己爸说:我是你儿子,给我生活费。此时我们提供了依赖关系和依赖方法才能成功的编译程序,你爸才知道你的目的。

编译程序失败也有两种情况:依赖关系不合理(给其他人打电话),依赖方法不合理(让别人给你100个亿)。

本质上,这个世界就是由(先)依赖关系+(后)依赖方法 才能运行的,求别人办事也是先送礼(建立依赖关系),再表明自己的目的(提供依赖方法)才能成功完成。

如何理解,见下图:

伪目标:它有依赖关系(可不用写),因为有目标文件,只不过依赖文件列表为空

如果此时我们再make:

为啥make不执行第二个步骤,而只执行第一个?

因为make它在识别Makefile时,会从上往下扫描,所以它遇到的第一个目标就是要它默认生成。所以:makefile默认只形成一个目标!

我们一般把我们需要的可执行程序的方法放在第一个

伪目标是什么?

如果一个对象被.PHONY,它表示被修饰的目标是一个伪目标(忽略时间变化 ),伪目标总是被执行的!

如:此时我们先把code文件删除

如果我们重复执行:

bash 复制代码
make

会有:

会发现没办法执行了,这是因为:形成的code已经是最新日期了,不让再编,这就叫不被执行!(与.PHONY修饰的总被执行对立)。

即使我们修改一下code.c代码变成:

此时我们再重复make:

此时只能执行一次make,这代表:总是不被执行的!

这是因为编译器发现:源文件已经被编过了,所以它觉得没必要编了。如果有1000个甚至更多文件,如果重复make是很浪费的,所以编译器就只对编过的代码的文件进行make,若1000个文件只有1-2个被修改了,此时重复make,效率会很高!

我们还可以:

bash 复制代码
vim Makefile

修改Makefile代码为:

此时我们保存退出,然后重复执行:

bash 复制代码
make code

就有:

也就代表此时的gcc -o code code.c可重复(总是)被执行的!

Makefile的最佳实践:可执行程序,不需要修饰成为PHONY(gcc/g++这种程序),clean修饰成为总是执行的!

4.原理

那么make是怎么知道这个文件有没有被编写过呢?

我们使用stat指令可以查看某文件的信息和各个时间,如:我们想要查看code.c的信息:

bash 复制代码
 stat code.c

此时有:

我们会发现有很多的时间:Access(文件最近被访问的时间)、Modify(文件内容的最近修改时间)、Change(文件属性最近修改时间(只改属性用chmod))、Birth(文件被创建的时间)。

只要我们访问了文件:vim,cat,more,less,Access时间都会被修改:

那为啥后面不会修改了?

因为系统会看你修改时间与Access时间进行对比,只有Modify的时间比Access新才会修改,你可以试着改code.c的代码试一下,会发现我们用cat和more和less都会修改!

那为啥make会知道我这个文件是否已经被make过了呢?

实际上我们的可执行文件code也可以用stat查看Modify-time:

而我们的源文件也有一个Modify-time:

make在执行gcc操作之前会查找生成的文件是否存在,如果存在,就会对比生成的文件的Modify-time,如果源文件的Modify-time更新,那么就代表这个文件在gcc完生成可执行文件之后还修改过,也就需要再次make才行;如果可执行文件的Modify-time更新,代表在生成可执行文件之后,源文件并没有修改过,也就没必要再次执行make;如果生成的文件不存在,那么就直接执行make操作!

那么为啥.PHONY修饰过的指令就能重复执行呢?

之前我们说.PHONY时多提了一嘴:.PHONY会忽略时间变化!也就是说.PHONY会让make忽略源文件和可执行文件 Modify-time时间对比。

5.Makefile的改进

我们学gcc的时候可以发现:

bash 复制代码
gcc code.c -o code

可以分成以下步骤:

bash 复制代码
gcc -E code.c -o code.i
gcc -S code.i -o code.s
gcc -c code.s -o code.o
gcc code.o -o code

那么我们就可以把这些步骤写到Makefile里面:

此时我们make一下会有:

此时我们除了会形成code,还有code.i,code.o,code.s文件(临时文件),为啥要倒着写呢?

由于make会从上往下扫描,当执行到第一步:

因为code依赖code.o,但是发现code.o文件没有,但是又发现有一个:

此时code.o依赖code.s,但是发现code.s没有,但是又有一个依赖关系:

此时code.s依赖code.i,但是发现code.i没有,但是又有一个依赖关系:

此时code.i依赖code.c,发现code.c有,因此执行:

然后此时code.i就生成了,因此可以执行:

因此可以执行:

因此可以执行:

最后生成了code以及以.i,.s,.o结尾的文件!因此我们发现:要先找依赖关系才能找依赖方法执行!

这个过程叫:makefile自动推导的过程。那么该如何推导?

make推导中,可以想象为一个栈结构 。在识别到code.o有依赖后,除了去找code.o的依赖关系,还会把上一层的依赖方法入栈,继续如此。若发现:依赖对象不存在(如code.i不存在),则会直接结束 并报错 。否则,执行完目标,出栈,然后完成目标(根据依赖关系先进行正向解析,将解析路径中的依赖方法依次出栈,最后类似递归到结尾再出栈)。这就是会形成这么多文件而且能逆向执行的原因!

若想一次把这些产生的文件删除,可以:

bash 复制代码
vim Makefile

改它为:

此时可以:

bash 复制代码
make clean

有:

未来想要的Makefile最佳实践为:

6.用变量的形式编写Makefile

我们会发现:这些gcc和code重复出现多次,在C/C++中我们喜欢把它们命名为变量,在Makefile里,我们喜欢在它里面用变量的形式把选项编译出来。

如:我今天想要形成可执行程序为BIN,Makefile不需要显式定义数据类型,只需要告诉可执行程序称为code.exe,因此可以把Makefile写成如下形式:

此时我们:

bash 复制代码
make print

有:

echo code.exe代表我们所执行的指令,code.exe为执行指令的结果。

那为啥code.exe不是一个文件名吗,为啥打印的是字符串?

在Makefile定义变量时,会把所有变量全当成字符串!

这样显示也太不方便了,我咋知道哪个是结果?

我们可以把Makefile改成这样:

加个@目的是:关闭回显,因此,

bash 复制代码
make print

结果为:

因此,我们可以把之前的那个Makefile改为:

保存退出,此时我们再make有:

此时我们的程序和之前执行的一样。

如果有人学了g++,那么我们也可以把gcc换成g++。变量一定要写到Makefile的最前面!

那么此时这个就是通用的Makefile,可以随时替换,只需要改变量就行:

bash 复制代码
BIN=code.exe
CC=gcc
SRC=code.c
FLAGS=-o
RM=rm -f
$(BIN):$(SRC)
	$(CC) $(FLAGS) $(BIN) $(SRC)

.PHONY:clean
clean:
	$(RM) $(BIN)

在Makefile里的变量相当于我们C语言的宏和全局变量

但是我们又发现:BIN和SRC在一个方法里出现了两次,可以改为:

其中@代表目标文件(依赖关系),^代表依赖文件列表。或者说@代表':'左边的内容,^代表':'右边所有的内容,但这个与我们最终理想的Makefile不一致,应该改为:

这个我们改写的比较多,我依次讲解一下:

wildcard是makefile提供的函数,帮我们获取当前目录下的所有C语言文件(源文件)。

因为我们之前的代码只能编译code.c文件,此时这个就能更加全面的编译C语言文件了!

这个会将所有的SRC同名.c文件替换成为.o的目标文件列表,即把SRC的.c文件全部替换成同名.o文件。

%.c:当前目录下以.c结尾的源文件.%.o:代表当前目录下同名源文件形成的.o文件。

<意思:相当于把依赖关系所依赖的源文件一个一个拿过来执行形成依赖方法,一个一个喂给(CC),让它编译同名.o,而^代表把(OBJ)(即把右边所有依赖文件列表)总体来形成:左边文件,实际上,<即%.c。

那么这样我们就可以,创建100个code.c文件:

bash 复制代码
touch code{1..100}.c

然后一并:

bash 复制代码
make

有:

我们发现实际上只有一个可执行文件:

这是因为所有的codex.c文件都自动编译形成只有一个文件名为code.exe的可执行文件(因为BIN只有一个),而且我们在编译的时候,gcc -c的顺序是由wildcard的排列顺序决定的。

若看不懂,可以改为:

这样就能看的懂了。

想要删除这些文件直接make clean即可:

想删除code1-code100.c文件有:

bash 复制代码
rm -f code{1..100}.c

后面的有点难理解,我们主要要理解最后一步:把所有的.o文件都形成一个可执行文件即可理解!

相关推荐
Zhu7582 小时前
在docker环境部署frp
运维·docker·容器
hhwyqwqhhwy2 小时前
Ubuntu22.04 安装vitis
linux
Black蜡笔小新3 小时前
从智慧社区到雪亮工程:国标GB28181公网平台EasyCVR如何构建视频“一张网”?
人工智能·自动化·easycvr
运维行者_3 小时前
企业带宽监控工具实战:网络流量分析与异常排查的5个关键能力
运维·服务器·开发语言·网络·分布式·后端·php
刚入门的大一新生3 小时前
Linux-Linux第一个系统程序-进度条
linux·运维·服务器
wuminyu3 小时前
JDK21解决虚拟线程IO阻塞原理剖析
java·linux·c语言·jvm·c++
梦想的旅途23 小时前
企业微信自动化实战:客户资料自动修改与备注同步
运维·自动化·企业微信
梦想的旅途23 小时前
企业微信自动化实战:客户通讯录备份与关系自动迁移
运维·自动化·企业微信
茫忙然3 小时前
Linux提权辅助查询工具——BinLens的安装和使用
linux·提权·binlens
FlightYe3 小时前
linux系统编程(八):阻塞与非阻塞IO对比
android·linux·运维·服务器·github·vim