GNU make 总结 (二)

规则描述了在何种状况下使用什么命令来建立或者更新一个目标。若是在makefile中第一个规则有多个目标的话,那么多个目标中的第一个将会做为make的“终极目标”。shell

3.1 规则语法函数

TARGETS : PREREQUISITES
    COMMAND
    ...
    ...
或者
TARGETS : PREREQUISITES; COMMAND
    COMMAND
    ...
    ...

 TARGETS: 能够是空格隔开的多个目标名,也能够是标签。也可使用通配符。spa

COMMAND: 能够和依赖放在同一行,以分号;隔开。能够单独放在一行,可是必须以Tab键开头。命令行

注:设计

makefile中,在第一个规则以后出现的全部以Tab开始的行都会被看成COMMAND来处理。code

makefile中,$表示变量或者函数的引用,所以若是确实须要使用$自己,以下书写:”$$”。blog

makefile中,使用\将比较长的行断开,而且\后面不能有空格。进程

 

3.2 依赖的类型字符串

一般的依赖规则中,若是有任何一个依赖文件被更新,则必须重建目标。那么如何只根据部分依赖的状况来决定目标是否被重建,而不是在依赖文件中的任何一个被修改后都重建目标?编译器

此时须要对依赖文件进行分类:一类依赖文件改变后重建目标;另外一类依赖文件改变后不重建目标。咱们称第二类依赖为order-only依赖。“order-only”使用管道|开始,”|”左边的是常规依赖,“|”右边的是”order-only”依赖,以下所示:

TARGETS : NORMAL-PREREQUISITES | ORDER-ONLY-PREREQUISITES

若是一个文件同时出如今常规依赖和”order-only”依赖列表中,那么此文件将被视为常规依赖。

 

3.3 通配符

通配符,好比 *?、[...]等。makefile中通配符能够出如今如下两种场合:

1>  目标名、依赖名

2>  命令行语句

Linux中的~表示用户的主目录,输入”echo ~”,显示/home/benxintuzi(benxintuzi为用户名)。Makefile中,~后接一个用户名表示该用户的主目录,好比:~benxintuzi/Desktop表示目录/home/benxintuzi/Desktop

 

在如上两种状况下,通配符会被自动展开。但在变量定义和函数引用中,通配符将退化为普通字符,此时须要使用函数wildcard,以下:$(wildcard PATTERN...)表示以空格分隔的、匹配此模式的全部文件列表。若是不存在任何符合此模式的文件,则该函数会忽略模式字符并返回空。

好比:使用$(wildcard *.c)获取当前目录下的全部.c文件列表。

 

3.4 目录搜索

  • 通常搜索

经过变量VPATH能够指定依赖文件的搜索路径。当依赖文件不在当前目录中时,make会在VPATH指定的目录下搜索这些依赖文件。

事实上,VPATH变量指的是makefile中全部文件的搜索路径,既包括依赖文件,也包括目标文件。

定义VPATH时,使用空格或者冒号分割多个不一样的目录,make按照VPATH中目录定义的顺序进行搜索。

以下:

VPATH = src : ../header

 

  • 选择性搜索

与VPATH变量相比,vpath是一个make关键字,其能够为不一样类型的文件指定不一样的搜索目录,有三种使用方法:

1>  vpath PATTERN DIRECTORIES

为全部匹配PATTERN的文件指定搜索目录DIRECTORIES。多个目录之间使用空格或者冒号隔开,相似于VPATH。

2>  vpath PATTERN

清除以前为匹配PATTERN的文件设置的搜索路径。

3>  vpath

清除以前全部已经被设置的搜索路径。

vpath中使用%代替*,表示一个或多个。例如:%.h表示全部以.h结尾的文件。

 

  • 命令行和搜索目录

