• 定义模式规则
    • 模式规则介绍
    • 模式规则示例
    • 自动化变量
    • 模式的匹配
    • 重载内建隐含规则

    定义模式规则

    你可以使用模式规则来定义一个隐含规则。一个模式规则就好像一个一般的规则,只是在规则中,目标的定义需要有 % 字符。 % 的意思是表示一个或多个任意字符。在依赖目标中同样可以使用 % ,只是依赖目标中的 % 的取值,取决于其目标。

    有一点需要注意的是, % 的展开发生在变量和函数的展开之后,变量和函数的展开发生在make载入Makefile时,而模式规则中的 % 则发生在运行时。

    模式规则介绍

    模式规则中,至少在规则的目标定义中要包含 % ,否则,就是一般的规则。目标中的 % 定义表示对文件名的匹配, % 表示长度任意的非空字符串。例如: %.c 表示以 .c 结尾的文件名(文件名的长度至少为3),而 s.%.c 则表示以 s. 开头, .c 结尾的文件名(文件名的长度至少为5)。

    如果 % 定义在目标中,那么,目标中的 % 的值决定了依赖目标中的 % 的值,也就是说,目标中的模式的 % 决定了依赖目标中 % 的样子。例如有一个模式规则如下:

    1. %.o : %.c ; <command ......>;

    其含义是,指出了怎么从所有的 .c 文件生成相应的 .o 文件的规则。如果要生成的目标是a.o b.o ,那么 %c 就是 a.c b.c

    一旦依赖目标中的 % 模式被确定,那么,make会被要求去匹配当前目录下所有的文件名,一旦找到,make就会规则下的命令,所以,在模式规则中,目标可能会是多个的,如果有模式匹配出多个目标,make就会产生所有的模式目标,此时,make关心的是依赖的文件名和生成目标的命令这两件事。

    模式规则示例

    下面这个例子表示了,把所有的 .c 文件都编译成 .o 文件.

    1. %.o : %.c
    2. $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

    其中, $@ 表示所有的目标的挨个值, $< 表示了所有依赖目标的挨个值。这些奇怪的变量我们叫“自动化变量”,后面会详细讲述。

    下面的这个例子中有两个目标是模式的:

    1. %.tab.c %.tab.h: %.y
    2. bison -d $<

    这条规则告诉make把所有的 .y 文件都以 bison -d <n>.y 执行,然后生成 <n>.tab.c<n>.tab.h 文件。(其中, <n> 表示一个任意字符串)。如果我们的执行程序 foo依赖于文件 parse.tab.oscan.o ,并且文件 scan.o 依赖于文件 parse.tab.h ,如果 parse.y 文件被更新了,那么根据上述的规则, bison -d parse.y 就会被执行一次,于是, parse.tab.oscan.o 的依赖文件就齐了。(假设, parse.tab.oparse.tab.c 生成,和 scan.oscan.c 生成,而 fooparse.tab.oscan.o 链接生成,而且 foo 和其 .o 文件的依赖关系也写好,那么,所有的目标都会得到满足)

    自动化变量

    在上述的模式规则中,目标和依赖文件都是一系例的文件,那么我们如何书写一个命令来完成从不同的依赖文件生成相应的目标?因为在每一次的对模式规则的解析时,都会是不同的目标和依赖文件。

    自动化变量就是完成这个功能的。在前面,我们已经对自动化变量有所提涉,相信你看到这里已对它有一个感性认识了。所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。

    下面是所有的自动化变量及其说明:

    • $@ : 表示规则中的目标文件集。在模式规则中,如果有多个目标,那么, $@ 就是匹配于目标中模式定义的集合。

    • $% : 仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是 foo.a(bar.o) ,那么, $% 就是 bar.o$@ 就是 foo.a 。如果目标不是函数库文件(Unix下是 .a ,Windows下是 .lib ),那么,其值为空。

    • $< : 依赖目标中的第一个目标名字。如果依赖目标是以模式(即 % )定义的,那么 $<将是符合模式的一系列的文件集。注意,其是一个一个取出来的。

    • $? : 所有比目标新的依赖目标的集合。以空格分隔。

    • $^ : 所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。

    • $+ : 这个变量很像 $^ ,也是所有依赖目标的集合。只是它不去除重复的依赖目标。

    • $ : 这个变量表示目标模式中 % 及其之前的部分。如果目标是 dir/a.foo.b ,并且目标的模式是 a.%.b ,那么, $ 的值就是 dir/a.foo 。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么 $ 也就不能被推导出,但是,如果目标文件的后缀是make所识别的,那么 $ 就是除了后缀的那一部分。例如:如果目标是 foo.c ,因为.c 是make所能识别的后缀名,所以, $ 的值就是 foo 。这个特性是GNU make的,很有可能不兼容于其它版本的make,所以,你应该尽量避免使用 $ ,除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么 $* 就是空值。

    当你希望只对更新过的依赖文件进行操作时, $? 在显式规则中很有用,例如,假设有一个函数库文件叫 lib ,其由其它几个object文件更新。那么把object文件打包的比较有效率的Makefile规则是:

    1. lib : foo.o bar.o lose.o win.o
    2. ar r lib $?

    在上述所列出来的自动量变量中。四个变量( $@$<$%$* )在扩展时只会有一个文件,而另三个的值是一个文件列表。这七个自动化变量还可以取得文件的目录名或是在当前目录下的符合模式的文件名,只需要搭配上 DF 字样。这是GNU make中老版本的特性,在新版本中,我们使用函数 dirnotdir 就可以做到了。 D 的含义就是Directory,就是目录, F 的含义就是File,就是文件。

    下面是对于上面的七个变量分别加上 D 或是 F 的含义:

    • $(@D)
    • 表示 $@ 的目录部分(不以斜杠作为结尾),如果 $@ 值是 dir/foo.o ,那么$(@D) 就是 dir ,而如果 $@ 中没有包含斜杠的话,其值就是 . (当前目录)。

    • $(@F)

    • 表示 $@ 的文件部分,如果 $@ 值是 dir/foo.o ,那么 $(@F) 就是 foo.o$(@F) 相当于函数 $(notdir $@)

    • $(D), $(F)

    • 和上面所述的同理,也是取文件的目录部分和文件部分。对于上面的那个例子, $(D) 返回 dir ,而 $(F) 返回 foo

    • $(%D), $(%F)

    • 分别表示了函数包文件成员的目录部分和文件部分。这对于形同 archive(member) 形式的目标中的member 中包含了不同的目录很有用。

    • $(<D), $(<F)

    • 分别表示依赖文件的目录部分和文件部分。

    • $(^D), $(^F)

    • 分别表示所有依赖文件的目录部分和文件部分。(无相同的)

    • $(+D), $(+F)

    • 分别表示所有依赖文件的目录部分和文件部分。(可以有相同的)

    • $(?D), $(?F)

    • 分别表示被更新的依赖文件的目录部分和文件部分。

    最后想提醒一下的是,对于 $< ,为了避免产生不必要的麻烦,我们最好给 $ 后面的那个特定字符都加上圆括号,比如, $(<) 就要比 $< 要好一些。

    还得要注意的是,这些变量只使用在规则的命令中,而且一般都是“显式规则”和“静态模式规则”(参见前面“书写规则”一章)。其在隐含规则中并没有意义。

    模式的匹配

    一般来说,一个目标的模式有一个有前缀或是后缀的 % ,或是没有前后缀,直接就是一个 % 。因为 % 代表一个或多个字符,所以在定义好了的模式中,我们把 % 所匹配的内容叫做“茎”,例如%.c 所匹配的文件“test.c”中“test”就是“茎”。因为在目标和依赖目标中同时有 % 时,依赖目标的“茎”会传给目标,当做目标中的“茎”。

    当一个模式匹配包含有斜杠(实际也不经常包含)的文件时,那么在进行模式匹配时,目录部分会首先被移开,然后进行匹配,成功后,再把目录加回去。在进行“茎”的传递时,我们需要知道这个步骤。例如有一个模式e%t ,文件 src/eat 匹配于该模式,于是 src/a 就是其“茎”,如果这个模式定义在依赖目标中,而被依赖于这个模式的目标中又有个模式 c%r ,那么,目标就是 src/car 。(“茎”被传递)

    重载内建隐含规则

    你可以重载内建的隐含规则(或是定义一个全新的),例如你可以重新构造和内建隐含规则不同的命令,如:

    1. %.o : %.c
    2. $(CC) -c $(CPPFLAGS) $(CFLAGS) -D$(date)

    你可以取消内建的隐含规则,只要不在后面写命令就行。如:

    1. %.o : %.s

    同样,你也可以重新定义一个全新的隐含规则,其在隐含规则中的位置取决于你在哪里写下这个规则。朝前的位置就靠前。