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命令

  1. make会在当前目录下找名字叫“Makefile”或“makefile”的文件。

  2. 如果找到,它会找文件中第一个目标文件(target),在上述例子中,它会找到test这个文件,并将其作为最终的目标文件

  3. 如果test文件不存在,或是test所依赖的后面的test.o文件的修改时间要比test这个文件新,那么,它就会执行后面所定义的命令来生成test这个文件

  4. 如果test所依赖的test.o文件不存在,那么make会在当前文件中找目标为test.o文件的依赖性,如果找到则再根据那一个规则生成test.o文件(这有点像一个堆栈的过程)

  5. 当然,你的C文件和H文件是存在的,于是make会生成test.o文件,然后再用test.o文件声明make的终极任务,也就是执行文件hello

  6. 这就是整个make的依赖性,make会一层又一层的去找文件的依赖关系,直到最终编译出第一个目标文件

  7. 在寻找的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出并报错,而对于所定义的命令的错误,或是编译不成功,或是make根本不理会

  8. make只管文件的依赖性,即:如果在找了依赖关系后,毛好后面的文件还是不存在,那么它就不工作了

  9. 工程是要被清理的

  10. 像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令不会被自动执行,不过,我们可以显示要make执行,即命令:make clean,以此来清除所有目标文件,以便重新编译

  11. 一般我们要clean的目标文件,我们将它设置为伪目标,用.PHONY修饰,伪目标的特性是总是被执行的

Makefile的编写规则:

  1. 目标对象(要生成的文件名称)

  2. 依赖对象(用于判断目标对象是否最新是否需要重新生成)

  3. tab 命令(生成这个目标对象所要执行的命令,但是有时候这个命令也不一定非要生成目标对象)

预定义变量:
$< 第一个依赖文件
$^ 所有的依赖文件
$@ 目标文件

#[目标对象:要生成的可执行程序或者动态库或者静态库]:[依赖对象]
#     [执行的命令]gcc a.c -o a
#     $@:[目标对象] $^:[所有的依赖的对象] $<:[第一个依赖的对象]

#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 $@

make的解释执行规则:

  1. make在Makefile中只找第一个目标对象,为了生成这个目标对象,而执行命令,完毕之后直接退出(后边的对象都不会在生成了)

  2. make在Makefile中找到第一个对象,这时候这个对象的依赖对象不存在,则在后续编译规则中,寻找是否可以生成这个依赖对象,当所有依赖对象生成完毕后,最终生成目标对象.

.PHONY的使用

.PHONY:$(target) 声明伪对象:使一个目标对象无论如何每次都需要重新生成

.PHONY 后面定义的是伪目标

所谓伪目标就是这样一个目标,它不代表一个真正的文件名,在执行make时可以指定这个目标来执行其所在规则定义的命令,有时我们将一个伪目标成为标签。

为什么要使用伪目标?

  1. 为了避免在makefile中定义的只执行命令的目标和工作目录下的实际文件出现名字冲突
  2. 为了提交执行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配置项会改善性能**,并且不需要担心实际同名文件存在与否。

习题

  1. Makefile 怎么获取环境变量?

Makefile 用到环境变量时,不能直接使用$ORACLE_HOME,而是要使用 $(ORACLE_HOME)


如有不同见解,欢迎留言讨论~~

猜你喜欢

转载自blog.csdn.net/AngelDg/article/details/106611867