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

目录

  • 前言
  • 1. Makefile 文件的基本构成
  • 2. makefile的依赖关系的自动化推导
  • 3. make执行过程中的一些现象及其原理
    • 3.1 证明该现象原理
    • 3.2 关于 stat 时间属性的拓展

前言

身处 linux 平台环境开发中的伙伴们都知道 gcc/g++ 编译器以及编译指令,但是不难想象在以后的生活或者工作中,肯定是有多文件编译的需求,少则数10个,多则上百也不是不可能。

那么我们难道就直接 gcc -o test t1 t2 t3 ..... t99 吗??显然是费力不讨好,毕竟还有一个 rm t1 t2 t3 ...... t99 等着你呢!

所以针对上述场景及其需求,该篇文章主要介绍的是在linux系统中项目自动化构建工具make以及其配置文件Makefile的相关内容。



我们先抛开一切原理及其设计理念,先见一见所谓的make以及Makefile,所谓 “没吃过猪肉,咱也得见一见猪跑吧~~”

# Makefile
test:test.cpp
	g++ -o test test.cpp
clean:
    rm -f test

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/eb256ed177f14a4e89da9d1bcc4d3cb9.png

有了 Makefile 之后,我们只需要make 即可完成编译生成可执行程序,make clean 即可完成删除可执行程序。

1. Makefile 文件的基本构成

# Makefile
test:test.cpp
   g++ -o test test.cpp
clean:
   rm -f test

其中的 test:test.cpp 和 clean 我们称之为依赖关系,test 和 clean 下面带的指令我们称为依赖方法

这么一听似乎有点抽象。什么是依赖关系?什么是依赖方法?

打个比方,月底到了,作为大学生的张三生活费以及见底了,于是乎,他需要打电话给他的老爸,而接通电话的那一刻,张三即需要表面身份,即与他电话沟通的人的关系,即所谓的依赖关系。而张三表面完关系之后,需要表面其来电目的,即依赖方法。如果张三不表明依赖关系,他爸凭什么给他生活费?换言之,假设今天张三拨号给中国银行,让中国银行给予其生活费,中国银行会同意吗??道理很简单,因为张三与中国银行之间不存在依赖关系,因此,没有依赖关系的基础上,无法执行依赖方法(也即为张三来电的目的)。同理,假设张三拨号给他老爸,开头一句:”爸!“,然后马上把电话挂了,他爸知道他要干嘛吗??是不是显然不知道!因此,仅有依赖关系也不够,还需要有与该依赖关系对应匹配的依赖方法!

2. makefile的依赖关系的自动化推导

我们把 Makefile 文件改稍微复杂一点,如下:

test:test.o   			想要生成 test 可执行程序,需要先有 test.o 文件
	g++ test.o -o test    
test.o:test.s			想要生成 test.o 文件,需要先有 test.s 文件
	g++ -c test.s -o test.o
test.s:test.i			想要生成 test.s 文件,需要先有 test.i 文件
	g++ -S test.i -o test.s
test.i:test.cpp			直到最后找到 test.cpp,然后预处理之后生成 test.i 文件
    g++ -E test.cpp -o test.i
clean:
    rm -f test.i test.s test.o test

在这里插入图片描述

我们可以看到,将makefile 改为四步编译之后,执行make依旧可以顺利的进行编译,并且其编译过程是按照四步编译的顺序进行编译的(与Makefile 文件里面的依赖关系与其匹配的依赖方法的位置顺序无关!!!)。这就说明在 make 执行的过程中,make会自动推导 makefile 中的依赖关系。

并且我们这么一看,想要 test,需要先有 test.o, 想要 test.o,需要先有 test.s, 想要 test.s,需要先有 test.i, 想要 test.i,需要先有 test.cpp,然后 .i 文件有了,返回给 .s 的依赖关系。。。依次进行返回。这个过程不就是类似于递归的过程吗?!而所谓的 test.cpp 就类似于递归出口。 我们都知道,递归就需要借助栈帧或者栈结构来完成。那么我们就将 这种类似于递归过程,栈这样的结构,称之为makefile的依赖关系的自动化推导!


