分类: LINUX
2008-03-12 10:52:28
在一个较大的工程中,一般会将源代码和二进制文件(.o文件和可执行文件)安排在不同的目录来进行区分管理。这种情况下,我们可以使用make提供的目录搜索依赖文件功能(在指定的若干个目录下自动搜索依赖文件)。在Makefile中,使用依赖文件的目录搜索功能。当工程的目录结构发生变化后,就可以做到不更改Makefile的规则,只更改依赖文件的搜索目录。
本节我们将详细讨论在书写Makefile时如何使用这一特性。在自己的工程中灵活运用这一特性,将会起到事半功倍的效果。
GNU make可以识别一个特殊变量“VPATH”。通过变量“VPATH”可以指定依赖文件的搜索路径,当规则的依赖文件在当前目录不存在时,make会在此变量所指定的目录下去寻找这些依赖文件。通常我们都是用此变量来指定规则的依赖文件的搜索路径。其实“VPATH”变量所指定的是Makefile中所有文件的搜索路径,包括了规则的依赖文件和目标文件。
定义变量“VPATH”时,使用空格或者冒号(:)将多个需要搜索的目录分开。make搜索目录的顺序是按照变量“VPATH”定义中的目录顺序进行的(当前目录永远是第一搜索目录)。例如对变量的定义如下:
VPATH = src:../headers
这样我们就为所有规则的依赖指定了两个搜索目录,“src”和“../headers”。对于规则“foo:foo.c”如果“foo.c”存在于“src”目录下,此规则等价于“foo:src:/foo.c”。
通过“VPATH”变量指定的路径在Makefile中对所有文件有效。当需要为不同类型的文件指定不同的搜索目录时,需要使用另外一种方式。下一小节我们将会讨论这种更高级的方式。
另一个设置文件搜索路径的方法是使用make的“vpath”关键字(全小写的)。它不是一个变量,而是一个make的关键字,它所实现的功能和上一小节提到的“VPATH”变量很类似,但是它更为灵活。它可以为不同类型的文件(由文件名区分)指定不同的搜索目录。它的使用方法有三种:
1、vpath PATTERN DIRECTORIES
为所有符合模式“PATTERN”的文件指定搜索目录“DIRECTORIES”。多个目录使用空格或者冒号(:)分开。类似上一小节的“VPATH”变量。
2、vpath PATTERN
清除之前为符合模式“PATTERN”的文件设置的搜索路径。
3、vpath
清除所有已被设置的文件搜索路径。
vapth使用方法中的“PATTERN”需要包含模式字符“%”。“%”意思是匹配一个或者多个字符,例如,“%.h”表示所有以“.h”结尾的文件。如果在“PATTERN”中没有包含模式字符“%”,那么它就是一个明确的文件名,这样就是给定了此文件的所在目录,我们很少使用这种方式来为单独的一个文件指定搜索路径。在“vpath”所指定的模式中我们可以使用反斜杠来对字符“%”进行引用(转义)(和其他的特使字符的引用一样)。
“PATTERN”表示了具有相同特征的一类文件,而“DIRECTORIES”则指定了搜索此类文件目录。当规则的依赖文件列表中的文件不能在当前目录下找到时,make程序将依次在“DIRECTORIES”所描述的目录下寻找此文件。例如:
vpath %.h ../headers
其含义是:Makefile中出现的.h文件;如果不能在当前目录下找到,则到目录“../headers”下寻找。注意:这里指定的路径仅限于在Makefile文件内容中出现的.h文件。 并不能指定源文件中包含的头文件所在的路径(在.c源文件中所包含的头文件路径需要使用gcc的“-I”选项来指定,可参考gcc的info文档)。
在Makefile中如果存在连续的多个vpath语句使用了相同的“PATTERN”,make就对这些vpath语句一个一个进行处理,搜索某种模式文件的目录将是所有的通过vpath指定的符合此模式的多个目录,其搜索目录的顺序由vpath语句在Makefile出现的先后次序来决定。多个具有相同“PATTERN”的vpath语句之间相互独立。下边是两种方式下,所有的.c文件的查找目录的顺序(不包含工作目录,对工作目录的搜索永远处于最优先地位)比较:
vpath %.c foo
vpath %.c blish
vpath %.c bar
表示对所有的.c文件,make依次查找目录:“foo”、blish”、“bar”。
而:
vpath %.c foo:bar
vpath %.c blish
对于所有的.c文件make将依次查找目录:“foo”、“bar”、“blish”
规则中一个依赖文件可以通过目录搜寻找到(使用前边提到的一般搜索或者是选择性搜索任一种),可能得到的是文件的完整路径名(文件的相对路径或者绝对路径,如:/home/Stallman/foo.c),它却并不是规则中列出的文件名(规则“foo : foo.c”,在执行搜索后可能得到的依赖文件为:“../src/foo.c”。目录“../src”是使用“VPATH”或“vpath”指定的);因此使用目录搜索所到的完整的文件路径名可能需要废弃(可能废弃的是规则目标文件的全名,规则依赖文件全名不能废弃,否则无法执行规则。为了保证在规则命令行中使用正确的依赖文件,规则的命令行中必须使用自动化变量来代表依赖文件。关于这一点,在下一小节有专门讨论)。make在解析Makefile文件执行规则时对文件路径保存或废弃所依据的算法如下:
1. 首先,如果规则的目标文件在Makefile文件所在的目录(工作目录)下不存在,那么就执行目录搜寻。
2. 如果目录搜寻成功(在指定的目录下存在此规则的目标)那么搜索到的完整的路径名就被作为临时的目标文件被保存。
3. 对于规则中的所有依赖文件使用相同的方法处理。
4. 完成第三步的依赖处理后,make程序就可以决定规则的目标是否需要重建,两种情况的后续处理如下:
a) 规则的目标不需要重建:那么通过目录搜索得到的所有完整的依赖文件路径名有效,同样,规则的目标文件的完整的路径名同样有效。就是说,当规则的目标不需要被重建时,规则中的所有的文件完整的路径名有效。已经存在的目标文件所在的目录不会被改变。
b) 规则的目标需要重建:那么通过目录搜索所得到的目标文件的完整的路径名无效,规则中的目标文件将会被在工作目录下重建。就是说,当规则的目标需要重建时,规则的目标文件会在工作目录下被重建,而不是在目录搜寻时所得到的目录。这里,必须明确:此种情况只有目标文件的完整路径名失效,依赖文件的完整路径名是不会失效的。否则将无法重建目标。
该算法看起来比较法杂,但它确实使make实现了我们所需要的东西。此算法使用纯粹的语言描述可能显得晦涩。本小节后续将使用一个例子来说明。使大家能够对此算法有明确的理解。对于其他版本的make则使用了一种比较简单的算法:如果规则的目标文件的完整路径名存在(通过目录搜索可以定位到目标文件),无论该目标是否需要重建,都使用搜索到的目标文件完整路径名。
实际上,GNU make也可以实现这种功能。如果需要make在执行时,将目标文件在已存在的目录存下进行重建,我们可以使用“GPATH”变量来指定这些目标所在的目录。“GPATH”变量和“VPATH”变量具有相同的语法格式。make在执行时,如果通过目录搜寻得到一个过时的完整的目标文件路径名,而目标存在的目录又出现在“GPATH”变量的定义列表中,则该目标的完整路径将不废弃,目标将在该路径下被重建。
为了更清楚地描述此算法,我们使用一个例子来说明。存在一个目录“prom”,“prom”的子目录“src”下存在“sum.c”和“memcp.c”两个源文件。在“prom”目录下的Makefile部分内容如下:
LIBS = libtest.a
VPATH = src
libtest.a : sum.o memcp.o
$(AR) $(ARFLAGS) $@ $^
首先,如果在两个目录(“prom”和“src”)都不存在目标“libtest.a”,执行make时将会在当前目录下创建目标文件“libtest.a”。另外;如果“src”目录下已经存在“libtest.a”,以下两种不同的执行结果:
1) 当它的两个依赖文件“sum.c”和“memcp.c”没有被更新的情况下我们执行make,首先make程序会搜索到目录“src”下的已经存在的目标“libtest.a”。由于目标“libtest.a”的依赖文件没有发生变化,所以不会重建目标。并且目标所在的目录不会发生变化。
2) 当我们修改了文件“sum.c”或者“memcp.c”以后执行make。“libtest.a”和“sum.o”或者“memcp.o”文件将会被在当前目录下创建(目标完整路径名被废弃),而不是在“src”目录下更新这些已经存在的文件。此时在两个目录下(“prom”和“src”)同时存在文件“libtest.a”。但只有“prom/libtest.a”是最新的库文件。
当在上边的Makefile文件中使用“GPATH”指定目录时,情况就不一样了。首先看看怎么使用“GPATH”,改变后的Makefile内容如下:
LIBS = libtest.a
GPATH = src
VPATH = src
LDFLAGS += -L ./. –ltest
…….
……
同样;当两个目录都不存在目标文件“libtest.a”时,目标将会在当前目录(“prom”目录)下创建。如果“src”目录下已经存在目标文件“libtest.a”。当其依赖文件任何一个被改变以后执行make,目标“libtest.a”将会被在“src”目录下被更新(目标完整路径名不会被废弃)。
make在执行时,通过目录搜索得到的目标的依赖文件可能会在其它目录(此时依赖文件为文件的完整路径名),但是已经存在的规则命令却不能发生变化。因此,书写命令时我们必须保证当依赖文件在其它目录下被发现时规则的命令能够正确执行。
解决这个问题的方式是在规则的命令行中使用“自动化变量”,诸如“$^”等。规则命令行中的自动化变量“$^”代表所有通过目录搜索得到的依赖文件的完整路径名(目录 + 一般文件名)列表。“$@”代表规则的目标。所以对于一个规则我们可以进行如下的描述:
foo.o : foo.c
cc -c $(CFLAGS) $^ -o $@
变量“CFLAGS”是编译.c文件时gcc的编译选项,可以在Makefile中给它指定明确的值、也可以使用隐含的定义值。
规则的依赖文件列表中可以包含头文件,而在命令行中不需要使用这些头文件(这些头文件的作用只有在make程序决定目标是否需要重建时才有意义)。我们可以使用另外一个变量来书代替“$^”,如下:
VPATH = src:../headers
foo.o : foo.c defs.h hack.h
cc -c $(CFLAGS) $< -o $@
自动化变量“$<”代表规则中通过目录搜索得到的依赖文件列表的第一个依赖文件。关于自动化变量我们在后续有专门的讨论。
通过变量“VPATH”、或者关键字“vpath”指定的搜索目录,对于隐含规则同样有效。例如:一个目标文件“foo.o”在Makefile中没有重建它的明确规则,make会使用隐含规则来由已经存在的“foo.c”来重建它。当“foo.c”在当前目录下不存在时,make将会进行目录搜索。如果能够在一个可以搜索的目录中找到此文件,同样make会使用隐含规则根据搜索到的文件完整的路径名去重建目标,编译这个.c源文件。
隐含规则中的命令行中就是使用自动化变量来解决目录搜索可能带来的问题;相应的命令中的文件名都是使用目录搜索得到的完整的路径名。(可参考上一小节)
Makefile中程序链接的静态库、共享库同样也可以通过搜索目录得到。这一特性需要我们在书规则的依赖时指定一个类似“-lNAME”的依赖文件名(一个奇怪的依赖文件!一般依赖文件名应该是一个普通文件的名字。库文件的命名也应该是“libNAME.a”而不是所写的“-lNAME”。这是为什么,熟悉GNU ld的话我想这就不难理解了,“-lNAME”的表示方式和ld的对库的引用方式完全一样,只是我们在书写Makefile的规则时使用了这种书写方式。所以你不应该感到奇怪)。下边我们来看看这种奇怪的依赖文件到底是什么。
当规则中依赖文件列表中存在一个“-lNAME”形式的文件时。make将根据“NAME”首先搜索当前系统可提供的共享库,如果当前系统不能提供这个共享库,则搜索它的静态库(当然你可以在命令行中使用连接选项来指定程序采用动态连接还是静态连接,这里我们不讨论)。来看一下详细的过程:1. make在执行规则时会在当前目录下搜索一个名字为“libNAME.so”的文件;2. 如果当前工作目录下不存在这样一个文件,则make会继续搜索使用“VPATH”或者“vpath”指定的搜索目录。3. 还是不存在,make将搜索系统库文件存在的默认目录,顺序是:“/lib”、“/usr/lib”和“PREFIX/lib”(在Linux系统中为“/usr/local/lib”,其他的系统可能不同)。
如果“libNAME.so”通过以上的途径最后还是没有找到的话,那么make将会按照以上的搜索顺序查找名字为“libNAME.a”的文件。
假设你的系统中存在“/usr/lib/libcurses.a”(不存在“/usr/lib/libcurses.so”)这个库文件。看一个例子:
foo : foo.c -lcurses
cc $^ -o $@
上例中,如果文件“foo.c”被修改或者“/usr/lib/libcurses.a”被更新,执行规则时将使用命令“cc foo.c /usr/lib/libcurses.a -o foo”来完成目标文件的重建。需要注意的是:如果“/usr/lib/libcurses.a”需要在执行make的时候生成,那么就不能这样写,因为“-lNAME”只是告诉了链接器在生成目标时需要链接那个库文件。上例中的“-lcurses”并没有告诉make程序其依赖的库文件应该如何重建。当所有的搜索目录中不存在库“libcurses”时。Make将提示“没有规则可以创建目标“foo”需要的目标“-lcurses”。如果在执行make时,出现这样的提示信息,你应该明确发生了什么错误,而不要因为错误而不知所措。
在规则的依赖列表中如果出现“-lNAME”格式的依赖时,表示需要搜索的依赖文件名为“libNAME.so”和“libNAME.a”,这是由变量“.LIBPATTERNS”指定的。“.LIBPATTERNS”的值一般是多个包含模式字符(%)的字(一个不包含空格的字符串),多个字之间使用空格分开。在规则中出现“-lNAME”格式的依赖时,首先使用这里的“NAME”代替变量“.LIBPATTERNS”的第一个字的模式字符(%)而得到第一个库文件名,根据这个库文件名在搜索目录下查找,如果能够找到、就是用这个文件,否则使用“NAME”代替第二个字的模式字符,进行同样的查找。默认情况时,“.LIBPATTERNS”的值为:“lib%.so lib%.a”。这也是默认情况下在规则存在“-lNAME”格式的依赖时,链接生成目标时使用“libNAME.so”和“libNAME.a”的原因。
变量“.LIBPATTERNS”就是告诉链接器在执行链接过程中对于出现“-LNAME”的文件如何展开。当然我们也可以将此变量置空,取消链接器对“-lNAME”格式的展开。