make在执行时,经过目录搜索获得的目标依赖文件可能存在于其余目录下,但此时经过变量获得的依赖文件名为文件的完整路径名,若是要保证在命令行正确执行规则命令,必须使用自动化变量。自动化变量的取值是根据所执行的具体规则来决定的,取决于所执行规则的目标和依赖文件名。

$@ : 表示规则的目标文件名(加上完整路径后的文件名)

$% 当规则的目标文件是一个静态库文件时,表示静态库中的一个成员;若是目标文件不是静态库,则其值为空。

$< : 表示规则的第一个依赖文件名。

$* : 文件名相关,后面介绍。

$? : 全部比目标文件更新的依赖文件列表,以空格分隔。

$^ : 规则的全部依赖文件列表,以空格分隔,重复的依赖文件只记录一次。

$+ : 相似于S^,可是其对于重复的依赖文件都会进行记录,主要用于程序连接时库的交叉引用场合。

总结:前4个表明文件名,后4个表明文件名列表。

 

在这些变量中加入D或者F就能够造成一系列的自动化变量了,以下:

$(@D) : 表示目标文件的目录部分,可是不包括斜杠。例如,"$@"的值为”dir/foo.o”,那么"$(@D)"的值为”dir”;若是"$@"中不存在斜杠,则"$(@D)"的值为“.”。

$(@F) : 表示目标文件的实际文件名。例如,"$@"的值为”dir/foo.o”,那么"$(@F)"的值为”foo.o”。"$(@F)"等价于"$(notdir$@)"。

$(*D)、$(*F) : 分别表明“茎”中的目录部分和文件名部分。

$(%D)、$(%F) : 表示静态库成员中的目录部分和文件名部分。

$(<D)、$(<F) : 表示规则中第一个依赖文件的目录部分和文件名部分。

$(^D)、$(^F) : 表示全部依赖文件的目录部分和文件名部分,没有重复文件。

$(+D)、$(+F) : 表示全部依赖文件的目录部分和文件名部分,能够有重复文件。

$(?D)、$(?F) : 表示比目标文件更新的依赖文件的目录部分和文件名部分。

 

注:GNU make同时支持"Sysv"特性,容许在规则的依赖列表中使用特殊的变量引用(通常的自动化变量只能在规则的命令行中被引用)"$$@"、"$$(@D)"、"$$(@F)",分别表示“目标的完整文件名”、“目标文件中的目录部分”、“目标文件中的文件名部分”。可是,这三个特殊的变量只能用在明确指定目标文件的规则以及静态模式规则中,不能用于隐含规则中。

 

  • 库文件和搜索目录

makefile中程序连接的静态库和共享库一样能够经过搜索目录获得,须要在依赖文件列表中以下加入库名:-INAME,其中NAME为库名的一部分。make在执行规则时首先会在当前目录中寻找”libNAME.so”;若是没找到,接着在”VPATH”和“vpath”中寻找;仍然没找到,那么make搜索系统库/lib、/usr/lib、/usr/local/lib。若是最终仍是没找到,那么make将会按照以上的搜索顺序查找libNAME.a

在规则的依赖列表中出现“-INAME”格式的依赖时,表示须要搜索的依赖文件名为“libNAME.so”或者“libNAME.a”,这是由变量“.LIBPATTERNS”指定的。 “.LIBPATTERNS”的值通常是由多个包含模式字符%的字(一个不包含空格的字符串),多个字之间使用空格分开。在规则中出现“-INAME”时,首先使用“NAME”代替变量“.LIBPATTERNS”中的第一个字的%获得第一个库文件名,根据这个库文件名在搜索目录下查找,若是找到了,就用这个库文件;不然,使用“NAME”代替第二个字的%,进行一样的查找流程。默认状况下,“.LIBPATTERNS”的内容为lib%.so lib%.a

 

3.5 伪目标

伪目标并不表明一个真正的文件名,在执行make时能够执行这个目标所定义的命令,有时也可将其称为一个标签。

使用伪目标有两个缘由:

