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

3.1 规则语法

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

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

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

注:

makefile中,在第一个规则之后出现的所有以Tab开始的行都会被当作COMMAND来处理。

makefile中,$表示变量或者函数的引用,因此如果确实需要使用$本身,如下书写:”$$”。

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文件。

GNU make 总结 (二)的更多相关文章

  1. 从零开始之uboot、移植uboot2017.01(二、从入口分析流程)

    原创: To_run_away 从零开始学linux 本节的开始之前,先看一下uboot的链接脚本. 一.链接脚本 /* * Copyright (c) 2004-2008 Texas Instrum ...

  2. 查看centos版本号

    --写在开始-- 玩Linux,不同的版本会有一些细微区别: so,经常需要查看服务器版本号: --正文-- 有以下命令可以查看linux服务器版本号: # lsb_release -a LSB Ve ...

  3. 在Linux下怎么确定哪个网卡对应哪个接口?

    国内私募机构九鼎控股打造APP,来就送 20元现金领取地址:http://jdb.jiudingcapital.com/phone.html 内部邀请码:C8E245J (不写邀请码,没有现金送) 国 ...

  4. [Linux Kernel]查看CentOS版本方法

    查看CentOS版本方法  有以下命令可以查看:   # lsb_release -a LSB Version:    :core-3.1-ia32:core-3.1-noarch:graphics- ...

  5. buildroot构建项目(三)--- u-boot 2017.11 适配开发板修改 1

    当前虽然编译成功了,但是对于我们自己的目标板并不太适用.还得做一系列得修改. 一.lds 文件分析 u-boot 中最重要得链接文件即是,u-boot.lds.我们可以查看我们编译出来得 u-boot ...

  6. npm 版本问题

    STF之问题篇 https://yq.aliyun.com/articles/221602 装完成后输入stf doctor查看工具依赖是否正确,安装教程可以参考我之前写的,这里不再多说,直接说问题. ...

  7. 给广大码农分享福利:一个业界良心的github仓库,中文计算机资料

    我今天查资料时无意发现的,https://github.com/CyC2018/CS-Notes 这个仓库包含了下列几个维度的计算机学习资料: 深受国内程序员喜爱,已经有超过3万多star了. 1. ...

  8. Yum三方仓库——RPMForge

    参考:How to Enable RPMForge Repository in RHEL/CentOS 7.x/6.x/5.x RPMForge / RepoForge这两个项目已经死亡,不应该使用 ...

  9. GNU make使用(二)

    [时间:2017-06] [状态:Open] [关键词:makefile,gcc,编译,shell命令,目标文件] 0 引言及目标 之前使用Makefile都是把源文件和目标文件放到同一个目录编译.近 ...

随机推荐

  1. Oracle自增ID实现

    首先,创建一张表: CREATE TABLE example( ID Number(4) NOT NULL PRIMARY KEY, NAME VARCHAR(25)); 然后,自定义一个序列(seq ...

  2. python字符串常用操作方法

    python字符串操作常用操作,如字符串的替换.删除.截取.复制.连接.比较.查找.分割等,需要的朋友可以参考下. 1.去除空格str.strip():删除字符串两边的指定字符,括号的写入指定字符,默 ...

  3. 父窗口调用iframe子窗口方法

    一.父窗口调用iframe子窗口方法 1.HTML语法:<iframe name="myFrame" src="child.html"></i ...

  4. JS常用的设计模式(10)——模版方法模式

    模式方法是预先定义一组算法,先把算法的不变部分抽象到父类,再将另外一些可变的步骤延迟到子类去实现.听起来有点像工厂模式( 非前面说过的简单工厂模式 ). 最大的区别是,工厂模式的意图是根据子类的实现最 ...

  5. Android OptionMenu

    1.Java package com.fish.helloworld; import android.app.Activity; import android.content.Context; imp ...

  6. GUI异步编程之BackgroundWorker类

    GUI编程中,经常需要另建一个线程,在后台运行以完成某项工作,并不时地与界面主线程进行通信,以改变界面显示.BackgroundWorker类为此而生. BackgroundWorker类的主要成员: ...

  7. 获取本地ip地址

    #import <ifaddrs.h> #import <arpa/inet.h> // Get IP Address - (NSString *)getIPAddress { ...

  8. linux安装ftp服务器

    Ftp(文件传输协议) 概念 FTP是File Transfer Protocol(文件传输协议)的英文简称,而中文简称为“文传协议”.用于Internet上的控制文件的双向传输.同时,它也是一个应用 ...

  9. 内核linux-3.4.2支持dm9000

    当前烧写:      fs:    nfs 30000000 192.168.1.17:/work/nfs_root/first_fs_mdev.yaffs2    //这里不能使用nfs挂载,只能直 ...

  10. 在信号处理函数中调用longjmp

    错误情况及原因分析 前两天看APUE的时候,有个程序要自己制作一个sleep程序,结果在这个程序中就出现了在信号处理函数中调用longjmp函数的情况,结果就出现了错误,具体错误是啥呢,请参见下面这段 ...