makefile规则语法-依赖和搜索

一、规则语法:

通常规则的语法格式如下:
TARGETS : PREREQUISITES
COMMAND
...
或者:
TARGETS : PREREQUISITES ; COMMAND
COMMAND
...

规则中“TARGETS”可以是空格分开的多个文件名,也可以是一个标签(例如:执行清空的“clean”)。

通常规则只有一个目标文件(建议这么做),偶尔会在一个规则中需要多个目标。

书写规则需要注意的几点:

1. 规则的命令部分有两种书写方式:a. 命令可以和目标:依赖描述放在同一行。命令在依赖文件列表后并使用分号(;)和依赖文件列表分开。b. 命令在目标:依赖的描述的下一行,作为独立的命令行。当作为独立的命令行时此行必须以[Tab]字符开始。在 Makefile 中,在第一个规则之后出现的所有以[Tab]字符开始的行都会被当作命令来处理。

2.Makefile 中符号“$”有特殊的含义(表示变量或者函数的引用),在规则中需要使用符号“$”的地方,需要书写两个连续的(“$$”)。

3. 对于 Makefile 中一个较长的行,我们可以使用反斜线“\”将其书写到几个独立的物理行上。虽然 make 对 Makefile 文本行的最大长度是没有限制的,但还是建议这样做。不仅书写方便而且更有利于别人的阅读(这也是一个程序员修养的体现)。

一个规则告诉“make”两件事:1. 目标在什么情况下已经过期; 2. 如果需要重建目标时,如何去重建这个目标。目标是否过期是由那些使用空格分割的规则的依赖文件所决定的。当目标文件不存在或者目标文件的最后修改时间比依赖文件中的任何一个晚时,目标就会被创建或者重建。就是说执行规则命令行的前提条件是以下两者之一:
1. 目标文件不存在; 2. 目标文件存在,但是规则的依赖文件中存在一个依赖的最后修改时间比目标的最后修改时间晚。

规则的中心思想是:目标文件的内容是由依赖文件文件决定,依赖文件的任何一处改动,将导致目前已经存在的目标文件的内容过期。规则的命令为重建目标提供了方法。这些命令运行在系统 shell 之上。



二、依赖的类型

在 GNU make 的规则中可以使用两种不同类型的依赖:1. 以前章节所提到的规则中使用的是常规依赖,这是书写 Makefile 规则时最常用的一种。2. 另外一种在我们书写 Makefile 时不会经常使用,它比较特殊、称之为“order-only”依赖。

常规依赖中,规则中如果依赖文件的任何一个比目标文件新,则认为规则的目标已经过期而需要重建目标文件。

有时需要定义这样一个规则,在更新目标(目标文件已经存在)时只需要根据依赖文件中的部分来决定目标是否需要被重建,而不是在依赖文件的任何一个被修改后都重建目标。为了实现这一目的,相应的就需要对规则的依赖进行分类,一类是在这些依赖文件被更新后,需要更新规则的目标;另一类是更新这些依赖的,可不需要更新规则的目标。我们把第二类称为:“order-only”依赖。书写规则时,“order-only”依赖使用管道符号“|”开始,作为目标的一个依赖文件。规则依赖列表中管道符号“|”左边的是常规依赖,管道符号右边的就是“order-only”依赖。这样的规则书写格式如下:

TARGETS : NORMAL-PREREQUISITES | ORDER-ONLY-PREREQUISITES

注意:规则依赖文件列表中如果一个文件同时出现在常规列表和“order-only”列表中,那么此文件被作为常规依赖处理

“order-only”依赖的使用举例:
LIBS = libtest.a
foo : foo.c | $(LIBS)
$(CC) $(CFLAGS) $< -o $@ $(LIBS)
make在执行这个规则时,如果目标文件“foo”已经存在。当“foo.c”被修改以后,目标“foo”将会被重建,但是当“libtest.a”被修改以后。将不执行规则的命令来重建目标“foo”。就是说,规则中依赖文件$(LIBS)只有在目标文件不存在的情况下,才会参与规则的执行。当目标文件存在时此依赖不会参与规则的执行过程。



三、文件名使用通配符

Maekfile 中表示文件名时可使用通配符。可使用的通配符有:“*”、“?”和“[...]”。在 Makefile 中通配符的用法和含义和 Linux(unix)的 Bourne shell 完全相同。例如,“*.c”代表了当前工作目录下所有的以“.c”结尾的文件等。但是在 Makefile 中这些统配符并不是可以用在任何地方,Makefile 中统配符可以出现在以下两种场合:

1. 可以用在规则的目标、依赖中,make 在读取 Makefile 时会自动对其进行匹配处理(通配符展开);

2. 可出现在规则的命令中,通配符的通配处理是在 shell 在执行此命令时完成的。除这两种情况之外的其它上下文中,不能直接使用通配符。而是需要通过函数“wildcard”实现。