1>  避免makefile中定义的目标名称与当前目录下的文件名发生冲突。

假设makefile文件中有以下目标:

clean :

    rm main main.o benxin.o tuzi.o

可是恰好在makefile文件目录下有一个名为clean的文件,此时咱们若是执行:make clean,那么因为clean目标没有依赖文件,所以被认为是最新的,继而rm命令不会获得执行。为了解决这个问题,咱们须要将目标clean声明为伪目标,就是将clean做为.PHONY的依赖(.PHONY : clean)。使用伪目标后,make程序在执行此规则时不会使用隐含查找,这样同时也提升了效率。所以,目标“clean”的完整格式以下所示:

.PHONY : clean

clean :

  rm main main.o benxin.o tuzi.o

注:一般在清除文件的伪目标命令中,rm使用选项-f(--force)来忽略不存在的文件。一样可使用make的内嵌变量RM,其被定义为”RM=rm -f”,所以编写clean时,可使用"$(RM)"来代替“rm”。

 

2>  提升make程序的效率。

伪目标的另外一个用途就是用于make的循环设计中。以下程序对多个目录进行make操做:

SUBDIRS = foo bar baz
subdirs :
    for dir in $(SUBDIRS); do \
        $(MAKE) –C $$dir; \
    done

 假设在某个目录中执行make时出现错误,那么make是不会退出的,而是会继续对其余目录执行make操做。所以,最后的结果中很难发现究竟是哪个目录中执行make出错了。同时因为规则中的命令是一条完整的shell语句,所以也不能利用并行处理功能。

使用伪目标从新设计以下:

SUBDIRS = foo bar baz
.PHONY : subdirs $(SUBDIRS)
subdirs : $(SUBDIRS)
$(SUBDIRS) :
    $(MAKE) –C $@
foo : baz

上边的实现中,foo : baz没有命令行,是用来控制子目录的make顺序,即在处理foo以前,必须等待baz处理完毕。

通常状况下,一个伪目标不能做为目标的依赖。这是由于当一个目标的依赖文件列表中包含伪目标时,每一次在执行这个规则时,伪目标定义的命令都会被执行。一个伪目标能够有本身的依赖,能够是文件,也能够是伪目标。若是须要建立多个可执行程序,那么可使用一个all伪目标做为“终极目标”,其依赖文件就是那些须要建立的可执行程序,以下所示:

all : prog1 prog2 prog3
.PHONY : all

prog1 : prog1.o utils.o
    cc –o prog1 prog1.o utils.o

prog2 : prog2.o
    cc –o prog2 prog2.o

prog3 : prog3.o sort.o utils.o
    cc –o prog3 prog3.o sort.o utils.o

 

3.6 强制目标

若是一个没有命令或者依赖而且其目标也不是一个已有的文件名,那么在执行该规则时,目标老是被认为是最新的。以下所示:

clean : FORCE

    rm $(OBJECTS)

FORCE :

这个效果与将clean声明为伪目标.PHONY等效,主要用于非GNU版的make中。在GNU make中,尽可能使用伪目标方式,这样更加直观高效。

 

3.7 空目标

形式上与伪目标相同,但不一样的是目标能够是一个已有的文件,而这个文件一般是一个空文件。空目标文件能够用来记录上一次执行此规则命令的时间。在这样的规则中,命令部分会使用touch在最后来更新目标文件的时间戳,用以记录此命令的最后执行时间。若是当前目录没有这个文件,那么touch会在第一次执行时建立这个空文件:

print : foo.c bar.c

    ...

    touch print

如上所示,一个空目标文件能够有一个或者多个依赖文件,当依赖文件被修改后,空目标文件所在规则中定义的命令就会被执行。

 

3.8 特殊目标

.PHONY : .PHONY的全部依赖被看做伪目标,当使用make指定伪目标时,不论目标文件是否存在,该规则下所定义的命令都会被无条件地执行。

