GUN make学习笔记之make显示规则

本文详细介绍了GUN make的学习笔记,主要讲解了显示规则(包括多个目标文件的规则)、通配符的使用以及伪目标的概念。通配符简化了makefile中文件列表的处理,伪目标则用于表示执行命令而非创建文件,常用于如`all`和`clean`这样的标准任务。同时,文章还提到了空目标的用途,用于在特定命令执行后创建一个空文件作为标记。
摘要由CSDN通过智能技术生成

在上一篇GUN make 学习笔记之make初探中,我们编写了一些规则用于编译和链接我们的程序。每个规则都定义了目标(即被更新的文件)。每个目标文件都依赖一组前提条件(同样也是文件)。当要求更新目标时,make会在任何前提条件被修改且比目标文件新的情况下,执行规则下的命令脚本。因为,每个规则的目标可能是另一规则的前提条件,这样,由目标和前提条件组成的集合将会形成依赖图或者依赖关系(简称依赖图)。make所做的就是通过建立并处理依赖图来更新特定的目标。


规则对于make来说非常重要,make中有几种不同的规则。显示规则(Explicitrules),就像是前面出现的一样,表明一个特定目标在它依赖的前提过时时将要更新。这是你最常使用的规则类型。模式规则(Patternrules)使用通配符代替具体的文件名称。这允许make在目标文件匹配模式的条件下应用规则来更新。隐含规则(Implicitrules)可以是模式规则,也可以是内建于make的后缀规则。拥有内建的规则数据库使得编写makefile更为容易,因为对于多数常见的任务,make已经知道文件的类型,后缀以及更新的目标程序。静态模式规则(Staticpattern rules)类似于正则模式规则(Regularpattern rules),只不过它们只能应用于特定的目标文件列表。


GUNmake可以作为许多其他版本的make的替代品,它特别针对兼容性提供了若干特性。make的后缀规则最初的用意是为编写通用规则(generalrules)。尽管GUNmake支持后缀规则,不过为了更完整和更一般化,它已经被模式规则所替换。


显示规则

你编写的大多数规则都是显示规则,即用特定的文件作为目标和前提条件。一条规则可以有多个目标。这就意味着每个目标拥有相同的一组前提条件。如果目标过时,则make会为每个目标执行相同的一组更新动作。例如:

vpath.ovariable.o: make.h config.h getopt.h gettext.h dep.h


这表明vpath.ovariable.o依赖于相同的C头文件。这一行等效于:

vpath.o:make.h config.h getopt.h gettext.h dep.h
variable.o:make.h config.h getopt.h gettext.h dep.h


这两个目标将分开处理。如果任何一个目标文件相对于它的前提条件过时了(即,任何一个头文件修改的时间比目标文件新),make将执行相应规则的命令更新目标文件。


规则不必一次性定义完全。每当make看见目标文件时,它将目标和前提条件加入到依赖图中。如果目标已经在依赖图中存在,任何额外的前提条件都将追加到make依赖图的相应目标实体后面。在较为简单的情况下,这个特性可用来断开太长的规则并提高makefile的可读性:

vpath.o:vpath.c make.h config.h getopt.h gettext.h dep.h
vpath.o:filedef.h hash.h job.h commands.h variable.h vpath.h


在更为复杂的情况下,前提条件可以由看似无关的文件组成:

# Make sure lexer.c is created before vpath.c is compiled.
vpath.o: lexer.c
…
# Compile vpath.c with special flags.
vpath.o: vpath.c
		$(COMPILE.c) $(RULE_FLAGS) $(OUTPUT_OPTION) $<
…
# Include dependencies generated by a program.
Include auto-generated-dependencies.d

第一条规则说明当 lexer.c (可能是由于生成 lexer.c 文件会导致其他副作用)更新后必须更新 vpath.o 目标。这条规则同样保证前提条件的更新在目标更新之前发生。(注意规则的双向作用。在正向情况下,该规则说明,如果 lexer.c 文件已经更新,则执行更性 vpath.o 的动作。在反向情况下,该规则说明,如果我们需要创建或者使用 vpath.o ,确保首先更新 lexer.c 。)这个规则应该放在处理 lexer.c 的规则附近,这样开发者将会注意到这个微妙的关系。接着, vpath.o 的编译规则放在其他编译规则之间。这条规则的命令使用了三个 make 变量。你将会看到更多的 make 变量,但就现在而言,你仅仅只需要知道一个变量可以是美元符号后面跟单一字符,也可以是美元符号后面跟一个加圆括号的单词。(在以后会对变量做进一步介绍)。最后, .o .h 文件的依赖通过由外部程序管理的独立文件引入到 makefile 中。

