【Linux】自动化构建工具--make/Makefile

欢迎拜访:Madison-No7个人主页
文章主题:自动化构建工具--make/Makefile
隶属专栏:从0到1掌握Linux
写作日期:2025年10月24号

目录

前言:

一、什么是make和makefile

makefile带来的好处:

使用make和makefile:

依赖关系:

依赖方法:

二、make的工作原理

[2.1 make的自动化推导过程:](#2.1 make的自动化推导过程:)

[2.2 项目清理](#2.2 项目清理)

[2.3 .PHONY伪目标:](#2.3 .PHONY伪目标:)

[2.4 源文件的重新编译:](#2.4 源文件的重新编译:)

[2.5 makefile的相关语法:](#2.5 makefile的相关语法:)

[2.5.1 makefile对单文件的编译:](#2.5.1 makefile对单文件的编译:)

[2.5.2 makefile对多文件的编译:](#2.5.2 makefile对多文件的编译:)


前言:

在一个大型的项目中,源文件不计其数,如果我们手动使用gcc/g++去编译源代码,那将会变得十分麻烦,而且容易出错。所以在Linux中有专门的自动化构建工具---make/makefile,来解决多个源文件编译的问题。

一、什么是make和makefile

⼀个⼯程中的源⽂件不计数,其按类型、功能、模块分别放在若⼲个⽬录中,makefile定义了⼀系列的规则来指定,哪些⽂件需要先编译,哪些⽂件需要后编译,哪些⽂件需要重新编译,甚⾄于进⾏更复杂的功能操作。make是一个命令行工具,运行时会读取当前目录的 makefile,根据规则自动执行编译、链接等命令.。

📖简单来说, make是⼀条命令,makefile是⼀个⽂件,两个搭配使⽤,完成项⽬⾃动化构建。

makefile带来的好处:

"⾃动化编译",⼀旦写好,只需要⼀个make命令,整个⼯程完全⾃动编译,极⼤的提⾼了软件开发的效率。

使用make和makefile:

1.创建makefile文件并进行文本编辑:

makefile中的m大小写都可以。

2.定义依赖关系和依赖方法:

3.执行make命令:

依赖关系:

test:test.c #:左边表示的是目标文件,:右边表示目标文件所依赖的文件列表。

依赖关系是指一个事物的存在或运行必须以另一个事物为前提,比如test可执行文件依赖test.c源文件,因为必须通过test.c翻译才能生成可执行文件。

依赖方法:

gcc -o test test.c

依赖方法是指当存在依赖关系时,如何按顺序、按规则处理这些依赖,先确保test.c存在,再执行程序的翻译过程。

💡 依赖关系和依赖方法在生活中的例子:

比如你今天要做一份宫保鸡丁:

  • 依赖关系:宫保鸡丁依赖 "炒好的鸡丁""炒好的花生""切好的黄瓜丁";"炒好的鸡丁" 依赖 "切好的鸡丁" 和 "调好的料汁";"切好的鸡丁" 依赖 "完整的鸡胸肉" 和 "菜刀"。这是一条依赖链。
  • 依赖方法:先处理最底层依赖(用菜刀把鸡胸肉切成丁),再处理中间依赖(用切好的鸡丁和料汁炒鸡丁),最后处理最终依赖(把炒好的鸡丁、花生、黄瓜丁混合翻炒,做出宫保鸡丁)。

对应的makefile就是:

复制代码
宫保鸡丁: 炒鸡丁 炒花生 切黄瓜丁
    把所有食材混合翻炒出锅

炒鸡丁: 切鸡丁 料汁
    用鸡丁和料汁翻炒

切鸡丁: 鸡胸肉 菜刀
    用菜刀把鸡胸肉切丁

二、make的工作原理

2.1 make的自动化推导过程:


观察上图可以发现,执行make指令时,实际运行的命令顺序与makefile中的编写顺序相反。这种反向执行机制源于文件间的依赖关系:要生成test需要test.o,但该文件不存在;而makefile中提供了生成test.o的方法,不过这又需要先有test.s文件。同样地,生成test.s又依赖于test.i文件。因此,完整的构建流程必须从test.i开始,依次生成test.s、test.o,最终才能得到test目标文件。这种依赖关系实质上构成了一个栈式结构,决定了命令的执行顺序与编写顺序相反的特性。

🌟注意:

由于是自动化推导,makefile文件中的依赖方法可以是任意顺序,但是不能缺少。还可以这样:

上面只是为例演示make的自动化推导过程,平时我们编译源文件不会这样定义。

2.2 项目清理

我们有时候需要清理文件,如果使用rm +要清理的文件名,如果要清理很多的文件,难道我们每次都要自己手动敲一遍吗?那就太麻烦了,同样可以使用自动化工具make与makefile,帮助我们清理。

同样需要定义依赖关系和依赖方法,清理文件这个操作,不需要依赖任何其他文件,所以:右边的依赖关系什么也没写,直接执行对应的操作,也就是执行依赖方法,依赖方法可以自己指定删除哪些文件。clean是自己命名的,表示清理的意思,大家也可以根据自己的喜好,取其他的名字。有了上面的依赖关系和依赖方法,此时只需要在命令行中执行make clean指令,就可以把自己指定要删除的文件删除。

📖为什么删除时make后面要加clean?

因为make会自顶向下扫描makefile,单独的make指令,会默认执行第一个依赖方法。

2.3 .PHONY伪目标:

.PHONY 用于声明 "伪目标" (Phony Target)。这些目标不是实际的文件,而是代表一组命令(比如清理文件、编译全部、运行程序等)。如果要想让makefile文件中对应的依赖方法总是被执行,可以用.PHONY对相应的标签进行修饰。

比如:

bash 复制代码
.PHONY:clean                                                                                                                                                                            
clean:
	rm -rf test.i test.s test.o test

📖总是被执行意思:

强制让目标被执行,即使该目录下存在同名文件。如果目录中没有与目标重名的文件 ,即使不加**.PHONY**,make clean 也会每次都执行对应的命令.

test目标文件没有被.PHONY修饰,只能执行一次test对应的gcc命令。

📖为什么不能连续编译源文件:

如下图,当执行完一次make,对源代码编译后,再去执行make,就不会对源代码重新编译。

对于一个源代码,进行一次编译,得到一个可执行文件后,如果没有对源代码做任何修改,再去对源代码进行编译,make会觉得没必要,不会重新编译,另外如果有1000个源文件需要编译,而我们只对其中的几个做了修改,编译器只对修改了的源文件重新编译,而对没改的源文件,编译器不做处理,这也是为了提升编译的效率。而加上.PHONY修饰test,make可以执行无数次,也就是强制make执行编译,即使该目录下存在test可执行文件了,但是这样是没有必要的。

🌟 结论:

一般情况下,对编译操作不加.PHONY进行修饰,而是对清理操作加.PHONY修饰,因为在该目录下当有很多文件时,如果刚好有个clean文件与makefile中的clean目标文件同名了,make会认为 "目标已完成",不会执行 rm命令,导致清理失败,加上.PHONY修饰,就会无视同名。

2.4 源文件的重新编译:

📖make在什么情况下,才会对源文件重新编译呢?(除开加.PHONY修饰)

执行make命令,自动生成一个可执行文件,如果还想对源文件编译就不行了,因为没有对源代码做任何修改,去对源代码进行编译,编译所生成的可执行文件与之前所生成的也一样,make会觉得没必要,不会重新编译。所以只有修改了源文件的内容,make命令所生成的可执行文件才与之前的不一样。

📖那编译器怎么知道源文件的内容修改了呢?

在Linux下,一切皆文件,文件=内容+属性,而与文件相关联的时间有三个,简记为ACM,A(Access)、C(Change)、M(Modify)。而Modify的时间与文件的内容修改有关,Change时间与文件的属性修改有关。也就是说Modify的时间改变了,就说明文件的内容改变了,就可以对源文件重新编译了;

📖查看文件时间:

**stat test:**查看mytest文件的有关时间。

  • Access:常指的是⽂件最近⼀次被访问的时间。

  • Modify: 内容变更,Modify时间更新。

  • Change:属性变更,Change时间更新。

这三个时间是相互关联的,有的操作可能会同时更新多个时间。

⏳ 修改文件的内容后,查看文件的有关时间:

修改文件的内容,这三个时间都会更新,因为修改文件内容,首先要访问该文件,其次修改后,文件的大小(属于文件属性)会发生变化,所以这三个时间都会更新。

⏳ 单纯的修改文件属性,只有Change时间会变化。

⏳ 单纯的访问文件:

在 Linux 的早期版本中,每当文件被访问时,其Access 时间都会立即更新;这些时间信息都存储在计算机的硬盘上,而硬盘都属于外部设备,进行读写操作会比较慢,过高频率的更新一个文件的Access,当整个系统在被多个用户使用的时候,就会有大量的Access更新行为,这些行为都会往硬盘中写数据,这就会导致整个系统的运行速度下降。而当前的 Linux 版本,通过策略优化:仅在两种场景下更新 Access:

  • 文件的修改(Modify)时间或状态变更(Change)时间,晚于当前 Access 时,与Access时间间隔还需超过 24 小时;
  • 距离上次 Access 更新已超过 24 小时。

📖手动更新文件时间:

  • touch test.c:将test.c文件的所有时间更至最新。
  • touch -m test.c:将test.c文件的Modify时间更至最新。
  • touch -a test.c:将test.c文件的Access时间更至最新。
  • touch -c test.c:将test.c文件的Change时间更至最新。

🌟 总结:

如果要对一个已经编译的源文件重新编译,就需要改变源文件的内容,也就是改变源文件的Modify Time,查看源文件的相关时间用stat指令,改变源文件的Modify时间,可以修改源文件的内容,还可以使用touch指令,手动修改,还可以在makefile中对目标文件加.PHONY修饰。

2.5 makefile的相关语法:

2.5.1 makefile对单文件的编译:

执行make后,test因为有.PHONY修饰,所以无论是否存在同名文件,都会执行对应命令。test目标下的echo $(BIN)会将BIN替换为code,执行echo code后输出code。

如果不想把echo $(BIN)命令在make执行的时候回显出来,可以在命令前加@符号。

📖初始版本的makefile写法:

📖特殊变量:

  • $@: 代表⽬标⽂件名。
  • $^: 代表依赖⽂件列表

更优雅的写法:

  • %.o:%.c : %.c 展开当前⽬录下所有的.c。****%.o: 同时展开同名.o

  • %<: 对展开的依赖.c⽂件,⼀个⼀个的交给gcc

我们对多文件编译的时候,有一个习惯,喜欢把.c文件先编译成.o文件,再通过链接生成可执行文件,所以%.o:%.c是为了应对多文件编译引入,$<也是为了对多文件的编译引入,<符号它的指向就像.o < .c,就是把.c文件编译成.o文件。

2.5.2 makefile对多文件的编译:

📖makefile模板:

如果有很多的源文件需要编译,不可能打出所有的.c源文件,makefile高级的地方就体现在makefile有自己的wildcard函数,可以获取当前目录下的所有.c文件。


完。

今天的分享就到这里,感谢各位大佬的关注,大家互相学习,共同进步呀!

相关推荐
讯捷蓝达1 小时前
戴尔R720xd服务器卡死在 “Configuring Memory...Done“,换了内存还是卡?问题根本不在内存
运维·服务器
且从容.2 小时前
ubuntu安装flink
linux·ubuntu·flink
数字新视界4 小时前
动环监控信创适配在智能机房管理中的应用价值与未来展望
大数据·服务器·数据库·动环监控系统·动力与环境监控系统
爱和冰阔落4 小时前
【Linux网络】TCP 服务器如何并发处理多个客户端?从 fork、僵尸进程到双重 fork
linux·运维·服务器·网络
程序员沉梦听雨4 小时前
WSL2的安装与使用
linux·wsl
微小的鱼4 小时前
Linux的渲染与显示系统链路
android·java·linux
lisanmengmeng4 小时前
alertmanger 标签 rules 打上分组标签
linux·运维·服务器
Android系统攻城狮5 小时前
Linux FFmpeg深度解析之av_write_frame调用流程与实战(三十三)
linux·运维·服务器·ffmpeg·音视频进阶·ffmpeg音视频
上火的金鱼妹5 小时前
k8s生产实际问题排查(网关,数据库读写等)
运维·服务器
孙克旭_5 小时前
k8s基础组件与创建资源
运维·docker·容器·kubernetes