但是,为什么 clean 的时候,我们需要在前面加上一个make,而编译的时候不用呢??
我们修改一下makefile文件:

clean:
    rm -f test.i test.s test.o test
    
test:test.o   			
	g++ test.o -o test    
test.o:test.s			
	g++ -c test.s -o test.o
test.s:test.i			
	g++ -S test.i -o test.s
test.i:test.cpp			
    g++ -E test.cpp -o test.i

在这里插入图片描述

在我们调换 可执行程序 和 clean 的位置之后,我们发现,make 变成 clean了!! 而编译我们需要 make + 目标文件名 才能够完成。因此我们不难得出结论:make会自顶向下扫面 makefile 文件,把你要形成的第一个目标文件,当作make的默认动作!而 make + 目标文件即为:指定名称的执行 该依赖关系 与其匹配的 依赖方法


3. make执行过程中的一些现象及其原理

在这里插入图片描述

可以看到,当我们make编译完该文件之后,系统就不让我们继续make了!这是为什么呢??

我们知道的是,不管在 windows 还是 linux, 文件 = 文件内容 + 文件属性,并且该等式恒成立。那么我们现在先猜测,可能是因为源文件没有进行任何修改,因此 make 会根据源文件和目标文件的新旧,判定是否需要重新执行依赖关系进行编译!

那么现在的问题是:为什么 make 要这么做??原因其实也很好理解,因为在学习阶段,源代码本身的编译时间几乎忽略不计,但是等到了工作和研发当中,几十万,几百万的源代码,编译时间可是少说一个小时起步的,那么 make 这样做的原因无非就是在不影响程序的前提下,提高效率!

到这里,又有另一个问题产生,make 是如何完成对源文件和目标文件的新旧进行判定的??
在回答这个问题之前,我们要有一个基本的认知:“一定是先有的源文件,才有的可执行程序,而一般而言,源文件的最近修改时间 比 可执行文件要早!!” 。那么这样一来,我们就不难猜测出,该操作只需要 对 可执行程序的最近修改时间 (.out)与 源文件的最近修改时间 (.cpp)进行比较即可判断是否需要对该源文件进行编译。如果 .out < .cpp 那么即说明在可执行程序生成之后,源文件有过变动,因此需要重新编译;反之, .out > .cpp,即说明源文件生成可执行程序之后,并没有过修改动作(不管是文件内容还是文件属性)。

讲到这里,上述的一切也只不过是我们的猜测结论而已!!那么接下来,我们将对上述的猜测进行一定的证明:

3.1 证明该现象原理


[outlier@localhost makefile]$ stat test.cpp 
  File: ‘test.cpp’
  Size: 153       	Blocks: 8          IO Block: 4096   regular file
Device: fd00h/64768d	Inode: 1351420     Links: 1
Access: (0664/-rw-rw-r--)  Uid: ( 1001/ outlier)   Gid: ( 1001/ outlier)
Access: 2024-07-06 22:13:05.007175733 +0800
Modify: 2024-07-06 21:55:05.962028145 +0800
Change: 2024-07-06 21:55:05.963028143 +0800
 Birth: -

以上是关于 test.cpp 文件的三个时间属性,分别是:
Access(最近访问时间,对文件增删查改都属于访问的范畴)
Modify(文件内容的最近修改时间),
Change(文件属性的最近修改时间)

先是对这三个时间属性进行分析,虽然我们不太能对访问的范畴有一个很好的标准,但是对于文件内容和文件属性的修改可以做一波推论 ==》 Modify 修改,极大可能的会导致 Change 的修改,最常见的就比如文件的大小发生改变。而Change 的修改,不一定会导致 Modify 的修改,比如改变文件的权限属性,那么这种情况下,文件内容是不变的。


接下来我们对文件内容进行修改,并且观察修改前后的 Modify 时间,以此来判断 make 是不是因为最近修改时间作为判断依据。

在这里插入图片描述

设 源文件的最近修改时间 = Tcpp,可执行程序的最近修改时间 = Tout

这次的测试证明了我们上述的猜想和推论是正确的,修改了源文件,Tcpp 得到改变之后,Tcpp > Tout,因此 make 会重新执行依赖关系进行编译源文件。

