默认的情况下,make执行Makefile中的第一个规则,此规则的第一个目标称之为“最终目的”或者“终极目标”(就是一个Makefile最终需要更新或者创建的目标)。
上例的Makefile,目标“edit”在Makefile中是第一个目标,因此它就是make的“终极目标”。当修改了任何C源文件或者头文件后,执行make将会重建终极目标“edit”。
当在shell提示符下输入“make”命令以后。make读取当前目录下的Makefile文件,并将Makefile文件中的第一个目标作为其“终极目标”,开始处理第一个规则(终极目标所在的规则)。在我们的例子中,第一个规则就是目标“edit”所在的规则。规则描述了“edit”的依赖关系,并定义了链接.o文件生成目标“edit”的命令; make在处理这个规则之前,首先将处理目标“edit”的所有的依赖文件(例子中的那些.o文件)的更新规则;对包含这些.o文件的规则进行处理。对.o文件所在的规则的处理有下列三种情况:
1. 目标.o文件不存在,使用其描述规则创建它;
2. 目标.o文件存在,目标.o文件所依赖的.c源文件、.h文件中的任何一个比目标.o文件“更新”(在上一次make之后被修改)。则根据规则重新编译生成它;
3. 目标.o文件存在,目标.o文件比它的任何一个依赖文件(的.c源文件、.h文件)“更新”(它的依赖文件在上一次make之后没有被修改),则什么也不做。
这些.o文件所在的规则之所以会被执行,是因为这些.o文件出现在“终极目标”的依赖列表中。如果在Makefile中一个规则所描述的目标不是“终极目标”所依赖的(或者“终极目标”的依赖文件所依赖的),那么这个规则将不会被执行。除非明确指定这个规则(可以通过make的命令行指定重建目标,那么这个目标所在的规则就会被执行,例如 “make clean”)。在编译或者重新编译生成一个.o文件时,make同样会去寻找它的依赖文件的重建规则(是这样一个规则:这个依赖文件在规则中作为目标出现),就是.c和.h文件的重建规则。在上例的Makefile中没有哪个规则的目标是.c或者.h文件,所以没有重建.c和.h文件的规则。不过C言语的源程序文件可以使用工具Bison或者Yacc来生成(具体用法可参考相应的手册)。
完成了对.o文件的创建(第一次编译)或者更新之后,make程序将处理终极目标“edit”所在的规则,分为以下三种情况:
1. 目标文件“edit”不存在,则执行规则创建目标“edit”。
2. 目标文件“edit”存在,其依赖文件中有一个或者多个文件比它“更新”,则根据规则重新链接生成“edit”。
3. 目标文件“edit”存在,它比它的任何一个依赖文件都“更新”,则什么也不做。
上例中,如果更改了源文件“insert.c”后执行make,“insert.o”将被更新,之后终极目标“edit”将会被重生成;如果我们修改了头文件“command.h”之后运行“make”,那么“kbd.o”、“command.o”和“files.o”将会被重新编译,之后同样终极目标“edit”也将被重新生成。
以上我们通过一个简单的例子,介绍了Makefile中目标和依赖的关系。对于Makefile中的目标。在执行“make”时首先执行终极目标所在的规则,接下来一层层地去寻找终极目标的依赖文件所在的规则并执行。当终极目标的规则被完全的展开以后,make将从最后一个被展开的规则处开始执行,之后处理倒数第二个规则,……依次回退。最后一步执行的就是终极目标所在的规则。整个过程就类似于C语言中的递归实现一样。在更新(或者创建)终极目标的过程中,如果出现错误make就立即报错并退出。整个过程make只是负责执行规则,而对具体规则所描述的依赖关系的正确性、规则所定义的命令的正确性不做任何判断。就是说,一个规则的依赖关系是否正确、描述重建目标的规则命令行是否正确,make不做任何错误检查。
因此,需要正确的编译一个工程。需要在提供给make程序的Makefile中来保证其依赖关系的正确性、和执行命令的正确性。
同样是上边的例子,我们来看一下终极目标“edit”所在的规则:
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
在这个规则中.o文件列表出现了两次;第一次:作为目标“edit”的依赖文件列表出现,第二次:规则命令行中作为“cc”的参数列表。这样做所带来的问题是:如果我们需要为目标“edit”增加一个的依赖文件,我们就需要在两个地方添加(依赖文件列表和规则的命令中)。添加时可能在“edit”的依赖列表中加入了、但却忘记了给命令行中添加,或者相反。这样给后期的维护和修改带来了很多不方便,而且容易出现修改遗漏。
为了避免这个问题,在实际工作中大家都比较认同的方法是,使用一个变量“objects”、“OBJECTS”、“objs”、“OBJS”、“obj”或者“OBJ”来作为所有的.o文件的列表的替代。在使用到这些文件列表的地方,使用此变量来代替。在上例的Makefile中可是添加这样一行:
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
“objects”作为一个变量,它代表所有的.o文件的列表。在定义了此变量后,我们就可以在需要使用这些.o文件列表的地方使用“$(objects)”来表示它,而不需要罗列所有的.o文件列表。因此上例的规则就可以这样写:
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
…….
…….
clean :
rm edit $(objects)
需要增加或者去掉一个.o文件时。我们只需要改变“objects”的定义(加入或者去掉若干个.o文件)。这样做不但减少维护的工作量,而且可以避免由于遗漏而产生错误的可能。
在使用make编译.c源文件时,可以省略编译一个.c文件所使用的命令。这是因为make存在一个默认的规则,能够自动完成对.c文件的编译并生成对应的.o文件。它执行命令“cc -c”来编译.c源文件。对于上边的例子,此默认规则就使用命令“cc -c main.c -o main.o”来创建文件“main.o”。因此对一个目标文件是“N.o”,倚赖文件是“N.c”的规则。可以省略其规则的命令行,使用make的默认命令。此默认规则称为make的隐含规则。
我们书写Makefile时,对于一个.c文件如果使用make的隐含规则,那么它会被自动作为对应.o文件的一个依赖文件(对应是指:文件名除后缀外,其余都相同的两个文件)。因此我们也可以在规则中省略目标的倚赖.c文件。
我们上边的例子就可以以更加简单的方式书写,使用了变量“objects”。简化版本的Makefile如下:
# sample 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)
这种格式的Makefile更接近于我们实际的应用。(关于目标“clean”的详细说明我们在后边。
make的隐含规则在实际工程的make中会经常使用,它使得编译过程变得方便。几乎在所有的Makefile中都用到了make的隐含规则,make的隐含规则是非常重要的一个概念。后续我们会在第九章会专门讨论make的隐含规则。
makefile
Makefile中,目标使用隐含规则生成,我们就可以也可以书写另外一种风格Makefile。在这个Makefile中,根据依赖而不是目标对规则进行分组。上例的Makefile就可以这样来实现:
#sample Makefile
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
$(objects) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h
例子中头文件“defs.h”作为所有.o文件的依赖文件。其它两个头文件作为其对应规则的目标中所列举的所有.o文件的依赖文件。
但是这种风格的Makefile并不值得我们借鉴。问题在于:同时把多个目标文件的依赖放在同一个规则中进行描述(一个规则中含有多个目标文件),这样导致规则定义不明了,比较混乱。建议大家不要在Makefile中采用这种方式了书写。否则后期维护将会是一件非常痛苦的事情。
书写规则建议的方式是:单目标,多依赖。就是说尽量要做到一个规则中只存在一个目标文件,可有多个依赖文件。尽量避免多目标,单依赖的方式。这样后期维护也会非常方便,而且Makefile会更清晰、明了。
在Makefile中的规则也可以完成除编译以外的任务。例如:前边提到的实现清除当前目录中在编译过程中生成的文件(edit和哪些.o文件)的规则:
clean :
rm edit $(objects)
在实际应用时,我们会把这个规则写成如下稍微复杂一些的样子。以防止出现始料未及的情况。
.PHONY : clean
clean :
-rm edit $(objects)
这两个实现有两点不同: 1. 通过“.PHONY”特殊目标将“clean”目标声明为伪目标。防止当磁盘上存在一个名为“clean”文件时,“clean”所在规则的命令无法执行。2. 在命令行之前使用“-”,意思是忽略命令“rm”的执行错误。
这样的一个目标在Makefile中,不能将其作为终极目标(Makefile的第一个目标)。因为我们的初衷并不是当你在命令行上输入make以后执行删除动作。而是要创建或者更新程序。在我们上边的例子中。就是在输入make以后要需要对目标“edit”进行创建或者重建。
上例中因为目标“clean”没有出现在终极目标“edit”依赖关系中,所以我们执行“make”时,目标“clean”所在的规则将不会被处理。如果需要执行此规则,需要在make的命令行选项中明确指定这个目标(执行“make clean”)。