Linux:详细解析项目自动化构建工具--make/Makefile
背景
-
会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力
-
一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作
-
makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
-
make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。
-
make是一条命令,makefile是一个文件,两个搭配使用,完成项目自动化构建。
make/Makefile:项目自动化构建工具
Makefile: 记录项目构建规则流程的普通文件
make: Makefile解释器,当前命令行下执行make,这时候这个解释程序会到当前路径下寻找名字叫Makefile的文件,对其内部的内容进行解释执行找到Makefile中第一个目标对象,通过依赖对象的时间关系判断是否需要重新生成,若需要则执行命令,完毕后退出;
若依赖对象也需要生成,则会在下边的生成规则中找到依赖对象的生成规则,先生成依赖对象。
C++代码
#include <iostream>
using namespace std;
int main() {
printf("hello Makefile!");
return 0;
}
Makefile文件
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
.PHONY:clean
clean:
rm -f hello.i hello.s hello.o hello
依赖关系
- 上面的文件 test ,它依赖 test.o
- test.o , 它依赖 test.s
- test.s , 它依赖 test.i
- hello.i , 它依赖 test.cpp
依赖方法
-
g++ test.* -option test.*
,就是与之对应的依赖关系
原理
make是如何工作的,在默认的方式下,也就是只输入make命令
-
make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
-
如果找到,它会找文件中第一个目标文件(target),在上述例子中,它会找到
test
这个文件,并将其作为最终的目标文件 -
如果
test
文件不存在,或是test
所依赖的后面的test.o
文件的修改时间要比test
这个文件新,那么,它就会执行后面所定义的命令来生成test
这个文件 -
如果
test
所依赖的test.o
文件不存在,那么make会在当前文件中找目标为test.o
文件的依赖性,如果找到则再根据那一个规则生成test.o
文件(这有点像一个堆栈的过程) -
当然,你的C文件和H文件是存在的,于是make会生成
test.o
文件,然后再用test.o
文件声明make的终极任务,也就是执行文件hello -
这就是整个make的依赖性,make会一层又一层的去找文件的依赖关系,直到最终编译出第一个目标文件
-
在寻找的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出并报错,而对于所定义的命令的错误,或是编译不成功,或是make根本不理会
-
make只管文件的依赖性,即:如果在找了依赖关系后,毛好后面的文件还是不存在,那么它就不工作了
-
工程是要被清理的
-
像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令不会被自动执行,不过,我们可以显示要make执行,即命令:make clean,以此来清除所有目标文件,以便重新编译
-
一般我们要clean的目标文件,我们将它设置为伪目标,用.PHONY修饰,伪目标的特性是总是被执行的
Makefile的编写规则:
-
目标对象(要生成的文件名称)
-
依赖对象(用于判断目标对象是否最新是否需要重新生成)
-
tab 命令(生成这个目标对象所要执行的命令,但是有时候这个命令也不一定非要生成目标对象)
预定义变量:
$< 第一个依赖文件
$^ 所有的依赖文件
[email protected] 目标文件
#[目标对象:要生成的可执行程序或者动态库或者静态库]:[依赖对象]
# [执行的命令]gcc a.c -o a
# [email protected]:[目标对象] $^:[所有的依赖的对象] $<:[第一个依赖的对象]
#a:a.o
# gcc a.c -o a
#a.o:a.c
# gcc -c a.c -o a.o
#
#a:a.c
# gcc a.c b.c -o a
#
#a:a.c b.c
# gcc $^ -o [email protected]
make的解释执行规则:
-
make在Makefile中只找第一个目标对象,为了生成这个目标对象,而执行命令,完毕之后直接退出(后边的对象都不会在生成了)
-
make在Makefile中找到第一个对象,这时候这个对象的依赖对象不存在,则在后续编译规则中,寻找是否可以生成这个依赖对象,当所有依赖对象生成完毕后,最终生成目标对象.
.PHONY的使用
.PHONY:$(target)
声明伪对象:使一个目标对象无论如何每次都需要重新生成
.PHONY 后面定义的是伪目标
所谓伪目标就是这样一个目标,它不代表一个真正的文件名,在执行make时可以指定这个目标来执行其所在规则定义的命令,有时我们将一个伪目标成为标签。
为什么要使用伪目标?
- 为了避免在makefile中定义的只执行命令的目标和工作目录下的实际文件出现名字冲突
- 为了提交执行makefile时的效率。
.PHONY配置项的主要作用在于避免指定命令和项目下的同名文件冲突,进行性能优化。.PHONY [command name] 显式指定了一个命令名称【而不是实际文件名】,是为了避免该命令名称与makefile同路径下的文件名重名冲突,以使make [command name]命令可以正确被解析、执行。 下面将举例说明。
如果编写一个规则名称为clean,不产生目标文件,那么该命令在执行make clean时规则都会被执行,makefile内容如下:
clean:
rm -f hello.i hello.s hello.o hello
rm 命令不产生任何clean文件。
如果目录下不存在名为"clean"的文件,则运行make clean时,命令都会正常执行;
如果目录下存在名为"clean"的文件,则运行make clean时,命令规则会失效,文件"clean"没有依赖文件,始终是最新的,make clean永远不会被执行。
为了解决上述问题,可以使用.PHONY clean指明该clean命令。如下:
.PHONY clean
clean:
rm -f *.o temp
这样执行命令make clean会自动忽略名为"clean"文件的存在。已知.PHONY配置项的目标并不是其他文件生成的实际文件,make命令会自动绕过隐含规则搜索过程,因此声明**.PHONY配置项会改善性能**,并且不需要担心实际同名文件存在与否。
如有不同见解,欢迎留言讨论~~
上一篇: GNU make
下一篇: GNU Make 目录搜索