而对文件内容进行修改之后,Change 也进行了更新,说明文件内容的修改,往往极大可能伴随的是,文件属性的修改!一般 Change 也会随之改变。

————————————————————————————————————————————————————

3.2 关于 stat 时间属性的拓展


上面说了, Access 是文件访问的最近时间,可是现在又有一个现象,当我访问了文件,却不见得 Access 得到更新,这是为什么呢??

在这里插入图片描述

Access 并没有随着我们的查看而进行改变,其中的原因可能涉及到 IO 方面的设计。

我们应该都需要清楚的是, Access 毫无疑问是三大时间属性当中,修改频率最高的一个,而文件是存储在磁盘当中,修改一次Access ,等价于修改一次文件属性,意味着需要做一次持久化。问题就在于磁盘属于外设,其读写速度远远低于内存,更别提cpu了,因此,高频率的对外设进行读写,是一个非常大的代价,也是效率极低的一个行为,该行为不利于系统整机的效率!

因此,Access 采用了类似“缓存“的更新策略,比如根据 Modify 和 Change 的修改次数 或者 文件被访问到一定次数 之后,才对 Access 进行新的持久化。

但是假如此刻我就是想要在访问文件之后,Access 就立刻、马上得到更新!那有办法吗??
这就需要我们重新来认识一下 touch 这个指令了

touch 文件
a. 文件不存在时,创建文件
b. 文件存在时,更新文件的时间(三个时间属性都会更新)

touch -a 	 
touch -m
touch -c 
也可以只根据某个时间属性进行更新

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


到这里,已经验证了我们上述的所有猜想以及推论,make 是否会重新执行依赖关系进行编译,取决于 可执行程序的最近修改时间 是否早于 或者晚于 源文件的最近修改时间。而 touch 文件 时,文件已经存在,则可以更新文件的时间属性,而一般情况下,不管是 touch -a 还是 touch -m ,Change 也会随着这两个的更新而更新,而 touch 不带选项是更新整个文件的时间


现在,我就是不想要使用 touch, 但是我还想要可以让 依赖关系 总是被执行!还有其它办法吗??

.PHNOY:test
test:test.cpp
   g++ -o test test.cpp
clean:
   rm -f test

其中的 .PHNOY 后面修饰的文件符号,我们称之为 伪目标
.PHNOY:test 代表的就是:test 的依赖关系 与其对应的 依赖方法 总是被执行!
就相当于告诉 make,你别管 Tcpp 和 Tout 的关系了!当用户执行 make 指令的时候,你就执行一遍依赖关系进行编译就行了!

在这里插入图片描述

按照 make 原本的规则,这 Tccp < Tout,其依赖关系与其对应的依赖方法是不被执行的啊!但是,我们在 Makefile 添加了 test 这个依赖关系的伪目标,因此 make 在执行的时候就可以忽略时间上的关系。


————————————————————————————————————————————————————

好了,回到我们这篇文章的主题:make

上面讲了那么多样的任性,我们可以那么任性,但是我们一般不那么做,因为出于各种考虑,给 make 加上时间上的限制也不是什么坏事。

所以我们的 makefile 文件一般会把伪目标设置成 clean,因为在以后,clean 的依赖方法可能不仅仅是 rm 这么简单的操作!

test:test.cpp
	g++ -o $@ $^
.PHNOY:clean
clean:
    rm -f test

其中的 $@ 就是目标文件,即冒号以左的部分,$^ 就是源文件,寂即冒号以右的部分,这样写的好处是,当依赖关系中的源文件很多的时候,依赖方法就不需要你重新在写一遍了,只需要 $@ $^ 即可。

而当我们在执行 make 或者 make clean 的时候,不希望执行的依赖关系对应的依赖方法回显在屏幕上,我们可以这样写

test:test.cpp
	@g++ -o $@ $^
.PHNOY:clean
clean:
    @rm -f test

OK,关于 make/Makefile 我们就讲这么多,该篇文章可以说是比较完整,系统的讲述了 make 和 Makefile 的方方面面,包括各种想象,以及其原因。

如果感觉该篇文章给你带来了收获,可以 点赞👍 + 收藏⭐️ + 关注➕ 支持一下!

