(一)什么是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 中的命令,必须要以[Tab]键开始。
(二)Makefile优势与make命令
Makefile带来的好处就是------“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
make是一个命令工具,是解释Makefile中指令的命令工具,一般来说,大多数IDE都有这个命令,Visual C++de nmake.linux下的GNU的make。可见,makefile都成为一种在工程方面的编译方法。
make命令执行时,需要一个Makefile文件,已告诉make命令需要怎么样的去编译和链接程序。
(三)Makefile文件示例
现在有5个文件,分别为:main.c my1.h my2.h my1.c my2.c;现在编写Makefile文件,名称为Makefile过程如下:
main:main.o my1.o my2.o
gcc -o main main.o my1.o my2.o
main.o :main.c my1.h my2.h
gcc -c main.c
my1.o:my1.c my1.h
gcc -c my1.c
my2.o:my2.c my2.h
gcc -c my2.c
clean:
rm -f *.o main
总结:
target_ _ :prerequisites_ _
command
_ _
_ _
target:也就是一个目标文件,也可以是Objject File,也可以是执行文件。还可以是一个标签(Lable)。
prerequisites:要生成target所需要的文件或是目标。
command:make需要执行的命令
这是一个文件的依赖关系,也就是说,target 这一个或多个的目标文件依赖于 prerequisites 中的文件,其生成规则定义在 command 中。说白一点就是说,make 会比较 targets 文件和 prerequisites 文件的修改日期,如果 prerequisites 文件的日期要比 targets 文件的日期要新,或者 target 不存在的话,那么,make 就会执行后续定义的命令。 这就是 Makefile 的 规则。也就是 Makefile 中核心的内容。
在定义好依赖关系后,后续的那一行定义了如何生成目标文件的操作系统命令,一定要以一 个 Tab 键作为开头。
每个 Makefile 中都应该写一个清空目标文件(.o 和执行文件)的规则,这不仅便于重编译, 也很利于保持文件的清洁
(四)make工作原理
当我们输入make时:
- make会首先在当前目录下寻找makefile文件;
- 如果找到,它会找到文件中的第一个目标文件(target),在上面的例子中,他会找到 “main”这个文件,并把这个文件作为最终的目标文件;
- 如果main文件不存在,或者main所依赖的后面的.o文件的修改时间要比main这个文件新,那么他就会执行后面所定义的命令来生成main这个文件;
- 如果main所依赖的.o文件也存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到在跟就那一个规则生成.o文件;
- 当.c文件和.h文件都是存在的,那么make会生成.o文件,然后再用.o文件声明make的最终文件,也就是执行文件mian.
这就是整个 make 的依赖性,make 会一层又一层地去找文件的依赖关系,直到终编译出 第一个目标文件。在找寻的过程中,如果出现错误,比如后被依赖的文件找不到,那么 make 就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make 根本不 理。make 只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在, 那么对不起,我就不工作啦。
(五)makefile文件优化
(1)使用变量
问题导入;
main:main.o my1.o my2.o
gcc -o main main.o my1.o my2.o
我们可以看到[.o]文件的字符串被重复了三次(包括clean),如果我们的工程需要加入一个新的[.o]文件, 那么我们需要在三个地方加,虽然不复杂,但是如果文件一旦过多,难免会出错,继而导致编译失败。所以为了makefile的易维护性,引入变量。
比如,我们声明一个变量,叫 objects,只要能够表示 obj 文件就行了。我们在 makefile 一开始就这样定义:
objects = main.o my1.o my2.o (在 makefile 中以“$(objects)”的方式来使用这个变量了)
改良版
objects = main.o my1.o my2.o
main:$(objects)
gcc -o main $(objects)
main.o :main.c my1.h my2.h
gcc -c main.c
my1.o:my1.c my1.h
gcc -c my1.c
my2.o:my2.c my2.h
gcc -c my2.c
clean:
rm -f $(objects) main
(2)自动推导
GNU 的 make 很强大,它可以自动推导文件以及文件依赖关系后面的命令,于是我们就没必要去在每一个[.o]文件后都写上类似的命令,因为我们的 make 会自动识别,并自己推导命令。 只要 make 看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中,如果 make 找到一 个 my1.o,那么 my1.c,就会是 my1.o 的依赖文件。并且 cc -c my1.c 也会被推导出来,于是,我们的 makefile 再也不用写得这么复杂。我们的是新的 makefile 又出炉 了。
objects = main.o my1.o my2.o
main:$(objects)
gcc -o main $(objects)
main.o :my1.h my2.h
my1.o:my1.h
my2.o:my2.h
clean:
rm -f $(objects) main
这种方法,也就是 make 的“隐晦规则”。
(3)另类风格
即然我们的 make 可以自动推导命令,那么我看到那堆[.o]和[.h]的依赖就有点不爽,那么多的重复的[.h],能不能把其收拢起来,好吧,没有问题,这个对于 make 来说很容易,谁叫它提供了自动推导命令和文件的功能呢,来看看新风格的 makefile 吧。
objects = main.o my1.o my2.o
main:$(objects)
gcc -o main $(objects)
main.o my1.o:my1.h
main.o my2.o:my2.h
clean:
rm -f $(objects) main
这种风格,让我们的 makefile 变得很简单,但我们的文件依赖关系就显得有点凌乱了。鱼和 熊掌不可兼得。
(4)环境变量VPATH
在一些大的工程中,有大量的源文件,我们通常的做法是把这许多的源文件分类,并存放在 不同的目录中。所以,当 make 需要去找寻文件的依赖关系时,你可以在文件前加上路径, 但好的方法是把一个路径告诉 make,让 make 在自动去找。
Makefile 文件中的特殊变量“VPATH”就是完成这个功能的,如果没有指明这个变量,make 只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,make 就会 在当当前目录找不到的情况下,到所指定的目录中去找寻文件了。