.SUFFIXES : .SUFFIXES的全部依赖指出了一系列在后缀规则中须要检查的后缀名。

.DEFAULT : 一个文件做为某个规则的依赖,但却不做为任何一个规则的目标,make在重建这种文件时,因为没法找到重建规则,所以须要借助于.DEFAULT所指定的命令。

.PRECIOUS : 其全部的依赖文件在make中断时不会被自动删除,其依赖文件能够经过模式指定,如“%.o”。

.SECONDARY :.PRECIOUS相似,当没有任何依赖时,全部文件都不会被自动删除。

.INTERMEDIATE : 其全部依赖文件被做为中间过程文件对待,没有任何依赖的.INTERMEDIATE是没有意义的。

.DELETE_ON_ERROR : 若是执行make规则的命令中发生错误,将删除已被修改的目标文件。

.IGNORE : 若是.IGNORE存在依赖文件,则忽略重建这个目标文件时发生的错误;若是没有任何依赖文件,则忽略全部命令执行的错误。

.LOW_RESOLUTION_TIME : 其依赖文件被认为是低分辨率时间戳文件,相对于高分辨率时间戳文件而言,有时更易于判别两个文件建立时间的前后关系。

.SILENT : 重建.SILENT依赖列表中的文件时不会打印出重建命令。

.EXPORT_ALL_VARIABLES : 将其后全部的变量传递给子make进程。

.NOTPARALLEL : 没有依赖文件,全部的命令按串行方式执行。

 

3.9 多目标

多个目标依赖于一样的文件列表:

bigoutput littleoutput : text.g
    generate text.g -$(subst output,,$@) > $@

等价于:

bigoutput : text.g
    generate text.g –big > bigoutput
littleoutput : text.g
    generate text.g –little > littleoutput

 

3.10 多规则目标

makefile中,一个文件能够做为多个规则的目标文件,此时,以这个文件为目标的规则的全部依赖文件将会被合并成一个依赖文件列表,当其中任何一个依赖文件比目标更新时,make将会执行特定的命令来重建这个目标。对于一个多规则的目标,重建此目标的命令只能出如今一个规则中。若是多个规则同时给出重建此目标的命令,则make将会使用最后一个规则中定义的命令,同时给出错误提示信息。固然,若是任何一个规则都没有给出重建此目标的命令,那么make将会寻找一个合适的隐含规则来重建此目标。

好比,全部的目标文件在config.h发生变化时都须要更新:

objects = foo.o bar.o
foo.o : defs.h
bar.o : defs.h test.h
$(objects) : config.h

 

3.11 静态模式规则

规则存在多个目标,而且不一样的目标能够根据目标文件的名字来自动构造出依赖文件。静态模式比多目标模式更加灵活,其不须要多个相同的依赖文件。

TARGETS ... : TARGET-PATTERN : PREREQ-PATTERNS ...
    COMMANDS
    ...

TARGETS : 一系列目标

TARGET-PATTERN与PREREQ-PATTERNS中均可以包含模式字符%,%匹配的部分称为“茎”,以下所示:

objects = foo.o bar.o
all : $(objects)
$(objects) : %.o : %.c
  $(CC) –c $(CFLAGS) $< -o $@

对于foo.o,取出“茎”foo,代替模式字符%,则目标foo.o的依赖关系表示为foo.o : foo.c

 

在使用静态模式规则时,TARGETS和TARGET-PATTERN必须匹配,不然执行make时将发生错误。若是存在一个文件列表,其中一部分符合一种模式,而另外一部分符合另外一种模式,此时咱们可使用filter函数对这个列表进行分类,例如:

