Linux系列
``
文章目录
- Linux系列
- 前言
- 一、make/makefile是什么?
- 二、make/makefile的使用
- 2.1、语法规则
- 2.2、依赖关系和依赖方法
- 2.3、清理可执行文件
- 2.4、执行依据
- 三、循环依赖问题
- 总结
前言
上一篇博客给大家分享了在Linux下编译源代码的两个工具,gcc和g++的使用。每次编译源代码,都要输入一串很长的指令,这个过程显然是十分复杂,且容易出错的,尤其是在一些大型的项目中,源代码可能有多个,此时编译起来就会更费劲。为了解决上面的问题,今天就给大家分享一个,Linux环境下的项目自动化构建工具——make/makefile。
一、make/makefile是什么?
一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,而make是一个命令工具,是一个解释makefile中指令的命令工具。
简单来说make是一条命令,makefile是一个当前路径的文件,两个搭配使用,完成项目自动化构建
使用make/makefile带来的好处:类似于在Windows下使用编译软件,使我们编译程序不需要再写一大串指令,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
二、make/makefile的使用
要想使用这个自动化构建工具,我们首先需要在自己工作路径下创建一个依makefile命名的文件。(文件名makefile首字母忽略大小写,所以你可能会 看到Makefile)
2.1、语法规则
下面为makefile文件内部信息
目标文件:源文件 --------依赖关系
【Tab键】 命令... --------依赖方法
首先我们编写一个简单的程序(test.c文件),方便我们讲解、测试,下面为文件内容:
#include<stdio.h>
int main()
{
printf("Hello Linux\n");
printf("一个简单的c程序\n");
return 0;
}
进入新建的makefile文件,建立依赖关系、依赖方法:
当我们作完这些准备工作,就可以使用make命令快速编译程序了:
这是如何实现的呢?首先我们要清楚,我们的目的是使用源文件(test.c文件),编译形成一个可执行文件(my_test.exe文件),而当我们做完makefile文件的搭配后,再使用make命令,它就会自动在当前目录下找makefile文件,根据文件中的依赖关系,执行依赖方法来形成目标文件。
这样我们就完成了快速编译的操作
2.2、依赖关系和依赖方法
将一个源文件编译得到可执行文件,这是一件具体的事情。举个例子:月底,你的生活费用完了,于是你就要给你的老爸打电话,告诉他:“老爸,我是你儿子,我没钱啦”。这个过程中,你的老爸打电话,说你是他儿子,就叫做表明依赖关系。打了电话,如果什么都没说,把电话挂了,那打电话不就没什么意义,所以让老爸给你打钱,就叫做依赖方法。因此,只有依赖关系加上依赖方法,才能完成打钱这件事情,而在这在这个例子中,你给老爸打电话就好比是make指令,钱就是目标文件,你爸就相当于源文件,打钱的过程(支付宝、微信)就是依赖方法。
依赖关系: my_test.exe : test.c
对标上面的故事,我们的目的是根据源文件的到一个可执行文件。首先我们要告诉make,要得到my_test.exe这个可执行程序,需要依赖于test.c文件。
依赖方法: my_test.exe : test.c
告诉编译器,要完成这件事情,需要对依赖的文件,执行什么操作。
这只是完成了一键编译的操作,当我们要删除不需要的可执行文件时,依然需要一点但删除,要想是编译效率更高,我们就需要不断完善makefile工具
2.3、清理可执行文件
对于上面的目标(clean)来说,它没有任何的依赖关系,要使用它只需要像图中所示就可以达到目的:
这时可能你会有疑问:为什么生成可执行文件只需要make命令,而删除可执行文件需要make clean的方式呢?
其实我们也可以使用像删除可执行文件一样的方式,生成可执行文件:
而我们可以直接make的原因是因为,对于make来说,如果后面没有跟目标他会默认查找makefile文件中第一个依赖关系,并执行对应的依赖方法,大家可将上面两个调换一下,进行测试。
2.4、执行依据
大家在使用make对文件进行编译时,会出现编译一次后,需要更改文件内容才可再次编译的情况,这是为什么呢?
会出现下面这种情况:
这时我们需要更改文件内容才可进行再次编译:
#include<stdio.h>
int main()
{
printf("Hello Linux\n");
printf("一个简单的c程序\n");
printf("更改文件\n");
return 0;
}
这是因为当我们没有更改代码时,再次编译是没有意义的,系统不会执行,这样可以提高编译效率,而这就是make编译高效的另一种方式,那么他是如何判断是否执行编译的呢?
实现高效编译的原理其实就是通过比较源文件和可执行文件的修改时间,来判断是否可以再次执行,从而避免无效的执行,具体点来说就是源文件的修改时间新于可执行文件的修改时间时,就能够再次执行make命令,生成新的可执行文件我们可以用stat指令来查看文件的时间的相关的信息:
上面三种时间:
- Access:最近访问的时间
- Modify:最近对文件内容做修改的时间
- Change:最近对文件属性做修改的时间
make的判段方式就是,将源文件的Modify时间和已存在的可执行文件的Modify时间转化为时间戳进行比较,仅当已存在的可执行文件早于源文件时make才会执行编译。
大家可以更改源文件,配合stat命令查看文件信息比较、测试。
如果想达成,不更改文件也可以一直编译的行为,我们可以采用设置伪目标的方法来实现:.PHONY:目标文件,如图:
这样就可以一直编译了,而’.PHONY‘的作用就是告诉make,他所修饰的目标一直被执行,不需要再做判断。
这样就会是我们编译不那么高效,所以我们一般不这样使用。
三、循环依赖问题
上篇文章中我们介绍过了程序编译会经历的四个过程,以及在这个过程中会生成的三个临时文件和gcc的对应选项,下面我们就以这个为例,讲解循环依赖问题。
我们上面说过,直接使用’make’系统会从上至下扫描‘makefile文件’并且默认执行第一个目标的依赖关系所对应的依赖方法,从上图中我们看到,当我们直接使用’make’他并‘没有’执行第一个依赖关系“my_test.exe:my_test.o”对应的依赖方法“gcc my_test.o -o my_test.exe”,这是因为make在执行第一个依赖关系是会先查找依赖文件列表是否已存在,如果存在则执行该依赖关系对应的依赖方法,否则递归进入源文件的依赖关系,先执行源文件的依赖关系生成源文件,再执行目标文件的依赖关系,生成目标文件。这就类似于栈结构,系统会帮我们自动化推导。
总结
这部分知识理解起来比较抽象,多次尝试才能深刻理解,makefile还有很多特殊符号没有介绍,大家感兴趣自行搜索。