感谢各位观看!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/792388.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

解决方案 | IP地址申请专用HTTPS证书的常见问题

IP地址专用的HTTPS证书是一种专门为IP地址设计的SSL/TLS证书&#xff0c;它可以通过HTTPS协议安全地访问基于IP地址实现的网站或服务&#xff0c;以下是申请IP地址https证书时经常遇到的问题以及解决办法。 一 、如何选择合适的IP地址https证书的类型&#xff1f; 1、DV类型IP…

水文:CBA业务架构师

首先&#xff0c; 我们来了解一下什么是CBA业务架构师&#xff1f; CBA业务架构师认证是由业务架构师公会(Business Architecture Guild)授予的一种专业认证。标志着证书持有者已经掌握了业务架构的核心技能和知识&#xff0c;能够在实际工作中熟练运用业务架构技术和框架&…

安全极客团队荣获首届“矩阵杯”网络安全大赛人工智能挑战赛“三等奖”

近日&#xff0c;东半球规格高、规模大且奖金丰厚的网络安全顶级赛事——首届“矩阵杯”网络安全大赛在青岛国际会议中心圆满落幕。本次大赛设置了五大赛事&#xff0c;包括通用产品漏挖赛、国产软硬件安全检测赛、原创漏洞挖掘赛、人工智能&#xff08;大模型&#xff09;挑战…

CDH实操--集群卸载

作者&#xff1a;耀灵 1、停止正在运行的服务 a、控制台停止集群服务 b、控制台停止Cloudera Management Service c、命令行停止cm服务 systemctl stop cloudera-scm-agent #所有节点执行 systemctl stop cloudera-scm-server #cdh01节点执行2、主线并移除Parcles rm -r…

【完结】LeetCode 热题 HOT 100分类+题解+代码详尽指南

目录 LeetCode 热题 100 前言 Leetcode Top100题目和答案-哈希 Leetcode Top100题目和答案-双指针篇 Leetcode Top100题目和答案-滑动窗口篇 Leetcode Top100题目和答案-子串篇 Leetcode Top100题目和答案-普通数组篇 Leetcode Top100题目和答案-矩阵篇 Leetcode Top1…

ABAQUS大连正版代理商:亿达四方——开启东北工业智能仿真新篇章

在东北老工业基地的振兴道路上&#xff0c;大连以其独特的地理位置和深厚的产业基础&#xff0c;成为推动区域经济发展的领头羊。作为国际知名的仿真软件ABAQUS在大连地区的官方授权代理商&#xff0c;亿达四方正以科技创新为驱动&#xff0c;引领当地制造业迈向数字化、智能化…

c++多态——virtual关键字,C++11 override 和 final,析构函数的重写。

目录 多态基本概念 virtual关键字 C11 override 和 final 举个栗子 析构函数的重写(基类与派生类析构函数的名字不同) 多态基本概念 概念&#xff1a;通俗来说&#xff0c;就是多种形态&#xff0c;具体点就是去完成某个行为&#xff0c;当不同的对象去完成时会 产生出不同…

Coze触发器:触发任务的Python接口源码

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 Coze触发器 📒📝 触发器接口源码⚓️ 相关链接 ⚓️📖 介绍 📖 自动化,一个在现代软件开发中不可或缺的概念,它让我们的生活和工作变得更加高效。Coze也支持定时任务/触发任务,通过触发器,我们可以更自由的控制Bot去…

【中项第三版】系统集成项目管理工程师 | 第 11 章 规划过程组② | 11.3 - 11.5

前言 第 11 章对应的内容选择题和案例分析都会进行考查&#xff0c;这一章节属于10大管理的内容&#xff0c;学习要以教材为准。本章上午题分值预计在15分。 目录 11.3 收集需求 11.3.1 主要输入 11.3.2 主要工具与技术 11.3.3 主要输出 11.4 定义范围 11.4.1 主要输入…

鸿蒙开发:Universal Keystore Kit(密钥管理服务)【密钥派生(C/C++)】