files = foo.elc bar.o lose.o
$(filter %.o,$(files)): %.o: %.c
  $(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
  emacs -f batch-byte-compile $<

 

3.12 双冒号规则

双冒号用于在多个规则中为同一个目标指定不一样的重建命令。须要注意的是,一个目标能够出如今多个规则中,可是这规则必须属于同一类型,要么都是普通规则,要么都是双冒号规则

与普通规则的区别以下:

1>  对于一个没有依赖而只有命令行的双冒号规则,当make此目标时,规则的命令总会被无条件的执行;而对于普通规则,因为没有依赖,所以目标总被认为是最新的,此规则的命令永远不会被执行。

2>  当同一个文件做为多个双冒号规则的目标时,这些不一样的规则会被独立的处理,而不是像普通规则那样合并全部的依赖到一个目标文件。这就意味着对这些规则的处理就像多个不一样的普通规则同样。就是说多个双冒号规则中的每个的依赖文件被改变以后,make 只执行此规则定义的命令,而其它的以这个文件做为目标的双冒号规则将不会被执行。

Newprog :: foo.c
  $(CC) $(CFLAGS) $< -o $@
Newprog :: bar.c
  $(CC) $(CFLAGS) $< -o $@

若是“ foo.c”文件被修改,make将根据“ foo.c”文件重建目标“ Newprog”;而若是“ bar.c”被修改,那么“ Newprog”将根据“ bar.c”被重建。若是是普通规则,将会发生错误。

 

3.13 自动产生依赖

假设main.c中包含头文件defs.h,那么使用gcc时能够经过选项“-M”来自动寻找main.c中包含的头文件defs.h,并生成文件的依赖关系,若是执行:gcc –M main.c,将输出:main.o :main.c defs.h。使用“-M”选项时,其输出结果中也包含了对标准库头文件的依赖关系描述,若是不须要标准库描述,则使用“-MM”便可。

旧版本的 make中,使用编译器此项功能一般的作法是:在 makefile 中设置一个伪目标“depend”的规则来定义自动产生依赖关系文件的命令。输入“make depend”将生成一个称为“depend”的文件,其中包含了全部源文件的依赖规则描述。makefile 中使用“include”指示符包含这个文件便可。

新版本的 make 中,推荐的方式是为每个源文件产生一个描述其依赖关系的makefile 文件。对于一个源文件“NAME.c”,对应的这个 makefile 文件为“NAME.d”。“NAME.d”中描述了文件“NAME.o”所要依赖的全部头文件。采用这种方式,只有源文件在修改以后才会从新使用命令生成新的依赖关系描述文件“NAME.o”。采用以下的模式规则来自动生成每个.c文件对应的.d文件:

%.d: %.c
  $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
  sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
  rm -f $@.$$$$

上述规则表示全部的.d文件依赖于同名的.c文件。

首先,使用编译器自动生成依赖文件"$<"的头文件的依赖关系,并输出成为一个临时文件,"$$$$"表示当前进程号。产生的依赖头文件包括了全部使用的系统头文件用户定义头文件。若是须要生成的依赖描述文件中不包含系统头文件,可以使用“-MM”代替“-M”。

而后,使用 sed 处理产生的临时文件并生成此规则的目标文件。具体以下转换:例如对一个.c源文件。将编译器产生的依赖关系:

main.o : main.c defs.h

转换为:

main.o main.d : main.c defs.h

这样就将.d 加入到了规则的目标中,其和对应的.o文件文件同样依赖于对应的.c源文件和源文件所包含的头文件。当.c源文件或者头文件被改变以后规则将会被执行,相应的.d 文件一样会被更新。

最后,删除临时文件。

 

使用上例的规则就能够创建一个描述目标文件依赖关系的.d文件。咱们能够在makefile中使用include指示符将描述将这个文件包含进来。在执行make时,makefile所包含的全部.d文件就会被自动建立或者更新。makefile中对当前目录下.d文件处理能够参考以下:

sources = foo.c bar.c
include $(sources:.c=.d)

其中,变量“sources”表示源文件列表,“(sources : .c=.d)”根据“source”指定的.c文件自动生成对应的.d文件。

相关文章
相关标签/搜索