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 $@

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



摘自http://www.linuxsir.org/main/doc/gnumake/GNUmake_v3.80-zh_CN_html 由徐海兵老師翻译整理。 本文比较完整的讲述GNU make工具,涵盖GNU make的用法、语法。同时重点讨论如何为一个工程编写Makefile。作为一个Linux程序员,make工具的使用以及编写Makefile是必需的 目 录 第一章:概述 1.1 概述 1.2 准备知识 第二章:GNU make 介绍 2.1 Makefile简介 2.2 Makefile规则介绍 2.3 简单的示例 2.4 make如何工作 2.5 指定变量 2.6 自动推导规则 2.7 另类风格的makefile 2.8 清除工作目录过程文件 第三章:Makefile 总述 3.1 Makefile的内容 3.2 makefile文件的命名 3.3 包含其它makefile文件 3.4 变量 MAKEFILES 3.5 变量 MAKEFILE_LIST 3.6 其他特殊变量 3.7 makefile文件的重建 3.8 重载另外一个makefile 3.9 make如何解析makefile文件 3.9.1 变量取值 3.9.2 条件语句 3.9.3 规则的定义 3.10 总结 第四章:Makefile规则 4.1 一个例子 4.2 规则语法 4.3 依赖的类型 4.4 文件名使用通配符 4.4.1 统配符使用举例 4.4.2 通配符存在的缺陷 4.4.3 函数wildcard 4.5 目录搜寻 4.5.1 一般搜索(变量VPATH) 4.5.2 选择性搜索(关键字vpath) 4.5.3 目录搜索的机制 4.5.4 命令行和搜索目录 4.5.5 隐含规则和搜索目录 4.5.6 库文件和搜索目录 4.6 Makefile伪目标 4.7 强制目标(没有命令或依赖的规则) 4.8 空目标文件 4.9 Makefile的特殊目标 4.10 多目标 4.11 多规则目标 4.12 静态模式 4.12.1 静态模式规则的语法 4.12.2 静态模式和隐含规则 4.13 双冒号规则 4.14 自动产生依赖 第五章:规则的命令 5.1 命令回显 5.2 命令的执行 5.3 并发执行命令 5.4 命令执行的错误 5.5 中断make的执行 5.6 make的递归执行 5.6.1 变量MAKE 5.6.2 变量和递归 5.6.3 命令行选项和递归 5.6.4 -w选项 5.7 定义命令包 5.8 空命令 第六章:Makefile中的变量 6.1 变量的引用 6.2 两种变量定义(赋值 ) 6.2.1 递归展开式变量 6.2.2 直接展开式变量 6.2.3 如何定义一个空格 6.2.4 “?=”操作符 6.3 变量的高级用法 6.3.1 变量的替换引用 6.3.2 变量的套嵌引用 6.4 变量取值 6.5 如何设置变量 6.6 追加变量值 6.7 override 指示符 6.8 多行定义 6.9 系统环境变量 6.10 目标指定变量 6.11 模式指定变量 第七章:Makefile的条件执行 7.1 一个例子 7.2 条件判断的基本语法 7.2.1 关键字“ifeq” 7.2.2 关键字“ifneq” 7.2.3 关键字“ifdef” 7.2.4 关键字“ifndef” 7.3 标记测试的条件语句 第八章:make的内嵌函数 8.1 函数的调用语法 8.2 文本处理函数 8.2.1 $(subst FROM,TO,TEXT) 8.2.2 $(patsubst PATTERN,REPLACEMENT,TEXT) 8.2.3 $(strip STRINT) 8.2.4 $(findstring FIND,IN) 8.2.5 $(filter PATTERN…,TEXT) 8.2.6 $(filter-out PATTERN...,TEXT) 8.2.7 $(sort LIST) 8.2.8 $(word N,TEXT) 8.2.9 $(wordlist S,E,TEXT) 8.2.10 $(words TEXT) 8.2.11 $(firstword NAMES…) 8.3 文件名处理函数 8.3.1 $(dir NAMES…) 8.3.2 $(notdir NAMES…) 8.3.3 $(suffix NAMES…) 8.3.4 $(basename NAMES…) 8.3.5 $(addsuffix SUFFIX,NAMES…) 8.3.6 $(addprefix PREFIX,NAMES…) 8.3.7 $(join LIST1,LIST2) 8.3.8 $(wildcard PATTERN) 8.4 foreach 函数 8.5 if 函数 8.6 call函数 8.7 value函数 8.8 eval函数 8.9 origin函数 8.10 shell函数 8.11 make的控制函数 8.11.1 $(error TEXT…) 8.11.2 $(warning TEXT…) 第九章:执行make 9.1 指定makefile文件 9.2 指定终极目标 9.3 替代命令的执行 9.4 防止特定文件重建 9.5 替换变量定义 9.6 使用make进行编译测试 9.7 make的命令行选项 第十章:make的隐含规则 10.1 隐含规则的使用 10.2 make的隐含规则一览 10.3 隐含变量 10.3.1 代表命令的变量 10.3.2 命令参数的变量 10.4 make隐含规则链 10.5 模式规则 10.5.1 模式规则介绍 10.5.2 模式规则示例 10.5.3 自动化变量 10.5.4 模式的匹配 10.5.5 万用规则 10.5.6 重建内嵌隐含规则 10.6 缺省规则 10.7 后缀规则 10.8 隐含规则搜索算法 第十一章:使用make更新静态库文件 11.1 库成员作为目标 11.2 静态库的更新 11.2.1 更新静态库的符号索引表 11.3 make静态库的注意事项 11.4 静态库的后缀规则 第十二章 : GNU make的特点 12.1 源自System v的特点 12.2 源自其他版本的特点 12.3 GNU make自身的特点 第十三章 和其它版本的兼容 第十四章 Makefile的约定 14.1 基本的约定 14.2 规则命令行的约定 14.3 代表命令变量 14.4 安装目录变量 14.5 Makefile的标准目标名 14.6 安装命令分类 第十五章 make的常见错误信息   附录:关键字索引 1. GNU make可识别的指示 符 2. GNU make函数 3. GNU make的自动化变量 4. GNU make环境变量 后序
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值