如果规则的一个文件名包含统配字符(“*”、“.”等字符),在使用这样的文件时需要对文件名中的统配字符使用反斜线(\)进行转义处理。例如“foo\*bar”,在 Makefile中它表示了文件“foo*bar”。Makefile 中对一些特殊字符的转移和 B-SHELL 以及 C 语言中的基本上相同。



四、目录搜索

在一个较大的工程中,一般会将源代码和二进制文件(.o 文件和可执行文件)安排在不同的目录来进行区分管理。这种情况下,我们可以使用 make 提供的目录搜索依赖文件功能(在指定的若干个目录下自动搜索依赖文件)。在 Makefile 中,使用依赖文件的目录搜索功能。当工程的目录结构发生变化后,就可以做到不更改 Makefile 的规则,只更改依赖文件的搜索目录。

一般搜索(变量VPATH)

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 中对所有文件有效。当需要为不同类型的文件指定不同的搜索目录时,需要使用另外一种方式。下一小节我们将会讨论这种更高级的方式。

选择性搜索(关键字vpath)

另一个设置文件搜索路径的方法是使用 make 的“vpath”关键字(全小写的)。它不是一个变量,而是一个 make 的关键字,它所实现的功能和上一小节提到的“VPATH”变量很类似,但是它更为灵活。它可以为不同类型的文件(由文件名区分)指定不同的搜索目录。它的使用方法有三种:

1、vpath PATTERN DIRECTORIES
为所有符合模式“PATTERN”的文件指定搜索目录“DIRECTORIES”。多个目录使用空格或者冒号(:)分开。类似上一小节的“VPATH”变量。

2、vpath PATTERN
清除之前为符合模式“PATTERN”的文件设置的搜索路径。

3、vpath
清除所有已被设置的文件搜索路径。

vapth 使用方法中的“PATTERN”需要包含模式字符“%”。“%”意思是匹配一个或者多个字符,例如,“%.h”表示所有以“.h”结尾的文件。如果在“PATTERN”中没有包含模式字符“%”,那么它就是一个明确的文件名,这样就是给定了此文件的所在目录,我们很少使用这种方式来为单独的一个文件指定搜索路径。

“PATTERN”表示了具有相同特征的一类文件,而“DIRECTORIES”则指定了搜索此类文件目录。当规则的依赖文件列表中的文件不能在当前目录下找到时,make程序将依次在“DIRECTORIES”所描述的目录下寻找此文件。例如:

vpath %.h ../headers

其 含 义 是 :Makefile 中 出 现 的.h 文 件 ; 如 果 不 能 在 当 前 目 录 下 找 到 , 则 到 目 录“../headers”下寻找。注意:这里指定的路径仅限于在 Makefile 文件内容中出现的.h文件。

库文件和搜索目录

Makefile 中程序链接的静态库、共享库同样也可以通过搜索目录得到。这一特性需要我们在书规则的依赖时指定一个类似“-lNAME”的依赖文件名。

当规则中依赖文件列表中存在一个“-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 时,出现这样的提示信息,你应该明确发生了什么错误,而不要因为错误而不知所措。




五、makefile伪目标

伪目标是这样一个目标:它不代表一个真正的文件名,在执行 make 时可以指定这个目标来执行其所在规则定义的命令,有时也可以将一个伪目标称为标签。

使用伪目标有两点原因:1. 避免在我们的 Makefile 中定义的只执行命令的目标和工作目录下的实际文件出现名字冲突。2. 提高执行 make 时
的效率,特别是对于一个大型的工程来说,编译的效率也许你同样关心。

如果我们需要书写这样一个规则:规则所定义的命令不是去创建目标文件,而是通过 make 命令行明确指定它来执一些特定的命令。像常见的 clean 目标:

clean:
rm *.o temp

规则中“rm”不是创建文件“clean”的命令,而是删除当前目录下的所有.o 文件和 temp文件。当工作目录下不存在“clean”这个文件时,我们输入“make clean”,“rm *.o temp”总会被执行。这是我们的初衷。

但是如果在当前工作目录下存在文件“clean”,情况就不一样了,同样我们输入“make clean”,由于这个规则没有任何依赖文件,所以目标被认为是最新的而不去执行规则所定义的命令,因此命令“rm”将不会被执行。这并不是我们的初衷。为了解决这个问题,我们需要将目标“clean”声明为伪目标。将一个目标声明为伪目标的方法是将它作为特殊目标.PHONY”的依赖。如下:

.PHONY : clean

这样目标“clean”就被声明为一个伪目标,无论在当前目录下是否存在“clean”这个文件。我们输入“make clean”之后。“rm”命令都会被执行。而且,当一个目标被声明为伪目标后,make 在执行此规则时不会去试图去查找隐含规则来创建它。这样也提高了 make 的执行效率,同时也不用担心由于目标和文件名重名而使我们的期望失败。在书写伪目标规则时,首先需要声明目标是一个伪目标,之后才是伪目标的规则定义。

目标“clean”的完整书写格式应该如下:
.PHONY: clean
clean:
rm *.o temp


你可能感兴趣的:(makefile)