[make] 第三章 Makefile规则
第三章 Makefile 规则
1. 规则的定义
# 第一种写法
规则的目标 : 规则目标的依赖
[Tab]规则的命令
# 规则的目标可以是空格分开的多个文件名,也可以是一个如”clean“的标签
# 规则的目标的文件名可以使用通配符
# 规则中需要使用”$“时应使用”$$“来表示,因为”$“在规则中由其他含义
# 第二种写法
规则的目标 : 规则目标的依赖 ; 规则的命令 ...
在makefile文件中,除了”终极目标“所在的规则以外,其他规则的顺序在文件中没有意义。”终极目标“就是当没有使用make命令行指定具体目标时,make默认的更新的那一个目标。它时makefile文件中第一个规则的目标,如果第一个规则有多个目标的话,则多个目标中的第一个将会被作为make的”终极目标“。两种情况例外:
- 目标名以”.“开始的并且气候不存在”/“
- 模式规则的目标
2. 规则依赖的类型
- 常规依赖
- 即当依赖文件比目标文件新时,会重建目标,这一类依赖称为常规依赖
- ”order-only“依赖
- 即当依赖文件比目标文件新时,可以不用更新规则的目标
- 书写时,”order-only“依赖使用管道符号”|“开始,作为目标的一个依赖文件
规则列表中,管道符左边的为常规依赖,右边的为”order-only“依赖,如:
规则的目标 : 常规依赖 | "order-only"依赖
# 其中常规依赖可以为空
# 如果一个文件同时在两种依赖中出现,则该文件作为常规依赖处理
3. 文件名使用通配符
Makefile中表示文件名时可以使用通配符,如“*”、“?”和“[...]”。Makefile中通配符可以出现在以下两种场合:
-
可以在规则的目标、依赖中,make在读取Makefile时会自动对其进行匹配处理(通配符展开)
-
可以出现在规则的命令中,通配符的通配处理是在shell在执行此命令时完成的
-
除上述两种情况外,不能直接使用通配符,而是需要通过函数“wildcard”来实现
如果规则的一个文件名包含通配符字符(“*”、“.”等字符),在使用时需要对文件名中的通配字符进行转义(使用“\”进行转义),此外在Makefile的变量定义中不能直接使用通配符,需要使用wildcard函数实现通配的目的
3.1 函数wildcard
# wildcard 的使用
objects = $(wildcard *.o)
# 上述在将变量objects进行展开时,表示为当前目录下的所有以“.o”为后缀的文件
$(wildcard PATTERN...)
# 上述被展开为已经存在的、使用空格分开的、匹配PATTERN模式的所有文件列表
4. 目录搜寻
在一个较大的工程中,一般会将源代码和二进制文件安排在不同的目录来进行区分管理,这种情况下,我们可以使用make提供的目录搜素依赖文件功能(在指定的若干个目录下自动搜索依赖文件)。该种功能的好处在于,当工程的目录结构发生变化后,可以在不更改Makefile的规则下,只通过更改依赖文件的搜索目录,从而实现目标的构建。
4.1 一般搜索(VPATH)
GNU make可以识别一个特殊变量“VPATH”,通过变量“VPATH”可以指定依赖文件的搜索路径。当规则的依赖文件在当前的目录不存在时,make会在此变量所指定的目录下去寻找这些依赖文件。通常我们使用此变量来指定规则的依赖文件的搜索路径,但实际上,“VPATH”变量所指定的是Makefile中所有文件的搜索路径,包括了规则的依赖文件和目标文件。
VPATH = dir1:dir2:dir3
# 定义该变量时,使用空格或者冒号将多个需要所有的目录分开
# make在搜索时,首先搜索当前目录,之后再按照该变量的目录顺序进行搜索
4.2 选择性搜索(关键字vpath)
make的关键字“vpath”可以为不同类型的文件(有文件名区分)指定不同的搜索目录,使用方法有三种:
vpath PATTERN DIRECTORIES
# 为所有符合模式“PATTERN”的文件指定搜索目录“DIRECTORIES”,多个目录使用空格或冒号分开
# “PATTERN”需要包含模式字符“%”。“%”表示匹配一个或者多个字符,例如“%.h”表示所有以“.h”结尾的文件
vpath PATTERN
# 清除之前为符合模式“PATTERN”的文件设置的搜索路径
vpath
# 清除已被设置的文件搜索路径
4.3 目录搜索的机制
当存在上述的搜索目录时的重建机制,并在搜索目录中发现有目标文件时
- 如果目标文件需要被重建,则会在Makefile所在的目录进行目标文件的重建
- 当目标文件不需要被重建时,则什么也不做
如果使用GPATH来替代VPATH,则上述的重建机制变为
- 如果目标文件需要被重建,则make会在找到的目标文件所在的路径对目标文件进行更新
4.4 命令行和搜索目录
使用前提:make在执行时,通过目录搜索得到的目标的依赖文件可能会在其他目录(此时依赖文件为文件的完整路径名),但是已经存在的规则命令却不能发生变化。因此,书写命令时我们必须保证当依赖文件在其他目录下被发现时,规则的命令能够正确执行。
可以通过在规则的命令行使用“自动化变量”,诸如“$^”等。
- “$^”代表所有通过目录搜索得到的依赖文件的完整路径名列表
- 规则的依赖文件列表中可以包含头文件,当使用该自动化变量时,命令行中也会出现这些头文件,由于这些头文件的作用只有在make程序决定目标是否需要重建时才有意义,因此一般使用“$<”替换当前的自动化变量
- “$@”代表规则的目标
- “$<”代表规则中通过目录搜索得到的依赖文件列表的第一个依赖文件
VPATH = src:../headers
foo.o : foo.c head1.h head2.h
cc -c $(CFLAGS) $^ -o $@
# 命令相当于:cc -c $(CFLAGS) [VPATH]foo.c [VPATH]head1.h [VPATH]head2.h -o foo.o
VPATH = src:../headers
foo.o : foo.c head1.h head2.h
cc -c $(CFLAGS) $< -o $@
# 命令相当于: cc -c $(CFLAGS) [VPATH]foo.c -o foo.o
4.5 隐含的规则和搜索目录
通过变量“VPATH”、或者关键字“vpath”指定的搜索目录,对于隐含规则同样有效。例如:一个目标文件“foo.o”在 Makefile 中没有重建它的明确规则,make 会使用隐含规则来由已经存在的“foo.c”来重建它。当“foo.c”在当前目录下不存在时,make 将会进行目录搜索。如果能够在一个可以搜索的目录中找到此文件,同样 make 会使用隐含规则根据搜索到的文件完整的路径名去重建目标,编译这个.c 源文件。
4.6 库文件和搜索目录
在规则的依赖中使用库文件
foo : foo.c -lmylib
cc $^ -o $@
# -lmylib表示在重建foo目标文件时需要链接到的库文件,其中库文件名mylib(可以是动态也可以是静态)
# make会根据变量“.LIBPATTERNS”指定的模式来寻找mylib
# 一般变量“.LIBPATTERNS”的模式为“lib%.so lib%.a”
# make在找库文件时,首先查找当前目录和VPATH等变量指定的目录中是否有名为“libmylib.so”的依赖文件
# 如果没有则会在系统提供的库文件目录中去寻找,如果还没有找到,则会采用第二种模式即查找名为“libmylib.a”的依赖文件,查找路径和上一步相同
5. Makefile伪目标
伪目标定义:当使用make命令指定此目标时,这个目标所在规则定义的命令,无论目标文件是否存在都被无条件执行
伪目标的作用:在执行make时可以指定这个目标来执行其所在规则定义的命令,也可称为标签。
使用伪目标的两点原因:
- makefile中只执行命令的目标和工作目录下的实际文件出现名字冲突
- 提高make时的效率,尤其是大型的工程
5.1 伪目标可以解决make时目标与工作目录重名的冲突
背景介绍
clean:
rm *.o temp
当工作目录下没有clean这个文件时,输入“make clean”即可执行“rm *.o temp”这个规则;但当工作目录存在名为“clean”的文件时,由于该目标没有依赖项,make在检查是否需要更新时,始终认为当前工作目录下的文件为最新的,导致其下的规则无法被执行。
执行结果
当工作目录下没有与目标重名时,其规则可以正常被执行