通配符


makefile通常包含很长的文件列表。为了简化处理,make支持通配符。make的通配符与Bourneshell的相同:~*?[...]以及[^...]。例如,*.*匹配包含点号的所有文件。问号代表任何单一字符,[...]代表一个字符集,[^...]代表字符集的补集。


此外,‘~’字符可用来表示用户的当前目录。用户名后面跟着'~'字符可表示用户的主目录。


make在目标,前提条件或命令行脚本中看见通配符时,make会自动扩展通配符。在其他情况下,你可以通过调用函数来显示扩展通配符。通配符对于创建更强适应性的makefile非常有用。比如,你可以使用通配符而不是列出程序里所有的文件:

prog: *.c
		$(CC) -o $@ -$^

然而,使用通配符时必须小心。因为一不小心就会造成误用。比如:

*.o:constants.h

它的意图是很明显的:所有 .o 目标文件都依赖头文件 constants.h ,不过,若目录中并未包含任何目标文件,它会被扩展成:

:constants.h

这是合法的 make 表达式,并且不会产生任何错误。但是,它也没有提供用户想要的依赖关系。实现这个规则的正确方法是在源文件(它们总是存在)上使用通配符,并将其转化为 .o 的目标文件。


最后,值得注意的是,当模式出现在目标或前提条件中时,make将执行通配符扩展。然而,当模式出现在命令中时,subshell将执行通配符扩展。区分这两种情况非常重要。因为,make在读取makefile时会立即进行通配符的扩展,但是shell扩展命令中的统配符要稍晚一些,当命名被执行时,命令中的通配符才被扩展。当正在操作很多复杂的文件时,这两种通配符扩展将有很大的区别。


伪目标


 直到现在,所有的目标和前提条件都会进行文件的创建或更新。这是一种典型情况,但是以目标作为标签来代表命令脚本,通常也是很有用的。例如,在之前,我们注意到,在大多数makefile中,第一个目标通常是标准目标all。任何不代表文件的目标被称作伪目标(phonytarget)。另一个标准的伪目标是clean

clean:
		rm -f *.o lexer.c


通常,伪目标总是会被执行,因为与规则关联的命令没有创建目标名称。


需要注意的是,make不能区分文件目标和伪目标。如果恰好存在与伪目标同名的文件,make会将这个文件与伪目标关联到依赖图中。例如,文件clean在运行makeclean时已经创建,这回导致make产生令人疑惑的信息:

