文章目录
- 前言:
- 一、认识make和makefile
- 二、依赖关系和依赖方法
- 三、make工作原理
前言:
上一期分享了在Linux下编译源代码的两个工具,gcc和g++。每次编译源代码,都要输入一串很长的指令,这个过程显然是十分复杂,且容易出错的,尤其是在一些大型的项目中,源代码可能有多个,此时编译起来就会更费劲。为了解决上面的问题,今天就给大家分享一个,Linux环境下的项目自动化构建工具——make/makefile。
一、认识make和makefile
一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile
定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。
📖makefile带来的好处
“自动化编译”,一旦写好,只需要一个make
命令,整个工程完全自动编译,极大的提高了软件开发的效率。
📖make和makefile
make
是一个命令工具,是一个解释makefile中指令的工具,make是一条命令,makefile是一个文件,两个搭配使用,完成项目的自动化构建。
📖见见猪跑
- 先在当前目录创建一个
makefile
文件,m大小写都可以。
- 在makefile文件中写入依赖关系和依赖方法。
- 使用make。
二、依赖关系和依赖方法
将一个源文件编译得到可执行文件,这是一件具体的事情。举个例子:月底,你的生活费用完了,于是你就要给你的老爸打电话,告诉他:“老爸,我是你儿子,我没钱啦”。这个过程中,你为什么不给你舍友的父亲打电话呢?哈哈,其实,给你的老爸打电话,说你是他儿子,就叫做表明依赖关系。打了电话,如果什么都没说,把电话挂了,那打电话不就没什么意义,所以让老爸给你打钱,就叫做依赖方法。因此,只有依赖关系加上依赖方法,才能完成打钱这件事情。
📖依赖关系
mytest:test.c
同理,对于编译代码这件事情,我们希望把test.c
这个源文件,编译得到一个mytest
的可执行程序。首先我们要告诉make,要得到mytest
这个可执行程序,所依赖的源文件。其中:
左边就是我们想要创建的信息,也就是我们想要干的事情,一般也叫做标签,右边就是完成这件事情所依赖的东西,一般都是文件(可以有多个,中间用空格隔开)。
📖依赖方法
gcc -o mytest test.c
有了依赖关系,接下来该告诉编译器,要完成这件事情,需要对依赖的文件,执行什么操作。注意:依赖方法前面有一个Tab
。
三、make工作原理
📖先来看一个完整的编译过程
mytest:test.o
gcc test.o -o mytest
test.o:test.s
gcc -c test.s -o test.o
test.s:test.i
gcc -S test.i -o test.s
test.i:test.c
gcc -E test.c -o test.i
从上图可以看出,输入make指令以后,它实际执行makefile文件中指令的顺序,和我们写在makefile文件中的指令顺序是反的。这是因为,要得到mytest
必须要依赖test.o
,但是当前目录下没有test.o这个文件,但是makefile文件中有得到test.o的方法,但是,要得到test.o又必须依赖test.s
,但当前目录下依旧没有test.s,以此类推,要先得到test.i
文件,才能得到test.s
文件,然后才能得到test.o
文件,最后才能得到mytest
,所以实际指令的执行顺序,于我们在makefile文件中写入的,是反过来的。所以,实际上保存这些依赖关系的是一种栈式的结构。
📖结论
上面说的过程,就叫做makefile依赖关系的自动化推导,由于是自动推导,所以makefile文件中的依赖方法可以是任意顺序,但是不能缺少。像下面这样,也是可以的。
这里只是为了演示makefile的自动化推导能力,所以才把编译过程拆解的如此详细,一般我们只需要一个依赖关系和一个依赖方法,就可以完成编译工作。
📖清理生成的文件
clean:
rm -rf test.i test.s test.o mytest
清理编译生成文件的依赖关系和依赖方法,如上所示。清理文件这个操作,不需要依赖任何其他文件,所以:
右边的依赖关系什么也没写,直接执行对应的操作,也就是执行依赖方法的指令即可。clean
是我用来表示删除操作,自己取的名字,大家也可以根据自己的喜好,取其他的名字。有了上面的依赖关系和依赖方法,此时只需要在命令行中执行make clean
指令,就可以把编译过程中产生的文件全删除了。
📖为什么删除时make后面要加clean
通过上面截图可以看到,在编译源代码的时候,直接用make,但是删除的时候,却要在make后面加上clean。因为,make会自顶向下扫描makefile,单独的make指令,会默认执行第一个依赖方法。
📖不能连续编译
如下图,当执行完一次make,对源代码编译后,再去执行make,就不会对源代码重新编译。
📖结论
对于一个源代码,进行一次编译,得到一个可执行文件后,如果没有对源代码做任何修改,再去对源代码进行编译,make会觉得没必要,不会重新编译。
📖如何实现
一定是源文件 形成可执行文件,先有源文件,才有可执行文件,一般而言,源文件的最近修改,比可执行文件要早。如果,更改了源文件,那和之前形成的可执行文件相比,此时的源文件最近修改时间就比可执行文件新。
所以,只需要比较可执行文件的最近修改时间和源文件的最近修改时间,如果.out
文件的最近修改时间晚于.c
文件,就不需要重新编译。反知,则需要重新编译。
📖查看文件时间
stat mytest
:查看mytest文件的有关时间。
Access
:文件最近一次被访问的时间,查看文件内容、修改文件内容,都属于访问文件。Modify
:最近一次修改文件内容的时间。Change
:最近一次修改文件属性的时间。
这三个时间是相互关联的,有的操作可能会同时更新多个时间。例如:修改文件的内容,那这三个时间都会更新,因为修改文件内容,首先要访问该文件,其次修改后,文件的大小会发生变化,所以这三个时间都会更新。
如上图所示:修改文件内容,三个时间都更新了,但是修改文件属性,Modify没更新可以理解,为什么Access也没有更新???
正是因为对文件的各种操作,都会导致Access时间改变,早期的Linux系统,确实会随着对文件的操作,时刻更新Access时间,这些时间信息都存储在计算机的硬盘上,而硬盘都属于外部设备,进行读写操作会比较慢,过高频率的更新一个文件的Access,当整个系统在被多个用户使用的时候,就会有大量的Access更新行为,这些行为都会往硬盘中写数据,这就会导致整个系统的运行速度下降。所以,在现在的Linux中,对Access的更新策略进行了修改,维护了一个计数器,会根据Modify和Access的更新达到一定次数的时候,才会更新Access,以此来提高系统的运行效率。(注:不同系统的更新策略会有差异)
📖手动更新文件时间
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时间。
📖结论
make会根据源文件和目标文件的新旧,判定是否需要重新执行依赖方法进行编译。这意味着,依赖方法并不是每次都会执行。
📖.PHONY伪目标
要想让makefile文件中的依赖方法总是被执行,可以用.PHONY
对相应的标签进行修饰。
.PHONY:mytest
mytest:test.c
gcc test.c -o mytest
告诉make,mytest对应的依赖方法总是被执行。
上面只是给大家演示.PHONY
的作用,一般情况下,对编译操作不加.PHONY进行修饰,而是对清理操作加.PHONY修饰。
mytest:test.c
gcc test.c -o mytest
.PHONY:clean
clean:
rm -rf mytest
📖特殊符号
$@
:表示标签,依赖关系冒号左边的内容。$^
:表示依赖的文件,依赖关系冒号右边的内容。
mytest:test.c
gcc $^ -o $@
.PHONY:clean
clean:
rm -rf mytest
📖取消执行make指令时的回显
在前面的介绍中,每次执行make指令,都会把对应的依赖方法回显出来,像下面这样:
可以在makefile文件中的依赖方法前面加上@
,取消回显。
mytest:test.c
@gcc $^ -o $@
.PHONY:clean
clean:
@rm -rf mytest
🎁结语:
今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,您的支持就是春人前进的动力!