当工作目录下存在与目标重名时,规则不被执行

使用伪目标代替
.PHONY : clean
clean:
rm -rf *.o temp
执行结果

注:当一个目标被声明为一个伪目标后,make在执行此规则时不会去试图查找隐含规则来创建它
5.2 伪目标在make并行和递归执行过程中的作用
- 伪目标可以作为依赖出现,其作用相当于编程语言中的函数调用
make的隐含变量“RM”,其实质为“rm -f”,可以使用“$(RM)”在makefile中直接使用
6. Makefile的特殊目标
- .PHONY
- 该目标的所有依赖都被认为是伪目标
- 伪目标定义:当使用make命令指定此目标时,这个目标所在规则定义的命令,无论目标文件是否存在都被无条件执行
- .DEFAULT
- Makefile中,该目标所在规则定义的命令,被用在重建那些没有具体规则的目标(明确规则和隐含规则)。即:一个文件作为某个规则的依赖,但却不是另外一个规则的目标时,Make程序无法找到重建此文件的规则,此种情况时就会执行“.DEFAULT”所指定的命令
- .PRECIOUS
- .INTERMEDIATE
- .SECONDARY
- 该目标的依赖文件被作为中间过程文件对待。但这些文件不会被自动删除。当该目标没有依赖文件时,表示将所有的文件作为中间过程文件(不会自动删除任何文件)
- .DELETE_ON_ERROR
- 如果该目标存在,则make在执行过程,如果规则的命令执行错误,将删除已经被修改的目标文件
- .IGNORE
- make在执行时,会忽略创建该目标依赖文件的执行命令错误。当此目标没有依赖问件时,将忽略所有命令执行的错误
- 此目标的命令没有意义
- .LOW_RESOLUTION_TIME
- .SILENT
- 出现在目标“.SILENT”的依赖列表中的文件,make 在创建这些文件时,不打印出重建此文件所执行的命令。同样,给目标“.SILENT”指定命令行是没有意义的
- 可以用“-s”或者“-silent”选项替代此特殊目标
- .EXPORT_ALL_VARIABLES(★)
- 此目标应该作为一个简单的没有依赖的目标,它的功能含义是将之后所有的变量传递给子make 进程
- .NOTPARALLEL
- Makefile 中,如果出现目标“.NOPARALLEL”,则所有命令按照串行方式执行,即使存在make 的命令行参数“-j”。但在递归调用的子 make 进程中,命令可以并行执行。此目标不应该有依赖文件,所有出现的依赖文件将被忽略
7. 多目标
一个规则中可以有多个目标,规则所定义的命令对所有的目标有效。多目标规则意味着所有的目标具有相同的依赖文件
自动环变量“$@”
多目标的使用场景:
-
只需要一个描述依赖关系的规则,不需要在规则中定义命令
-
target_1.o target_2.o target_3.o : command.h
-
-
对于多个具有类似重建命令的多个目标,重建这些目标的命令并不需要是完成相同的,可以在命令中使用自动环变量“$@”来引用具体的目标,完成对其的重建
-
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
-
8. 多规则目标
定义:
- 一个文件可以作为多个规则的目标
- 多个规则中只能有一个规则定义命令(重建此目标的命令只能出现在一个规则中,可以是多条命令)
- 如果多个规则同时给出重建此目标的命令,make将使用最后一个规则中所定义的命令,同时提示错误信息
- 例外,使用“.”开头的多规则目标文件,可以在多个规则中给出多个重建命令
- 相同的目标使用不同的规则中所定义的命令的方法为
“双冒号”规则
- make在执行时,以这个文件为目标的规则的所有依赖文件将会被合成此目标一个依赖文件列表
- 当依赖列表中任何一个依赖文件比目标更新时,将会执行特定的命令来重建这个目标
9. 静态模式规则
定义:
- 规则存在多个目标
- 不同的目标可以根据目标文件的名字来自动构造出依赖文件
- 不需要具有相同的依赖文件,但依赖文件必须是相类似的而不是完成相同的
语法:
TARGETS ...: TARGET-PATTERN: PREREQ-PATTERNS ...
COMMANDS
....
# TARGETS: 此规则的一系列目标文件
# ”TARGET-PATTERN“和”PREREQ-PATTERNS“:说明了如何为每一个目标文件生成依赖文件
# 从”TAGET-PATTERN“,即目标模式的目标名字中抽取一部分字符串(称为”茎“)
# 使用”茎“来替代依赖模式(PAEREQ-PATTERNS)中的相应部分来产生对应目标的依赖文件
# 模式字符”%“
# 在目标模式中”%“可以匹配目标文件的任何部分;eg: "foo.o"符合模式”%.o“,其茎为”foo“
# 每一个目标依赖文件都是使用茎替代依赖模式中的模式字符”%“得到的;eg:"%.c", 则茎”foo“,替换后的依赖为”foo.c“
注: 在Makefile中,”$<" 表示规则中的第一个依赖文件,"$@"表示规则中目标文件
10. 双冒号规则
同一个目标可以出现在多个规则中,但这些规则要么都是单冒号规则要么都是双冒号规则,不存在一个目标同时出现在单冒号与双冒号规则的可能。
对于一个目标出现在多个双冒号规则的情况,每一个规则相互独立,只有当目标的依赖比目标新时会执行对应的规则
单冒号规则则会将多个同目标规则的依赖进行整合为一个规则
此外,双冒号规则的目标没有依赖时,如果make当前目标,则该规则始终被执行
单目标规则则是在工作目录下都目标文件时,该规则始终不会被执行,因为此处会认为,目标文件始终最新
11. 自动产生依赖
GCC通过“-M”选项可以自动对目标源文件产生依赖关系
例如:main.c中有include 一些头文件,当头文件更改时需要重新执行指令,传统做法为:“main.o : yilai.h”
为避免繁琐的书写这种规则,可以使用“GCC -M main.c”自动对main.c中include的头文件生成对应的依赖关系