🖌概述: makefile关系到整个工程的编译规则,一个工程的源文件不计数,其按类型、功能、模块分别放在若干个目录中,Makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,因其就像shell脚本一样,也可以执行操作系统的命令。Makefile最大的好处就是“自动化编译”,一旦写好,只需要一个make命令(make是一个命令工具,用于解释Makefile中指令的命令工具),整个工程完全自动编译。
目录
1、背景介绍
1.1 make的本质
“文件依赖性”
1.2 程序的编译和链接
1)无论是c、c++还是pas,首先要把源文件编译成中间代码文件,在Windows下也就是 .obj 文件,UNIX下是 .o 文件,即 Object File,这个动作叫做编译(compile)
注1:一般来说,每个源文件都应该对应于一个中间目标文件即O文件或者OBJ文件)。然后再把大量的Object File合成执行文件,这个动作叫作链接(link)。
注2:链接主要是链接函数和全局变量,故我们可以使用中间目标文件来链接我们的应用程序;多数情况下,源文件太多,导致编译生成的中间目标文件太多,故我们需要给中间目标文件打个包即(win下叫库文件library file即.lib文件;Unix下叫Archive file即.a文件)
2)在编译时,编译器只检测程序语法,和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成Object File。而在链接程序时,链接器会在所有的Object File中找寻函数的实现,如果找不到,那就会报链接错误码(Linker Error),在VC下,这种错误一般是:Link 2001错误,意思就是说,链接器未能找到函数的实现。你需要指定函数的ObjectFile.
2、makefile规则定义
make命令执行时,需要一个 Makefile 文件,以告诉make命令需要怎么样的去编译和链接程序。makefile的规则如下:
target... : prerequisites ...
command
...
...
注:1.target也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label)。
2. prerequisites就是,要生成那个target所需要的文件或是目标。
3. command也就是make需要执行的命令。(任意的Shell命令)
书写规则就是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容。
3、makefile例子
注:cc是unix下面用的编译命令,gcc是linux下面用的编译命令
edit : main.o kbd.o command.o display.o insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o
main.o : main.cpp defs.h
cc -c main.cpp
kbd.o : kbd.cpp defs.h command.h
cc -c kbd.cpp
command.o : command.cpp defs.h command.h
cc -c command.cpp
display.o : display.cpp defs.h buffer.h
cc -c display.cpp
insert.o : insert.cpp defs.h buffer.h
cc -c insert.cpp
search.o : search.cpp defs.h buffer.h
cc -c search.cpp
files.o : files.cpp defs.h buffer.h command.h
cc -c files.cpp
utils.o : utils.cpp defs.h
cc -c utils.cpp
clean :
rm edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o
4、makefile例子分析
4.1 cc命令的含义
1)clean这个动作名字从来都是放在文件的最后, 用于清理生成的中间文件和可执行程序,其编写规则如下(建议每个makefile中都应该含有一个清空目标文件的规则,这样便于重编译,同时利于保持文件的清洁):
clean:
rm lexer.c $(object) count_words
2) 变量定义的最简单形式(若变量名只有一个字符时,括号可以省略):$(variable_name);另外object变量的定义是为了makefile的易维护,可以理解成C语言中的宏,更改后的代码如下所示;
3) cc -c :编译.c或汇编源文件,但是不作连接,编译器输出对应于源文件的目标文件;(只编译不链接);
4) cc -o :允许用户指定输出文件名,输出可执行程序;(完成链接操作,生产某个节点);
5)命令command一定要以一个Tab键作为开头;切记:make并不管命令是怎么工作的,其只管执行所定义的命令。 make只管文件的依懒性,而对于所定义的命令的错误,或是编译不成功,make不会去理会,若冒号后面依赖的文件不在,则make就会直接退出并报错;
4.2 makefile流程
先去查找第一条规则目标,第一条规则的目标称为缺省目标,只有缺省目标更新了就算完成任务了,其他工作都是为这个目的而做的;
4.3 规则执行顺序
1) 先检查它的依赖文件,如果依赖文件需要更新,则执行以该文件为目标的的规则。如果没有该规则但找到文件,那么该依赖文件不需要更新。如果没有该规则也没有该文件,则报错退出。
2) 再检查该文件的目标,如果目标不存在或者目标存在但依赖文件修改时间比他要晚或某依赖文件已更新,那么执行该规则的命令。
4.4 示例的“依赖书关系”图
4.5 改良版的makefile
objects = main.o kbd.o command.o display.o insert.osearch.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit $(objects)
若有新的.o文件加入,我们只需要简单地修改一下objects变量就好了;
4.6 让make自动推导
GNU的make很强大,它可以自动推导文件以及文件依赖关系后面的命令,于是我们就没必要去在每一个[.o]文件后都写上类似的命令,因为,我们的make会自动识别,并自己推导命令。 只要make看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中,如果make找到一个whatever.o,那么whatever.c,就会是whatever.o的依赖文件。并且 cc -c whatever.c 也会被推导出来,于是,我们的makefile再也不用写得这么复杂。更改后如下:
objects = main.o kbd.o command.o display.o insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h
.PHONY : clean
clean :
rm edit $(objects)
这种方法,也就是make的“隐晦规则”。上面示例中,“.PHONY”表示,clean是个伪目标文件。
4.7 定义变量
4.7.1 变量定义的说明
变量在声明时需要给予初值,而在使用时,需要在变量名前加上“$”符号,但最好用小括号“()”或是花括号”{}”把变量给引用起来。
4.7.2 赋值变量
1)使用“=”操作符,使得前面的变量可以通过后面的变量来定义。
2)使用“:=”操作符,使得前面的变量不能使用后面的变量,只能使用前面已定义好了的变量。
3)使用”?=”操作符:例如foo ?= bar 如果foo没有被定义过,那么变量foo的值就是“bar”,否则此语句什么也不做。
4)使用“+=”操作符,可以追加值;
objects=main.o foo.o bar.o
objects+=another.o
等同于
objects=main.o foo.o bar.o
objects:=$(objects) another.o
5. 关于makefile的那点事
5.1 makefile里有什么
Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。
- 显式规则。显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。
- 隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。
- 变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点像C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
- 文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。
- 注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“\#”。
在Makefile中的命令,必须要以[Tab]键开始。
5.2 makefile的文件名
默认的情况下,make命令会在当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”的文件,找到了解释这个文件。在这三个文件名中,最好使用“Makefile”这个文件名,因为,这个文件名第一个字符为大写,这样有一种显目的感觉。最好不要用“GNUmakefile”,这个文件是GNU的make识别的。有另外一些make只对全小写的“makefile”文件名敏感,但是基本上来说,大多数的make都支持“makefile”和“Makefile”这两种默认文件名。当然,你可以使用别的文件名来书写Makefile,比如:“Make.Linux”,“Make.Solaris”,“Make.AIX”等,如果要指定特定的Makefile,你可以使用make的“-f”和“--file”参数,如:make -f Make.Linux或make --file Make.AIX。
5.3 make的工作方式
GNU的make工作时的执行步骤如下:(想来其它的make也是类似)
1. 读入所有的Makefile。
2. 读入被include的其它Makefile。
3. 初始化文件中的变量。
4. 推导隐晦规则,并分析所有规则。
5. 为所有的目标文件创建依赖关系链。
6. 根据依赖关系,决定哪些目标要重新生成。
7. 执行生成命令。
1-5步为第一个阶段,6-7为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么,make会把其展开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。当然,这个工作方式你不一定要清楚,但是知道这个方式你也会对make更为熟悉。有了这个基础,后续部分也就容易看懂了。
5.4 书写规则
1) makefile的书写规则包含两个部分,一个是依赖关系,一个是生成目标的方法。在Makefile中,规则的顺序是很重要的,因为,Makefile中只应该有一个最终目标,其它的目标都是被这个目标所连带出来的,所以一定要让make知道你的最终目标是什么。一般来说,定义在Makefile中的目标可能会有很多,但是第一条规则中的目标将被确立为最终的目标。如果第一条规则中的目标有很多个,那么,第一个目标会成为最终的目标。make所完成的也就是这个目标。
2)规则语法:
targets : prerequisites
command
...
或是这样:
targets : prerequisites ; command
command
...
targets是文件名,以空格分开,可以使用通配符。一般来说,我们的目标基本上是一个文件,但也有可能是多个文件。
command是命令行,如果其不与“target:prerequisites”在一行,那么,必须以[Tab键]开头,如果和prerequisites在一行,那么可以用分号做为分隔。(见上)
prerequisites也就是目标所依赖的文件(或依赖目标)。如果其中的某个文件要比目标文件要新,那么,目标就被认为是“过时的”,被认为是需要重生成的。
如果命令太长,你可以使用反斜框(‘\’)作为换行符。make对一行上有多少个字符没有限制。规则告诉make两件事,文件的依赖关系和如何成成目标文件。
一般来说,make会以UNIX的标准Shell,也就是/bin/sh来执行命令。