我们继续Linux开发工具的学习,这篇文章我们要学的是Linux中的项目自动化构建工具——make/Makefile
1. 背景
make和makefile提供了自动化构建的能力,可以根据源文件的依赖关系和规则自动决定哪些文件需要重新编译。而直接使用gcc需要手动指定每个源文件的编译命令,不具备自动化的构建功能。
会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作 makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。 make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一 种在工程方面的编译方法。
make是一条命令,makefile是一个文件,两个搭配使用,完成项目自动化构建。
也就是说,想完成项目的自动化构建,我们需要两个东西
一个是make,它是一条指令,那我们到时候可以直接用,另一个是makefile,它是一个文件,这个文件由我们自己创建,一般就直接创建在当前源代码所在路径下
那具体要怎么做呢? 接下来我们先给大家演示一下,然后再具体解释
2. 使用演示
2.1 生成
接下来就先给大家演示一下:
首先这里已经有一份源代码了
那上面我们说了,需要我们自己创建一个文件——Makefile(大小写都可)
然后,我们用vim打开Makefile文件 那我现在想把最后生成的可执行程序起名为myfile,那myfile是由myfile.c形成的 所以我这样写:
然后回车,切记回车之后一定要Tab一下,这是语法 然后呢,这里面其实还是用gcc对源代码进行翻译。 所以,Tab之后写gcc myfile.c -o myfile
相信这句代码就不用解释了,我们上一篇文章刚学
那这就写完了,我们保存退出
然后我们现在想把myfile.c生成可执行程序,直接make就行(它就会自动去执行Makefile里面的gcc命令)
我们看到,可执行文件myfile就生成了
执行:
没有问题。
2.2 清理
那如果我想把生成的myfile清理掉呢?
正常我们可以使用rm命令删除文件。
但其实我们也可以把它放到Mkfile里面
vim打开Makefile,这样写
大家先看,语法后面介绍 然后保存退出
此时我们想清理的话:
直接make clean
他就会自动帮我们执行清理的命令
后续,如果我们修改了源文件
然后如果想重新生成,直接make
想删除,直接make clean
3. 语法及概念介绍
3.1 makefile 的语法
然后,我们再回过来看Makefile,它为什么要那样写呢?该怎么理解呢?
我们打开它:
接下来我们就来解释一下
首先看第一行,为什么这样写呢? 语法:
代码语言:javascript
复制
target: dependencies
command
target是目标文件名,dependencies是目标文件依赖的文件列表(如果有多个用空格隔开),command是构建目标文件的命令(记得前面加Tab)。 那对应我们写的:
即myfile是由myfile.c得到的;myfile是目标文件,myfile.c是目标文件形成需要依赖的文件,第二行gcc myfile.c -o myfile
就是构建目标文件的命令。
我们把目标文件和他所依赖的文件列表之间的关系称为依赖关系,对应的命令称为依赖方法
所以: makefile是一个国绕依赖关系和依赖方法构建的一个自动化编译的工具
3.2 依赖关系与依赖方法的理解
那么我们该怎么理解依赖关系和依赖方法这两个名词呢?
下面通过一个例子帮助大家理解一下: 假设你在学校上学,到月底了,你钱花光了,于是就打算给你爸爸打电话要钱。 但是呢,电话接通,你只给你爸说了一句:“是我啊老爸,您儿子”。 然后就直接把电话挂了。 那你觉得你问你爸要钱这件事能办成嘛? 显然不能,因为你只跟你爸表明了依赖关系,你爸并不能知道你想干嘛。 所以,正常情况下,只有依赖关系是做不成一件事的。 你除了跟你爸说你是谁之外,你还应该说:“我的生活费花完了,你给我打点钱吧”。 这样你爸才会给你打钱。 那后半句话,就可以认为是依赖方法。 但是,如果你给你爸打电话说:我是你儿子,所以你应该给我写作业。那这个依赖方法显然是不合理的,你可能会挨一顿打。 又或者你给你室友的爸爸打电话,让人家给你打钱,那你的依赖关系肯定是不正确的。所以正常情况下,同时具备了正确的依赖关系和正确的依赖方法,才能做出一件事。
所以,在这里也是一样:
你想得到可执行程序,得有源代码啊(依赖关系),有了源代码,你得用编译器去编译链接啊(依赖方法)。 然后make命令,它会自动分析文件的依赖关系,决定哪些文件需要重新编译,然后执行相应的构建规则。
所以,总结一下:
make是一个基于文件依赖关系的构建工具,它可以根据指定的规则和条件来自动更新程序的部分或全部,从而减少手动编译的工作量。通过使用make,你可以只重新编译已修改的源文件,而不是整个项目,提高了编译的效率。 makefile是一个文本文件,它包含了构建目标(target)和构建规则(rule)。在makefile中,你可以定义编译器的选项、源文件的依赖关系以及如何生成可执行程序等内容。make命令会读取makefile文件并根据其中的规则来进行构建。
使用make和makefile的主要步骤如下:
创建一个makefile文件,并为各个目标指定构建规则。 在makefile中定义源文件之间的依赖关系以及对应的编译命令。 运行make命令,它会自动分析文件的依赖关系,决定哪些文件需要重新编译,然后执行相应的构建规则。
3.3 make 的工作原理
接下来我把上面写的Makefile修改一下:
大家想一下,如果我们想生成可执行文件,除了可以依赖源文件myfile.c之外,还可以依赖什么? 是不是还可以是myfile.o啊,因为.o链接之后就成可执行exe了嘛。
但是这样写的话,我们没有myfile.o 那就要先生成myfile.o
那这样又要先生成myfile.s
那要先生成myfile.i
所以是这样的。 从上到下就像是一个压栈的过程,而最后执行的顺序其实就是出栈的顺序。
我们可以看一下:
执行make之后显示的其实就是执行的顺序
make是如何工作的,在默认的方式下,也就是我们只输入make命令。那么:
- make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
- 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“myfile”这个文件, 并把这个文件作为最终的目标文件。
- 如果myfile文件不存在,或是myfile所依赖的后面的myfile.o文件的文件修改时间要比myfile这个文件新(可以用 touch 测试,这个后面会提到),那么,他就会执行后面所定义的命令来生成hello这个文件。
- 如果myfile所依赖的myfile.o文件不存在,那么make会在当前文件中找目标为myfile.o文件的依赖性,如果找到则再根据那一个规则生成myfile.o文件,以此类推,直至依赖的那个文件存在,则开始依次向上生成。(这有点像一个堆栈的过程)
- 这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。
- 在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。
3.4 依赖文件列表可以为空
我们刚开始演示的时候其实也演示过清理了,那现在针对刚才的那个例子,我们来写一下清理:
首先,我们上面说了,正常情况下,需要同时具备正确的依赖关系和正确的依赖方法。 但其实也有特殊情况: 在makefile中,依赖文件列表可以为空,表示目标文件没有任何依赖。
那清理操作其实就是这样
通常情况下,clean规则的依赖文件列表为空,因为清理操作不依赖于其他文件。 所以,clean我们直接这样写就行
然后,我们make clean
的时候,他就会自动执行clean对应的依赖方法
3.5 make默认只执行makefile文件中的第一个目标规则
那经过上面的学习,不知道大家有没有这样的疑惑:
为什么我们刚才执行生成操作的时候直接make
就行了,而清理的时候是make clean
,要把clean加上? 这是因为: 在终端中运行make命令时,我们可以指定目标。如果没指定,默认情况下,它只会执行makefile文件中的第一个目标规则。 而我们上面写的,生成myfile的目标规则是在第一个,而清理的在后面,所以,我们不指定的话,默认执行的就是第一个即生成myfile的操作。
那大家可以试一下:
如果你把清理放在第一个的话,我们直接make,执行的就是清理了。 你要想生成myfile,就需要显示指定了:make myfile
但是,我们一般把清理的规则放在最后。
3.6 伪目标
另外:
一般clean的目标文件,我们将它设置为伪目标,用 .PHONY
修饰,伪目标的特性是,总是被执行的。
什么意思呢?
还是以这个为例 大家看
我两次执行make(不指定默认是第一个即make myfile),有什么不同。 第一次make执行了makefile文件中的第一个目标规则,并生成了对应的文件。 但是第二次make,并没有执行对应的操作,而是告诉我们: myfile' is up to date.
——myfile已经是最新的 意思就是生成的myfile是最新的了,没必要在重新生成了。
那大家还记不记得我们最开始演示的:
这里面的clean我们多加了一个修饰 .PHONY
而我们刚才也提到: 一般clean的目标文件,我们将它设置为伪目标,用 .PHONY
修饰,伪目标的特性是,总是被执行的。 也就是说被 .PHONY
修饰后它的特性是总是被执行的。
那总是被执行的是什么意思呢?
我现在再用 .PHONY
修饰clean
然后执行make clean
我们发现它可以连续执行,即使被清理过了,还可以执行成功
而我们的make:
我们看到第一次可以执行,后面如果没有对源文件进行删除修改的话,就不能在执行了。 那这就不是总是被执行的 如果也把myfile加上.PHONY
修饰的话:
他就变成总是被执行了。
那现在问题来了:我们myfile不加.PHONY
修饰的时候,make执行一次之后,再执行的话就不行了,他告诉我们已经是最新的了。
3.7 touch更改文件时间
那它是怎么知道当前产生的文件已经是最新的了?
其实大家试一下就会发现,如果我们make之后,再去修改一下源文件的话,其实就可以继续make了。 因为源代码修改了,之前产生的可执行文件就不是最新的了。 所以它其实是通过对比源代码最后一次被修改的时间和可执行程序生成的时间来确定生成的可执行程序是不是最新的。 第一次生成可执行程序,文件生成时间肯定比源代码修改保存的时间新,所以你再make他就不让你make了,因为此时的可执行文件就是最新的了。 但是如果后面我们修改了源代码,那此时源代码的修改时间就比可执行程序生成的时间更新了,所以这种情况我们是可以重新make的。
那如果我们不修改源代码,能不能修改文件的时间呢?
可以的,使用touch 其实我们之前学touch这个命令的时候也提到过,touch除了可以创建文件还可以修改文件或目录的时间