密钥派生(C/C) 以HKDF256密钥为例&#xff0c;完成密钥派生。具体的场景介绍及支持的算法规格&#xff0c;请参考[密钥生成支持的算法]。 在CMake脚本中链接相关动态库 target_link_libraries(entry PUBLIC libhuks_ndk.z.so)开发步骤 生成密钥 指定密钥别名。 初始化密钥属…

【JavaEE】Spring AOP详解

一.AOP的定义. Aspect Oriented Programming&#xff08;面向切面编程&#xff09;概括的来说AOP是一种思想, 是对某一类事情的集中处理 什么是面向切面编程呢? 切面就是指某一类特定问题, 所以AOP也可以理解为面向特定方法编程.什么是面向特定方法编程呢? 比如上个博客文章…

Python基础教学之四:面向对象编程——迈向更高级编程

Python基础教学之四&#xff1a;面向对象编程——迈向更高级编程 一、面向对象编程概念 1. 类和对象 定义&#xff1a;在面向对象编程(OOP)中&#xff0c;类是创建对象的模板&#xff0c;它定义了对象的属性和方法。对象是类的实例&#xff0c;具体存在的实体&#xff0c;拥有…

从0到1开发一个Vue3的新手引导组件(附带遇到的问题以及解决方式)

1. 前言: 新手引导组件,顾名思义,就是强制性的要求第一次使用的用户跟随引导使用应用,可以让一些第一次使用系统的新手快速上手,正好我最近也遇到了这个需求,于是就想着开发一个通用组件拿出来使用(写完之后才发现element就有,后悔了哈哈哈&#x1f62d;&#x1f62d;) 示例图…

回车不搜索直接页面刷新问题解决

使用技术栈&#xff1a;vue3、elementUiPlus 问题&#xff1a;回车触发方法&#xff0c;会刷新整个页面&#xff0c;不执行搜索 解决方法&#xff1a;在搜索的表单中增加submit.native.prevent submit.native.prevent

LLM 合成数据生成完整指南

大型语言模型是强大的工具&#xff0c;不仅可以生成类似人类的文本&#xff0c;还可以创建高质量的合成数据。这种能力正在改变我们进行 AI 开发的方式&#xff0c;特别是在现实世界数据稀缺、昂贵或隐私敏感的情况下。在本综合指南中&#xff0c;我们将探索 LLM 驱动的合成数据…

学习测试8-数据库mysql操作

下载配置mysql 网络博客 使用 在Linux里 1 service mysql start 启动服务 2 在Navicatt 中连接Linux服务器 3 第一步 将所有文件上传到/opt目录下 第二步 chmod 777 deploy-mysql.sh 第三步 ./deploy-mysql.sh4 service mysql status 查看状态是否安装成功 5 重启mys…

WPS打开PDF文件的目录

WPS打开PDF文件的目录 其实WPS中PDF文件并没有像Word那样标准的目录&#xff0c;但是倒是有书签&#xff0c;和目录一个效果 点击左上角书签选项&#xff0c;或者使用Alt Shift 1快捷键即可

『Django』自带的后台

theme: smartblue 本文简介 点赞 关注 收藏 学会了 上一篇讲了 Django 操作 MySQL 的方法&#xff0c;讲了如何创建模型&#xff0c;如何对数据库做增删改查的操作。但每次修改数据都要写代码&#xff0c;多少有点麻烦。 有没有简单一点的方法呢&#xff1f; 有的有的&#…

centos 安装ffmpeg

这个错误表明在你的 CentOS 系统的默认仓库中没有 ffmpeg 包。CentOS 的默认仓库通常不包含 ffmpeg&#xff0c;因为它涉及一些许可证问题。但是&#xff0c;你可以通过添加第三方仓库来安装 ffmpeg。 使用 EPEL 和 RPM Fusion 仓库 # 安装 EPEL 仓库 sudo yum install epel-…

苹果入局,AI手机或将实现“真智能”?

【潮汐商业评论/原创】 “AI应用智能手机不就是现在的AI手机。” 当被问到现阶段对AI手机的看法时&#xff0c;John如是说。“术业有专攻&#xff0c;那么多APP在做AI功能&#xff0c;下载用就是了&#xff0c;也用不着现在换个AI手机啊。” 对于AI手机&#xff0c;或许大多…