2.3 make工具及makefile文件
无论是在Linux还是在UNIX环境中,make都是一个非常重要的编译工具。无论是自己进行项目开发还是安装应用软件,都需要使用make工具。利用make工具,可以将大型的开发项目分解成为多个更易于管理的模块,对于一个包括几百个源文件的应用程序而言,使用make工具和makefile文件就可以清晰地理顺各个源文件之间的关系。而且如此多的源文件,如果每次都要输入gcc命令进行编译的话,对程序员来说是很难忍受的。make工具可以自动完成编译工作,并且只对程序员在上次编译后修改过的部分进行编译。因此,有效地利用make工具可以大大提高项目开发的效率。
2.3.1 make工具简介
1.make工作原理
make工具最基本的功能是调用makefile文件,通过makefile文件来描述源程序之间的相互依赖关系并自动维护编译工作。当然,makefile 文件需要按照某种语法进行编写,需要说明如何编译各个源文件并连接生成可执行文件,以及定义源文件之间的依赖关系。makefile 文件是许多编译器(包括Windows下的编译器)维护编译信息的常用方法,在集成开发环境中,用户可以通过友好的界面修改 makefile 文件。
例如,在当前目标下有一个文件名为"makefile"的文件,其内容如下(此文件的语法结构将在接下来的内容中介绍):
#It is a example for describing makefile |
这个描述文档就是一个简单的makefile文件。在这个例子中,第一个字符为#的行为注释行。第一个非注释行指定edit由目标文件main.o、kbd.o、command.o、display.o、insert.o、search.o、files.o、utils.o连接生成,这只是说明一个依赖关系。第三行描述了如何从edit所依赖的文件建立可执行文件,即执行命令cc -o edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o,即调用gcc编译以上.o文件生成edit可执行文件。后面偶数行分别指定各目标文件所依赖的.c和.h文件。而紧跟依赖关系奇数行则指定了如何从目标所依赖的文件建立目标。
在默认的方式下,在当前目录提示符下输入"make"命令,系统将自动完成以下操作。
(1)make工具会在当前目录下寻找名字为"Makefile"或"makefile"的文件,GNU Make 工具在当前工作目录中按照GNUmakefile、makefile、Makefile的顺序搜索 makefile执行文件。
(2)如果找到,它会查找文件中的第一个目标文件,在上面的例子中,系统将查找到"edit"这个目标,并把这个文件作为最终的目标文件。
(3)如果edit文件不存在,或是edit所依赖的后面的 .o 文件的修改时间要比edit文件晚,那么,系统就会执行后面所定义的命令来生成edit这个文件。
(4)如果edit所依赖的.o文件也不存在,那么make工具会在当前文件中查找目标为.o文件的依赖性,如果找到,则根据这一个规则生成.o文件。
(5)如果此文件中列出的*.C文件和*.H文件都存在,于是make工具会首先生成 .o 文件,然后再用 .o文件生成可执行文件edit。
这就是make文件的依赖性,make会一层一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在查找的过程中,如果出现错误,比如最后被依赖的文件没有找到,make就会直接退出并报错。而对于所定义的命令的错误,make不会检查。
通常,makefile文件中还定义有clean目标,这是一个伪目标(后续小节将详细介绍),可用来清除编译过程中生成的中间文件,例如清除上例中的内容:
clean : |
更为简单的方法为:
clean: |
在上述makefile文件中,clean没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行。不过,可以在命令中要求执行,即使用命令"make clean",以清除所有的目标文件,以便重新编译。
2.make命令
make命令可带4个可选参数,分别为标志、宏定义、描述文件名和目标文件名。其标准形式为:
make [flags] [macro] [ definitions] [targets] |
(1)flags选项及其含义如下。
-f file:指定file文件为描述文件,如果file参数为"-"符,则描述文件指向标准输入。如果没有"-f"参数,系统将默认当前目录下名为makefile或者名为Makefile的文件为描述文件。如果要用其他文件作为makefile文件,则可用以下make命令选项来指定。
$ make -f filename |
-i:忽略命令执行返回的出错信息。
-s:沉默模式,在执行之前不输出相应的命令行信息。
-r:禁止使用build-in规则。
-n:非执行模式,输出所有执行命令,但并不执行。
-t:更新目标文件。
-q:make操作将根据目标文件是否已经更新返回"0"或非"0"的状态信息。
-p:输出所有宏定义和目标文件描述。
-d:Debug模式,输出有关文件和检测时间的详细信息。
-c di:在读取 makefile 之前改变到指定的目录dir。
-I dir:包含其他 makefile文件时,利用该选项指定搜索目录。
-h:help文挡,显示所有的make选项。
-w:在处理 makefile之前和之后,都显示工作目录。
(2)宏定义有两种方式。一种是在makefile文件中定义宏,另一种是使用make命令时直接在命令行下输入宏定义(此时的宏如果与makefile文件中的宏同名,它将替代makefile文件中的宏)。
在makefile文件中引用宏时只需在变量前加$符号,如果变量名的长度超过一个字符,在引用时就必须加圆括号"()"。下面都是有效的宏引用:
$(CFLAGS) |
其中后两个引用是一样的。以下是宏定义变量的实例。
# Define a macro for the object files |
如果执行不带参数的make命令,将调用GCC连接3个目标文件和库文件LS生成可执行文件prog;如果在make命令后带有新的宏定义:
make "LIBES= -LL -LS" |
则命令行后面的宏定义将覆盖makefile文件中的宏定义。此时若LL也是库文件,make命令将连接3个目标文件以及LS和LL两个库文件。
(3)target参数用来指定make命令要编译的目标文件,并且允许同时定义编译多个。操作时将按照从左到右的顺序依次编译各目标文件。如果命令行中没有指定目标文件,则系统默认将target指向描述文件中的第一个目标文件。
一般makefile文件有几个预设的目标可供使用,对应有以下几个命令。
make all:编译所有的目标。或只输入make,此时会编译源码,然后连接,并且产生可执行文件。
make clean:清除之前所编译的可执行文件及目的文件 (object file, *.o)。
make distclean:除了清除可执行文件和目的文件外,同时把Makefile文件也清除掉。
make install:将程序安装至系统中。如果源代码编译无误,且执行结果正确,便可以把程序安装至系统预设的执行文件存放路径。如果用bin_PROGRAMS 巨集,程序会被安装致电目录 /usr/local/bin。
make dist:将程式和相关的文件包装成一个压缩文件以供发布。执行完后在当前目录下会产生一个以 PACKAGEVERSION.tar.gz 为名称的文件。
2.3.2 makefile常用规则
makefile文件中主要包含了5项内容:显式规则、隐晦规则、宏定义、文件指示和注释。
(1)显式规则。显式规则说明如何生成一个或多个目标文件。这由makefile的书写者明确指出,包括要生成的文件,生成目标文件需要的依赖文件,生成目标文件的命令。例如:
foo.o : foo.c defs.h # foo模块 |
foo.o是目标,foo.c和defs.h是目标所依赖的源文件,命令为"cc -c -g foo.c"(此行一定要以Tab键开头)。这个规则包括以下两个主要内容:
文件的依赖关系:foo.o依赖于foo.c和defs.h文件,如果foo.c和defs.h的文件日期要比foo.o新,或是foo.o不存在,那么依赖关系发生。
cc命令说明了如何生成foo.o文件。
(2)隐晦规则。由于make有自动推导的功能,它会根据依赖关系决定源文件是否要重新编译以及源文件之间的编译顺序,所以隐晦规则可以让程序员简单方便地书写makefile文件。
(3)宏(变量)的定义。在makefile中需要定义一系列的宏(变量),宏(变量)一般都是字符串,类似于C语言中的宏。当makefile被执行时,其中的变量都会被扩展到相应的引用位置上,这一内容在前一小节已经介绍。
(4)文件指示。包括3个部分:一个makefile文件中引用另一个Makefile文件,就像C语言中的include一样;指根据某些情况指定makefile中的有效部分,就像C语言中的预编译#if一样;定义多行的命令。
(5)注释。makefile中只有行注释,和UNIX的Shell脚本一样,其注释用"#"字符开始,类似于C/C++中的"//"。如果要在makefile中使用"#"字符,需用反斜杠进行转义,如:"\#"。
在书写各部分内容时,可以使用以下方式。
1.使用通配符
make支持3种通配符:"*"、"?"和"[ ]"。这和B-Shell是相同的。波浪号("~")字符在文件名中也有特殊的用途,例如"~/test",这就表示当前用户的$HOME目录下的test目录。
通配符用于代替一系列内容,如"*.c"表示所有后缀为.c的文件。需要注意的是,如果文件名中有通配符,如"*",则需使用转义字符"\",如"\*"来表示真实的"*"字符。
通配符可用在显示规则中,如:
print: *.c |
目标print依赖于所有的.c文件。
通配符同样可用在变量中,如:
objects = *.o |
objects的值为"*.o"。
2.使用文件搜寻
在大的工程中,会有大量的源文件,通常将这些源文件分类存放在不同的目录中。所以,当make需要去寻找文件的依赖关系时,可以在文件前加上路径,但最好的方法是把路径告诉make,让make去自动查找。makefile文件中的特殊变量"VPATH"就是用于完成这个功能的,如果没有指明这个变量,make只会在当前的目录中去查找依赖文件和目标文件。如果定义了这个变量,那么,make在当前目录找不到相关文件的情况下,会自动到所指定的目录中查找。
VPATH = src:../headers |
这句代码指定了两个目录"src"和"../headers",make会按照这个顺序进行搜索。目录间用"冒号"分隔。
另一个设置文件搜索路径的方法是使用make命令的"vpath"关键字(注意,是小写的),这和VPATH变量很类似,但是它更灵活。它可以指定在不同的目录中搜索不同的文件。这是一个很灵活的功能。它的使用方法有3种:
vpath<pattern><directories>:为符合模式pattern的文件指定搜索目录directories。
vpath <pattern>:清除符合模式<pattern>的文件搜索目录。
vpath:清除所有已设置好了的文件搜索目录。
<pattern>指定了要搜索的文件集,<pattern>需要包含"%"字符。"%"表示的意思是匹配一个或若干个字符,例如:
"%.h |
表示所有以".h"结尾的文件。
<directories>则指定了<pattern>文件集的搜索目录。例如:
vpath %.h ../headers |
表示要求make命令在"../headers"目录下搜索所有以".h"结尾的文件(如果文件在当前目录没有找到)。
程序员可以连续地使用vpath语句,以指定不同的搜索策略。如果连续的vpath语句中出现了相同的<pattern>,或是重复了的<pattern>,那么,make会按照vpath语句的先后顺序来执行搜索。如:
vpath %.c foo |
表示以".c"结尾的文件,先被在"foo"文件夹下搜索,然后被在"blish"文件夹下搜索,最后被在"bar"文件夹下搜索。
3.伪目标说明
前面内容中提到过clean伪目标,语法如下:
clean: |
clean并不是一个文件,而只是一个伪目标,由于"伪目标"不是文件,所以make无法生成它的依赖关系和决定它是否要执行。因此,只有通过显示地指明这个"目标"才能让其生效这要求伪目标不能和文件重名。为了避免因和文件重名而使其无效,可以使用一个特殊的标记".PHONY"来显示地指明一个目标是"伪目标"。即向make命令说明,不管是否有这个文件,这个目标就是"伪目标"。语法如下:
.PHONY: clean |
如果执行make clean命令,将会执行rm *.o temp命令。
一般情况下,伪目标没有依赖的文件,但是,也可以为伪目标指定所依赖的文件。伪目标同样可以作为"默认目标",只要将其放在第一个即可。例如,Makefile文件需要生成若干个可执行文件,但用户又只想简单地敲一个make命令,并且,所有的目标文件都写在一个makefile文件中,那么此时可以使用"伪目标"这个特性。
all: prog1 prog2 prog3 |
".PHONY : all"声明了"all"这个目标为"伪目标"。makefile中的第一个目标会被作为其默认目标。这里声明了"all"的伪目标,它依赖于其他3个目标。由于伪目标总是会被执行,所以其依赖的那3个目标就总比"all"新。所以,其他3个目标的规则总是会被执行。
参考资料:
【1】linux高级程序设计[书籍]