Makefile简介
Makefile 可以简单的认为是一个工程文件的编译规则,描述了整个工程的编译和链接等规则。其中包含了那些文件需要编译,那些文件不需要编译,那些文件需要先编译,那些文件需要后编译,那些文件需要重建等等。编译整个工程需要涉及到的,在 Makefile 中都可以进行描述。换句话说,Makefile 可以使得我们的项目工程的编译变得自动化,不需要每次都手动输入一堆源文件和参数。
如果一个非常大得C工程,会有非常多的源文件。通过gcc编译,存在以下问题:
1、编译的时候 gcc 只会默认链接一些基本的C语言标准库,很多源文件依赖的标准库都需要我们手动链接。很多的文件,还要去链接很多的第三方库。所以在编译的时候命令会很长,并且在编译的时候我们可能会涉及到文件链接的顺序问题,手动编译会很麻烦。Makefile 就不一样了,它会彻底简化编译的操作。把要链接的库文件放在 Makefile 中,制定相应的规则和对应的链接顺序。这样只需要执行 make 命令,工程就会自动编译。每次想要编译工程的时候就执行 make ,省略掉手动编译中的参数选项和命令。
2、编译工程会花费很长的时间。通过gcc的话,如果代码修改,每次都重新编译会非常慢。但是Makefile 支持多线程并发操作,并且当我们修改了源文件之后,编译整个工程的时候,make 命令只会编译我们修改过的文件,没有修改的文件不用重新编译。
Makefile规则
Makefile 描述的是文件编译的相关规则,它的规则主要是两个部分组成,分别是依赖的关系和执行的命令,其结构如下所示:
targets : prerequisites
command
或者
targets : prerequisites; command
command
相关说明如下:
- targets:规则的目标,可以是 Object File(一般称它为中间文件),也可以是可执行文件,还可以是一个标签;
- prerequisites:是我们的依赖文件,要生成 targets 需要的文件或者是目标。可以是多个,也可以是没有;
- command:make 需要执行的命令(任意的 shell 命令)。可以有多条命令,每一条命令占一行。
注意:目标和依赖文件之间要使用冒号分隔开,命令的开始一定要使用Tab
键。
例子:
test:test.c
gcc -o test test.c
上述代码实现的功能就是编译 test.c 文件,通过这个实例可以详细的说明 Makefile 的具体的使用。其中 test 是的目标文件,也是我们的最终生成的可执行文件。依赖文件就是 test.c 源文件,重建目标文件需要执行的操作是gcc -o test test.c
。这就是 Makefile 的基本的语法规则的使用。
使用 Makefile 的方式:首先需要编写好 Makefile 文件,然后在 shell 中执行 make 命令,程序就会自动执行,得到最终的目标文件。
简单的概括一下Makefile 中的内容,它主要包含有五个部分,分别是:
1) 显式规则
显式规则说明了,如何生成一个或多的的目标文件。这是由 Makefile 的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。
2) 隐晦规则
由于我们的 make 命名有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写 Makefile,这是由 make 命令所支持的。
3) 变量的定义
在 Makefile 中我们要定义一系列的变量,变量一般都是字符串,这个有点像C语言中的宏,当 Makefile 被执行时,其中的变量都会被扩展到相应的引用位置上。
4) 文件指示
其包括了三个部分,一个是在一个 Makefile 中引用另一个 Makefile,就像C语言中的 include 一样;另一个是指根据某些情况指定 Makefile 中的有效部分,就像C语言中的预编译 #if 一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。
5) 注释
Makefile 中只有行注释,和 UNIX 的 Shell 脚本一样,其注释是用“#”字符,这个就像 C/C++ 中的“//”一样。如果你要在你的 Makefile 中使用“#”字符,可以用反斜框进行转义,如:“#”
makefile的工作流程
当我们在执行 make 条命令的时候,make 就会去当前文件下找要执行的编译规则,也就是 Makefile 文件。我们编写 Makefile 的时可以使用的文件的名称 "GNUmakefile" 、"makefile" 、"Makefile" ,make 执行时回去寻找 Makefile 文件,找文件的顺序也是这样的。
推荐使用 Makefile(一般在工程中都这么写,大写的会比较的规范)。如果文件不存在,make 就会给我们报错,提示:
Makefile 的具体工作流程可以通过例子来看一下:创建一个包含有多个源文件和 Makefile 的目录文件,源文件之间相互关联。在 Makefile 中添加下面的代码:
main:main.o test1.o test2.o gcc main.o test1.o test2.o -o main main.o:main.c test.h gcc -c main.c -o main.o test1.o:test1.c test.h gcc -c test1.c -o test1.o test2.o:test2.c test.h gcc -c test2.c -o test2.o
在编译项目文件的时候,默认情况下,make 执行的是 Makefile 中的第一规则(Makefile 中出现的第一个依赖关系),此规则的第一目标称之为“最终目标”或者是“终极目标”。
在 shell 命令行执行的 make 命令,就可以得到可执行文件 main 和中间文件 main.o、test1.o 和 test2.o,main 就是我们要生成的最终文件。通过 Makefile 我们可以发现,目标 main"在 Makefile 中是第一个目标,因此它就是 make 的终极目标,当修改过任何 C 文件后,执行 make 将会重建终极目标 main。
它的工作顺序是:当在 shell 提示符下输入 make 命令以后。 make 读取当前目录下的 Makefile 文件,并将 Makefile 文件中的第一个目标作为其执行的“终极目标”,开始处理第一个规则(终极目标所在的规则)。在我们的例子中,第一个规则就是目标 "main" 所在的规则。规则描述了 "main" 的依赖关系,并定义了链接 ".o" 文件生成目标 "main" 的命令;make 在执行这个规则所定义的命令之前,首先处理目标 "main" 的所有的依赖文件(例子中的那些 ".o" 文件)的更新规则(以这些 ".o" 文件为目标的规则)。
对这些 ".o" 文件为目标的规则处理有下列三种情况:
- 目标 ".o" 文件不存在,使用其描述规则创建它;
- 目标 ".o" 文件存在,目标 ".o" 文件所依赖的 ".c" 源文件 ".h" 文件中的任何一个比目标 ".o" 文件“更新”(在上一次 make 之后被修改)。根据规则重新编译生成;
- 目标 ".o" 文件存在,目标 ".o" 文件比它的任何一个依赖文件(".c" 源文件、".h" 文件)“更新”(它的依赖文件在上一次 make 之后没有被修改),则什么也不做
过程文件清除
使用的时候会产生中间文件会让整个文件看起来很乱,所以在编写 Makefile 文件的时候会在末尾加上这样的规则语句:
.PHONY:clean
clean:
rm -rf *.o test
其中 "*.o" 是执行过程中产生的中间文件,"test" 是最终生成的执行文件。我们可以看到 clean 是独立的,它只是一个伪目标,不是具体的文件。不会与第一个目标文件相关联,所以我们在执行 make 的时候也不会执行下面的命令。在shell 中执行 "make clean" 命令,编译时的中间文件和生成的最终目标文件都会被清除,方便我们下次的使用。