【Linux】自动化构建工具——make/Makefile
make与makefile背景
(1)掌握make与makefile就可以从侧面说明一个人是否具备完成大型工程的能力。
(2)一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更加复杂的功能操作。
(3)makefile带来的好处就是——自动化编译,一旦写好只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发效率。
(4)make是一个命令工具,是一个解释makefile中的指令的命令工具,一般来说,大多数的IDE都有这个命令。
(5)make是一条命令,makefile是一个文件,两个搭配使用,完成项目自动化构建。
make和makefile如何工作
总的来讲,make是一条命令;而makefile是当前目录下的一个文件。
在使用make和makefile前,需要在一条路径下建立makefile/Makefile文件
再编写一段代码放在mycode.c这个源文件中
现在在makefile文件中进行编辑,将这个自动化程序编写出来。
下面这套流程演示了如何使用make和makefile。
问题:为什么需要clean清理呢?
使用rm进行直接删除时,比较繁琐,并且可能会将源文件给删除,风险较大。而makefile中的make clean会直接将mycode文件清理完成,在以后编写代码时会更加的方便,直接进行make、make clean。
- 工程是需要被清理的
- clean没有被第一行的目标文件直接或者间接关联,那么其后面的命令就不会被自动执行。
- 可以显示使用make执行——“make clean”,一次来清除所有的目标文件。
- 但是一把这种clean的目标文件,会将其设置成为伪目标,.PHONY修饰,伪目标的特性是:总是被执行的。
理解make与makefile
mycode:mycode.c
首先冒号左右两边的mycode与mycode.c是依赖关系,mycode需要依赖于mycode.c
gcc -o mycode mycode.c
这一行表示的是依赖方法,有了方法才能够实现可执行程序。
这很好理解,在现实时候中不管干什么事情都需要有依赖关系和依赖方法,就比如英语句子中的必要两个主语和谓语。
mycode依赖于mycode.c,gcc -o mycode mycode.c是依赖方法。
完整依赖关系
在gcc编译有四个过程:预处理,编译,汇编,连接。将这四个过程完成写在makefile文件中。
这是比较完整的依赖关系,这里只有mycode.c是真实存在的。
执行可执行程序mycode可以发现,可以正常运行。
make在扫描makefile文件时,会先进行首行查看依赖关系,发现mycode依赖于mycode.o,mycode.o会依赖于mycode.s,mycode.s会依赖于mycode.i,mycode.i会依赖于mycode.c。
在整个的执行过程中,会进行递归式的搜素依赖文件,这种特征类似于函数的递归查找,而mycode.c是递归出口,再进行逐步返回,这种过程被称为makefile的自动化推导。
乱序是否可以
除去首行不变,将其他部分的顺序进行调整
类似于函数调用,而make可以自动化推导makefile中的依赖关系栈式结构。
问题:如果将make放在首行会发生什么?
一般生成项目后,会产生一大堆的临时文件需要及时清理,clean的依赖关系为空,不需要任何的文件,执行make clean指令,会将文件清理。
将clean放在前面,再执行make指令。
输入make指令后会执行clean并将文件清理,而输入make mycode会生成mycode文件。所以一般不建议将clean放在最前面,需要将目标文件放在前面。
问题:执行一次make后,后面为什么不可以呢?
为了方便观察,将makefile中的内容进行简化。
执行make指令后,仅第一次可以执行。
对mycode.c的代码进行修改,再次执行make可以执行,再次make无法执行。为什么不修改源代码,就无法执行make指令呢?因为没有必要,这里主要是提高编译效率。
系统是如何做到不重复执行make?
在常规情况下,一定是有源文件,才可以生成可执行文件,所以源文件的修改时间一定是比可执行文件提前的。第一次在源文件中编写代码,然后使用make指令生成可执行文件,一般情况下可执行文件一定比源文件新;更改源文件后,历史上存在可执行文件,此时源文件的修改时间比可执行文件的要新,所以可以使用make指令。
此时,基本可以确定,系统只需要比较可执行程序的最近修改时间和源文件的最近修改时间即可。
- 如果可执行文件新于源文件,说明源文件是老的,所以不需要编译。
- 如果可执行文件老于源文件,说明源文件是新的,所以需要重新编译。
问题:一般来讲可执行文件的最近修改时间会不会等于源文件的最近修改时间??
答:一般来讲不会,除非使用一些特殊的指令。
问题:系统是如何保存可执行文件和源文件的最近修改时间的?
在Linux中,存在一条指令,stat可以查看源文件和可执行文件的修改时间。
我们可以在Linux中发现,存在三种时间的概念:access、modify、change
- Access:就是最近访问的时间,可以是cat打印字符、vim访问、修改...这些都属于是访问,不管是增删查改都会被记录,所以一般来说access被修改是非常频繁的。
- Modify(修改):只对文件的内容做修改时,才会修改时间
- Change(改变):只对文件的属性做修改时,才会修改时间
文件是由文件的内容和文件的属性组成的,当对文件进行修改时,可以修改内容,也可也修改属性。而对文件的内容进行修改的时候,文件的属性势必也是一定会被修改的。这三个时间的概念可能会更改其中一个,或者两个,或者三个。
对mycode.c中的代码内容进行修改,查看哪些时间被修改。
三者均被修改
修改mycode.c的文件属性,发现change改变,而其他两个不变。
问题:为什么access不会发生改变呢?
在现阶段的Linux系统中,为了提高效率,会根据change或者modify更新的次数以及距离上次修改的时间进一步改变Access的。
问题:如何全部更改系统的时间呢?
这里可以使用touch命令,touch后面如果跟不存在的文件,会创建文本文件;而如果touch后面跟着已经存在的文件,会跟新时间。
此时,touch命令可以将这些时间的数据转化为时间戳。
如果可执行程序的时间大于源文件的时间,此时可以执行touch mycode.c文件,发现可以再次执行make指令了。
总的而言:make会根据源文件和目标文件的更新时间新旧,以此来判定是否需要重新执行依赖关系进行编译,而这个依赖关系,不一定总是执行的。
问题:可执行程序如何每次都执行呢?
.PHONY:被称为伪目标,作用时:总是被执行。
一般不会建议将目标文件总结被执行,因为执行一个大型的目标文件需要很长的时间,而一般都是将清理变成总是执行。
使用特殊符号代替文件名称
一般来说,makefile文件中gcc指令后不需要放很多的文件名称,只需要使用$@ $^。
- $@代表:左边的文件名称
- $^代表:右边的文件名称
此时输入make会将内容回显出来,如果不想将内容回显出来也可以使用@这个特殊符号。
这样也就不会回显make和make clean的内容了。
Linux程序——进度条
结合上述知识编写Linux的一个小程序——进度条,该进度条大概是这样的:
[##### ][40%][\]
在制作缓冲区之前需要有两个储备知识:回车换行,缓冲区。
1.回车换行
回车换行分为回车和换行,回车是回到此行的最开始,在C语言中使用\r;而换行是换到下一行。回车换行是换到下一行的最开始位置,使用\n表示。
创建一个processbar的目录,进入目录后创建三个文本文件,分别是processBar.h、processBar.c、main.c。
- processBar.h 进度条函数的声明
- processBar.c 进度条函数的实现
- main.c 进度条函数的调用
在processBar.h文件编写头文件。
在main.c文件中引用processBar.h头文件。
创建一个makefile文件,在makefile文件中编写自动化执行的代码。
由于源代码main.c中包含了processBar.h文件,并且processBar.h文件和main.c文件在同一条路径下面,所以makefile文件中不需要体现processBar.h文件。
2.缓冲区
执行可执行代码后发现,打印hello后需要等待两秒才出现新的命令行提示词。
在main.c文件中去掉\n后,发现是等待2秒后,hello和命令行提示词一起出现了。
从观察到的现象来说,是先进行sleep函数的调用,再进行printf函数的打印。但是实际上不是的,C语言是按照顺序执行的,所以一定是先执行printf函数的调用,再执行sleep函数的调用。在等待2秒的过程中其实printf函数以及执行结束,但是没有在显示屏幕中打印出来,这一定是被保存起来了,而这被保存的内容,一定需要一定的内存空间,而这个内存空间就是缓冲区。这个缓冲区也是C语言需要维护的一段内容。
问题:如何强制刷新
显示器也是文件,可以输出出去,可以使用标准输出,在C语言中是stdout.
而这里需要使用一个函数将这个标准输出给刷新到显示器上,需要使用fflush函数。
此时,没有\n也可以将hello打印到屏幕上去了,printf将内容暂存在stdout这个流中,如果想要刷新时,使用fflush这个函数将这个流里面的内容刷新出去。
实现简单倒计时
测试完代码后发现,刚开始什么都没有显示,后面等待10s后全部显现,这是由于没有\n,数据也就没有立马刷新到屏幕上去。
此时,开始逐渐显现出来了,但是是一行中逐渐出现倒计时的数字。
使用\r将内容放在一个字符上递减。
再将代码修改为可以使用十位数倒计时。
实现进度条
在processBar.h文件进行进度条函数的声明
在processBar.c文件进行进度条函数的实现
在main.c文件中进行函数的调用。
使用make指令后,执行可执行文件。
在processBar.c文件中实现进度条函数的实现。
在processBar.h文件中实现进度条函数的声明
运行结果如下: