makefile
1、什么是makefile
Windows 下的集成开发环境(IDE)已经内置了 Makefile,或者说会自动生成 Makefile,我们不用去手动编写。
Linux 中却不能这样,需要我们去手动的完成这项工作。不会 Makefile,就操作不了多文件编程,就完成不了相对于大的工程项目的操作。
Makefile 可以简单的认为是一个工程文件的编译规则,描述了整个工程的编译和链接等规则。其中包含了那些文件需要编译,那些文件不需要编译,那些文件需要先编译,那些文件需要后编译,那些文件需要重建等等。换句话说,由了 Makefile 可以使得我们的项目工程的编译变得自动化,不用每次都手动输入一堆源文件和参数。
如果我们源文件非常的多的话,我们就会遇到下面的这些问题。
(1)编译的时候需要链接库的的问题。我们那拿C语言来说,编译的时候 gcc 只会默认链接一些基本的C语言标准库,很多源文件依赖的标准库都需要我们手动链接。
下面列举了一些需要我们手动链接的标准库:
name1.c 用到了数学计算库 math 中的函数,我们得手动添加参数 -Im;
name4.c 用到了小型数据库 SQLite 中的函数,我们得手动添加参数 -lsqlite3;
name5.c 使用到了线程,我们需要去手动添加参数 -lpthread。
因为有很多的文件,还要去链接很多的第三方库。所以在编译的时候命令会很长,并且在编译的时候我们可能会涉及到文件链接的顺序问题,所以手动编译会很麻烦。
如果我们会使用 Makefile 就不一样了,它会彻底简化我们的操作。可以把要链接的库文件放在 Makefile,制定相应的规则和对应的链接顺序。这样我们只需要执行 make 命令,工程就会自动编译。每次想要编译工程的时候就执行 make ,省略掉我们在手动编译中的参数选项和命令,非常的方便。
(2)编译大的工程会花费很长的时间。
如果我们去做项目开发,免不了要去修改工程项目的源文件,每次修改后都要去重新编译。一个大的工程项目可不止有几个的源文件,里面的源文件个数可能有成百上千个。例如一个内核,或者是一个软件的源码包。这些都是我们做开发经常会遇到的。要完成这样的文件的编译,我们消耗的时间可不是一点点。如果文件特别大的话我们可能要花上半天的时间。
对于这样的问题我们 Makefile 可以解决吗?当然是可以的,Makefile 支持多线程并发操作,会极大的缩短我们的编译时间,并且当我们修改了源文件之后,编译整个工程的时候,make 命令只会编译我们修改过的文件,没有修改的文件不用重新编译,也极大的解决了我们耗费时间的问题。
这其实我们遇到的相比较来说比较常见的问题,当然我们遇到的问题还会有很多,比如:
我们的工程文件中的源文件的类型不止一种,编译的话需要选择的编译器。
我们的文件可能会分布在不同的目录中,我们需要查找。
这些问题我们都可以通过 Makefile 解决。并且我们文件中的 Makefile 只写一次就行,一般我们只要不增加或者是删除工程中的文件,Makefile 基本上不用去修改,编译时只用一个 make 命令。为我们提供了极大的便利。有助于提高我们的效率。
2、makefile文件包含的规则
Makefile 是文件编译的相关规则的描述,它的规则主要是两个部分组成,分别是依赖的关系和执行的命令,其结构如下所示:
targets:prerequisites
command
或者是
targets:prerequisites;command
command
相关说明如下:
targets:我们的目标,可以是 Object File(我们称它为中间的文件),也可以是可执行文件,还可以是一个标签;
prerequisites:是我们的依赖文件,要生成 targets 需要的文件或者是目标。可以是多个,也可以是没有;
command:make 需要执行的命令(任意的 shell 命令)。可以有多条命令,每一条命令占一行。
注意:我们的目标和依赖文件之间要使用冒号分隔开,命令的开始一定要使用Tab键。
我们可以的简单的介绍一下 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 中使用“#”字符,可以用反斜框进行转义,如:“\#”。
3、Makefile的工作流程
当我们在执行 make 条命令的时候,make 就会去当前文件下找要执行的编译规则,也就是 Makefile 文件。
"GNUmakefile" 、"makefile" 、"Makefile" 这是我们编写Makefile的时可以使用的文件的名称,make 在寻找文件的时候也是按照这样的顺序去执行的。
我们推荐使用 Makefile(一般在工程中都这么写,大写的会比较的规范)。如果文件不存在,make 就会给我们报错,提示:make:*** 没有明确目标并且找不到 makefile。停止
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 之后没有被修改),则什么也不做。
通过上面的更新规则我们可以了解到中间文件的作用,也就是我们编译时生成的 ".o" 文件。检查我们当中的某个源文件是不是进行过修改,最终目标文件是不是需要重建。如果存在 ".o" 文件,我们再去执行 make 的时,只有修改过的源文件或者是不存在的目标文件会进行重建,而那些没有改变的文件不用重新编译,这样会在很大程度上节省我们的时间,提高我们的编程效率。小的工程项目我们可能体会不到,项目工程文件越多,这样的效果才越明显。
当然 Makefile 能否顺利的执行,还在于我们是否制定了正确的的依赖规则,我们的当前目录下是不是存在我们需要的依赖文件,只要是任意一点不满足,我们在执行 make 的时候就会出错。所以正确的完成一个 Makefile 是一件很不容易的事情。
清除工作目录中的过程文件
我们在使用的时候会产生中间文件会让我们的文件看起来很乱,所以我们在编写 Makefile 文件的时候会在末尾加上这样的语句:
.PHONY:clean
clean:
rm -rf *.o test
其中 "*.o" 是我们执行过程中产生的中间文件,"test" 是我们的最终生成的执行文件。我们可以看到我们的 clean 是独立的,它只是一个伪目标,不是具体的文件。不会跟我们的第一个目标文件相关联,所以我们在执行 make 的时候也不会执行下面的命令。当我们相要去使用 clean 的时候可以直接使用命令 "make clean",编译时的中间文件和我们生成的最终目标文件都会被清除,方便我们下次的使用。

浙公网安备 33010602011771号