$ make clean
make:`clean' is up to date.

因为,多数的伪目标不具有前提条件, clean 目标总是被认为是最新的,所以相应的命令永远不会执行。


为了避免这个问题,GUNmake提供了一个特定的目标(.PHONY)用于告知make这个目标不是真实存在的文件。任何目标都可以通过一个前提条件(.PHONY)声明为伪目标:

.PHONY: clean
clean:
		rm -f *.o lexer.c


现在,即是存在名称为clean的文件,make也会执行与clean相关的命令。除了标记目标总是过时的,指定目标为伪目标告诉make这个文件布遵循正常的从源文件到目标文件的规则。因此,make可以优化它的正常搜索规则来提高性能。


以伪目标作为实际文件的前提似乎意义不大,因为伪目标总是过时的,并且会导致目标文件总是被重建。然而,以伪目标作为前提通常也有些用处。例如,all目标经常会被用来指定所有编译的一串程序:

.PHONY: all
all: bash bashbug


在这里,all目标创建bash程序和bashbug错误报告工具。


伪目标可以被认为是内嵌于makefileshell脚本。将伪目标作为其他目标的前提会使伪目标的脚本在执行真实目标前调用。假设我们的磁盘空间紧张,在执行磁盘密集型任务前,我们想要显示可用磁盘空间。我们可能会这么做:

.PHONY: make-documentation
make-documentation:
		df -k . | awk 'NR == 2 { printf(“%d available\n”, $$4) }'
		javadoc …

这么做的问题是,我们最后可能会在不同的目标下指定 df awk 命令,这会造成维护上的问题,因为如果我们在另一个系统上遇到不同输出格式的 df 命令,那么我们必须指定 df awk 命令的没一处进行修改。此时,我们可以把 df 作为伪目标:

.PHONY: make-documentation:
make-documentation: df
		javadoc …
.PHONY: df
df:
		df -k . | awk 'NR == 2 { printf(“%d available\n”, $$4) }'

df作为make-documentation的前提条件,可以让make在生成文档时先调用df目标。这能够很好的工作,因为make-documentation也是伪目标。现在,我们可以在其他目标中很容易的使用df了。


伪目标还有许多其他好的用处。


make的输出使阅读和调试变得糊涂。这有几个原因:makefile是自上而下的,但是make执行的命令却是自下而上的;此外,你根本无法判断正在执行那条规则。如果能在make的输出中为主目标加上注释,那么make的输出将会变得很容易阅读。伪目标是完成这项任务有效的方式。下面的例子摘自bashmakefie

$(Program): build_msg $(OBJECTS) $(BUILTINS_DEP) $(LIBDEP)
		$(RM) $@
		$(CC) $(LDFLAGS) -o $(Program) $(OBJECTS) $(LIBS)
		ls -l $(Program)
		size $(Program)

.PHONY: build_msg
build_msg:
		@printf “#\n# Building $(Program) \n#\n”


因为printf在伪目标中,所以任何前提条件更新前会立即输出信息。如果将build_msg作为$(Program)命令脚本的第一个命令,那么它只会在所有的编译和依赖关系都产生后才执行。切记,伪目标总是过时的,即使$(Program)是最新的,伪目标build_msg也会导致$(Program)重新生成。在这种情况下,它看上去是合理的选择,因为在编译目标文件是,多数计算已经执行了,因此,最后只剩下链接工作了。


伪目标同样可用于改善makefile的用户接口。通常,目标是包含目录路径元素,额外的文件名成分(比如版本号)和后缀名的复杂字符串。这使得在命令行上指定目标文件名成为挑战。这可以通过添加简单的伪目标,并以实际文件作为其前提条件来解决这个问题。


通常,makefile或多或少都会包含一组标准的伪目标。表1列出了标准的伪目标。

目标

功能

all

执行编译应用程序的所有工作

install

从已编译的二进制文件安装应用程序

clean

将产生自源代码的二进制文件删除

distclean

删除编译过程中所产生的任何文件

TAGS

建立可供编辑器使用的标记表

info

Texinfo源代码创建GUNinfo文件

check

执行与应用程序相关的任何测试


TAGS目标实际上并不是真正的伪目标,因为ctagsetags程序的输出就是名为TAGS的文件。它被包含在这里是因为它是我们所知道的、绝无仅有的、标准的伪目标。


空目标


空目标与伪目标一样,可以使用make发挥潜在的功能。伪目标总是过时的,因此总是被执行,它们也总是导致依赖它们的目标(以伪目标作为前提条件)重建。但是,假设我们有一些命令,并没有输出文件,这些命令只是偶尔会被执行,并且不想我们所依赖的目标更新,该怎么做呢?此时,我们可以创建一个规则,并且它的目标是空文件(有时称为cookie):

prog: size prog.o
		$(CC) $(LDFLAGS) -o $@ $^

	size: prog.o
		size $^
		touch size

注意到,size的规则是在命令完成是创建一个空的size文件。使用这个空文件的时间戳,make只有在prog.o更新后,才会执行size的规则。


当空文件与自动变量$?一起使用时,有特殊的用途。我们将在后面讨论自动变量,不过事先了解以下并没有什么问题。在规则的命令脚本中,make定义$?变量为比目标更新的前提条件(即前提条件文件的时间戳比目标文件的时间戳大)一组前提条件。例如,下面这个规则将会输出自从上次执行makeprint之后,变更过的所有文件:

print: *.[hc]
		lpr $?
		touch $@

通常,空文件可用来标记最近发生的特殊事件。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值