1.前言
在Makefile中,规则
描述了用什么命令生成一个文件,该文件被称为规则的目标
,生成"目标"的方式就是规则,目标通常只有一个,除了目标之外的其他文件称为“目标”的依赖
“终极目标”就是当没有指定具体目标时,make的默认目标,通常是Makefile中的第一个目标。
2.规则语法
规则语法通常如下:
TARGETS : PREREQUISITES
[Tab]COMMAND
或
TARGETS : PREREQUISITES;COMMAND
- ”TARGETS“可以是空格分开的多个文件名,也可以是一个标签(例如“clean”),它可以使用通配符
- 书写有两种方式:1.命令可以和目标:依赖描述放同一行,命令在依赖文件列表后使用分号“;”和依赖文件列表分开。2.命令在目标的下一行,当独立命令行,次数必须以[Tab]字符开始
- Makefile中符号“$”有特殊的含义(表示变量或者函数的引用),在规则中需
要使用符号“$”的地方,需要书写两个连续的“$$” - 对于较长行,我们可以使用反斜线“\”将其分离
规则的思想:目标文件的内容是由依赖文件文件决定,依赖文件的任何一处改动,将导致目前已经存在的目标文件的内容过期
。
3.依赖
在GNU make中有两种依赖
- 常规依赖,这是书写Makefile规则时最常用的一种
- 不经常使用的,它比较特殊、称之为“order-only”依赖
3.1 “order-only”依赖
一个规则的依赖表面了两件事
- 决定了重建此规则目标所要执行规则(确切的说是执行命令)的顺序
- 更新目标(执行此规则的命令行)之前要按照什么顺序、执行那些规则(命令)来重建依赖文件
通常,如果规则中依赖文件中的任何一个被更新,则规则的目标相应地也应该被更新
对于更新这些依赖的,不需要更新规则的目标。我们称为:“order-only”依赖
书写规则时,“order-only”依赖使用管道符号“|”开始,作为目标的一个依赖文件。规则依赖列表中管道符号“|”左边的是常规依赖,管道符号右边的就是“order-only”依赖
如下
TARGETS : NORMAL-PREREQUISITES | ORDER-ONLY-PREREQUISITES
3.3 通配符
Maekfile中表示文件名时可使用通配符。用法和含义和 Linux(unix)的 Bourne shell完全相同,包含
“*”、“?”和“[...]”
在Makefile中,通常只有两种情况才可以使用通配符
- 可以用在规则的目标、依赖中,make在读取Makefile时会自动对其进行匹配处理(通配符展开)
- 可出现在规则的命令中,通配符的通配处理是在shell在执行此命令时完成的
例如
clean:
[Tab]rm -f *.o
3.3.1 通配符的缺陷
在使用通配符时,可能有如下缺陷,例如
objects = *.o
foo : $(objects)
[Tab]cc -o foo $(CFLAGS) $(objects)
在构建foo目标的时候,对变量object进行展开,如果工作目录下存在必要的.o文件,那么这些.o文件就会成为依赖文件,如果目录下没有.o文件,那么会出现“没有创建.o文件”的错误
3.3.2 wildcard
上文提到通配符定义变量时,会出现错误的情况,这时可以使用函数“wildcard”,用法如下
$(wildcard pattern...)
这样的话,如果变量展开为已存在,使用空格分开的文件列表,如果不存在对应文件,也不会出现不存在文件的错误,它会忽略模式字符并返回空,使用如下
$(wildcard *.c)
这样就可以获得工作目录下所有的.c文件列表
4 搜索
4.1 一般搜索(变量VPATH)
变量“VPATH”可以指定依赖文件的搜索路径,
当规则的依赖文件在当前目录不存在时,make 会在此变量所指定的目录下去寻找这些依赖文件。
其实“VPATH”变量所指定的是Makefile中所有文件的搜索路径
,包括了规则的依赖文件和目标文件
用法如下:使用空格或冒号":"将多个需要搜索的目录分开
VPATH = src:../headers
这样搜索目录就是"src"和"../headers"
4.2 选择性搜索(关键字vpath)
还有一种搜索是使用make的关键字"vpath",这是一个关键字,它有三种用法
- vpath PATTERN DIRECTORIES
为所有符合模式“PATTERN”的文件指定搜索目录“DIRECTORIES”。多个目录使用空格或者冒号(:)分开。类似“VPATH”变量 - vpath PATTERN
清除之前为符合模式“PATTERN”的文件设置的搜索路径 - vpath
清除所有已被设置的文件搜索路径
4.2.1 %匹配字符
vapth使用的“PATTERN”需要包含模式字符“%”,表示匹配一个或者多个字符,例如"%.h"表示所有以".h"结尾的字符,如果没有%,那么就是一个明确文件
例如
vpath %.h ../headers
在"../headers"目录下搜索".h"结尾的文件
多个具有相同“PATTERN”的vpath语句之间相互独立,例如
vpath %.c foo
vpath %.c bar
和
vpath %.c foo:bar
表示对所有的.c文件,依次查找目录是foo和bar
4.3 目录搜索机制
make在解析Makefile文件执行规则时对文件路径保存或废弃所依据的算法如下
- 如果规则的目标文件在Makefile文件所在的目录(工作目录)下不存在,那么就执行目录搜寻
- 如果目录搜寻成功,在指定的目录下存在此规则的目标。那么搜索到的完整的路径名就被作为临时的目标文件被保存
- 对于规则中的所有依赖文件使用相同的方法处理
- 完成第三步的依赖处理后,make程序就可以决定规则的目标是否需要重建
4.1. 规则的目标不需要重建:那么通过目录搜索得到的所有完整的依赖文件路径名有效,同样,规则的目标文件的完整的路径名同样有效
4.2. 规则的目标需要重建:那么通过目录搜索所得到的目标文件的完整的路径名无效,规则中的目标文件将会被在工作目录下重建
4.4 隐含规则和搜索目录
通过变量“VPATH”、或者关键字“vpath”指定的搜索目录,对于隐含规则同样有效
例如一个目标文件“foo.o”在Makefile中没有重建它的明确规则,那么会根据隐含规则由已经存在的“foo.c”来重建它。当“foo.c”在当前目录下不存在时,make将会进行目录搜索。
4.5 库文件和搜索目录
Makefile中程序链接的静态库、共享库同样也可以通过搜索目录得到
例如对于依赖文件“-lNAME”,详细搜索过程如下
- make在执行规则时会在当前目录下搜索一个名字为“libNAME.so”的文件
- 如果当前工作目录下不存在这样一个文件,则 make会继续搜索使用“VPATH”或者“vpath”指定的搜索目录
- 还是不存在,make 将搜索系统库文件存在的默认目录,顺序是:“/lib”、“/usr/lib”和“PREFIX/lib”(不同系统可能有差异)
如果“libNAME.so”通过以上的途径最后还是没有找到的话,那么make将会按照以上的搜索顺序查找名字为“libNAME.a”的文件
5 Makefile伪目标
伪目标是这样一个目标: 它不代表一个真正的文件名,在执行make时可以指定这个目标来执行其所在规则定义的命令,有时也可以将一个伪目标称为标签
使用伪目标的原因(目的):
- 避免在我们的Makefile中定义的只执行命令的目标(此目标的目的为了执行执行一些列命令,而不需要创建这个目标)和工作目录下的实际文件出现名字冲突。
- 提高执行make时的效率
常见的伪目标
clean:
rm *.o temp
这里的clean就是一个伪目标
但是,如果当前目录下存在clean文件,那这就不是一个伪目标了
这时,为了让它依旧是一个伪目标,可以这样做
.PHONY : clean
这样无论当前目录是否存在clean,clean都是一个伪目标
完整格式如下
.PHONY: clean clean:
rm *.o temp
5.1 伪目标的用法
除了上文中伪目标的用法,伪目标的一个常见用法是并行和递归中,例如
- 没有使用伪目标
SUBDIRS = foo bar baz
subdirs:
for dir in $(SUBDIRS); do \
$(MAKE) -C $$dir; \ done
这样做的缺点:
- 当子目录执行make出现错误时,make不会退出
- 使用这种shell的循环方式时, 没有用到make对目录的并行处理功能
- 使用伪目标
SUBDIRS = foo bar baz
.PHONY: subdirs $(SUBDIRS)
subdirs: $(SUBDIRS) $(SUBDIRS):
[Tab]$(MAKE) -C $@
foo: baz
上面最后一行是限制foo,baz的顺序,baz在foo之后执行,这样写就解决了方法1中的两个问题
一般情况下,一个伪目标不作为另外一个目标的依赖。这是因为当一个目标文件的依赖包含伪目标时,每一次在执行这个规则时伪目标所定义的命令都会被执行
6 强制目标
一个规则没有命令或者依赖,并且它的目标不是一个存在的文件名.在执行此规则时,目标总会被认为是最新的
就是说:这个规则一旦被执行,make就认为它的目标已经被更新过。这样的目标在作为一个规则的依赖时,因为依赖总被认为被更新过,因此作为依赖所在的规则中定义的命令总会被执行
例如
clean: FORCE
rm $(objects)
FORCE:
目标“FORCE”符合上边的条件。它作为目标“clean”的依赖,在执行make时,总被认为被更新过。因此“clean”所在规则在被执行时其所定义的命令总会被执行。这样的一个目标通常我们将其命名为“FORCE”
7 空目标文件
空目标文件是伪目标的一个变种;此目标所在规则执行的目的和伪目标相同——通过make命令行指定将其作为终极目标来执行此规则所定义的命令
空目标文件只是用来记录上一次执行此规则命令的时间,当前目录下如果不存在 这个文件,“touch”会在第一次执行时创建一个空的文件(命名为空目标文件名),例如
print: foo.c bar.c
[Tab]lpr -p $?
[Tab]touch print
执行“make print”,当目标“print”的依赖文件任何一个被修改之后,命令“lpr –p $?”都会被执行,打印这个被修改的文件
8 Makefile的特殊目标
在 Makefile 中,有一些名字,当它们作为规则的目标时,具有特殊含义,即特殊目标。具体如下
8.1 .PHONY:
目标“.PHONY”的所有的依赖被作为伪目标
8.1 .SUFFIXES:
特殊目标“SUFFIXES”的所有依赖指出了一系列在后缀规则中需要检查的后缀名 (就是当前make需要处理的后缀,后续说明)
8.3 .DEFAULT
就是说一个文件作为某个规则的依赖,但却不是另外一个规则的目标时。Make程序无法找到重建此文件的规则,此种情况时就执行“.DEFAULT”所指定的命令
8.4 .PRECIOUS和.SECONDARY
特殊处理:当命令在执行过程中被中断时,make不会删除它们,而且如果目标的依赖文件是中间过程文件,同样这些文件不会被删除
8.5 .INTERMEDIATE
目标“.INTERMEDIATE”的依赖文件在make时被作为中间过程文件对待。没有 任何依赖文件的目标“.INTERMEDIATE”没有意义
8.6 .DELETE_ON_ERROR
make在执行过程中,如果规则的命令执行错误,将删除已经被修改的目标文件
8.7 .IGNORE
给目标“.IGNORE”指定依赖文件,则忽略创建这个文件所执行命令的错误,给此目标指定命令是没有意义的
8.8 .LOW_RESOLUTION_TIME
目标“.LOW_RESOLUTION_TIME”的依赖文件被make认为是低分辨率时间戳 文件。给目标“.LOW_RESOLUTION_TIME”指定命令是没有意义的
8.9 .SILENT
出现在目标“.SILENT”的依赖列表中的文件,make在创建这些文件时,不打印出重建此文件所执行的命令。同样,给目标“.SILENT”指定命令行是没有意义的
8.10 .EXPORT_ALL_VARIABLES
此目标应该作为一个简单的没有依赖的目标,它的功能含义是将之后所有的变量传 递给子make进程
8.10 .NOTPARALLEL
Makefile 中,如果出现目标“.NOPARALLEL”,则所有命令按照串行方式执行,但在递归调用的字make进程中,命令可以并行执行
9 多目标
一个规则中可以有多个目标,规则所定义的命令对所有的目标有效
例如
kbd.o command.o files.o: command.h
这个规则实现了同时给三个目标文件指定一个依赖文件
对于多个具有类似重建命令的目标。重建这些目标的命令并不需要是完全相同,
因为可以在命令行中使用自动环变量“$@”来引用具体的目标,完成对它的重建
例如
bigoutput littleoutput : text.g
[Tab]generate text.g -$(subst output,,$@) > $@
//等价于
bigoutput : text.g
[Tab]generate text.g -big > bigoutput
littleoutput : text.g
[Tab]generate text.g -little > littleoutput
10 多规则
上面说了一个规则可以有多个目标,另外,一个目标也可以有多个规则。这时,以这个文件的所有依赖文件将会被合并成此目标一个依赖文件列表。
11 静态模式
静态模式:规则存在多个目标,并且不同的目标可以根据目标文件的名字来自动构造出依赖文件。
静态模式比多目标更通用,它不需要多目标具有相同的依赖。但是依赖文件必须是相类似的而不是完全相同的。
用法如下
TARGETS ...: TARGET-PATTERN: PREREQ-PATTERNS ...
[Tab]COMMANDS
- TARGETS:目标文件
- “TAGET-PATTERN”和“PREREQ-PATTERNS”:如何为每一个目标文件生成依赖文件
从目标模式(TAGET-PATTERN)的目标名字中抽取一部分字符串(称 为“茎”)。使用“茎”替代依赖模式(PREREQ-PATTERNS)中的相应部分来产生对应目标的依赖文件
举一个例子
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
[Tab]$(CC) -c $(CFLAGS) $< -o $@
- 规则描述了所有的.o文件依赖.c文件
- 对于目标“foo.o”,取其茎“foo”替代对应的依赖模式“%.c”中的模式字符“%”之后可得到目标的依赖文件“foo.c”,这就是依赖关系"foo.o: foo.c"
- 描述了如何完成由“foo.c”编译生成目标“foo.o”,命令行中“@”是自动化变量
上述规则等价于
foo.o : foo.c
[Tab]$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
[Tab]$(CC) -c $(CFLAGS) bar.c -o bar.o
11.1 静态模式和隐含规则
隐含规则
可被用在任何和它相匹配的目标上
当存在多个隐含规则和目标模式相匹配时,只执行其中的一个规则。具体执行哪一个规则取决 于定义规则的顺序
静态模式
只能用在规则中明确指出的那些文件的重建过程中,不能用在除此之外的任何文件的重建过程中
它对指定的每一个目标来说是唯一的。如果一个目标存在于两个规则,并且这两个规则都定以了命令,make执行时就会提示错误
静态模式的优点
- 不能根据文件名通过词法分析进行分类的文件,可以明确列出并使用静态模式规则来重建其隐含规则
- 对于无法确定工作目录内容,并且不能确定是否此目录下的无关文件会使用错误的隐含规则而导致make失败的情况,使用静态模式规则就可以避免这些不确定因素
12 双冒号规则
双冒号规则就是使用“::”代替普通规则的“:”得到的规则
当同一个文件作为多个规则的目标时,双冒号规则的处理和普通规则的处理过程完全不同
Makefile中,一个目标可以出现在多个规则中。但是规则必须是同一类型的规则,要么都是普通规则,要么都是双冒号规则。而不允许一个目标同时出现在两种不同类型的规则中
12.1 双冒号规则和普通规则的不同
- 双冒号规则中,对于一个没有依赖而只有命令行的双冒号规则,当引用此目标时,规则的命令将会被无条件执行。而普通规则,当规则的目标文件存在时,此规则的命令永远不会被执行(目 标文件永远是最新的)
- 当同一个文件作为多个双冒号规则的目标时。这些不同的规则会被独立的处理,而不是像普通规则那样合并所有的依赖到一个目标文件
举个例子
Newprog :: foo.c
[Tab]$(CC) $(CFLAGS) $< -o $@
Newprog :: bar.c
[Tab]$(CC) $(CFLAGS) $< -o $@
如果“foo.c”文件被修改,执行make以后将根据“foo.c”文件重建目标“Newprog”。 而如果“bar.c”被修改那么“Newprog”将根据“bar.c”被重建
当同一个目标出现在多个双冒号规则中时,规则的执行顺序和普通规则的执行顺序 一样,按照其在 Makefile 中的书写顺序执行
一般这种需要的情况很少,所以双冒号规则的使用比较罕
13 自动产生依赖
Makefile中,有时需要书写一些规则来描述一个.o文件和头文件的依赖关系,例如在 main.c中使用“#include defs.h”,那么需要这样写
main.o: defs.h
这样修改起来就比较麻烦,所有可以使用自动寻找源文件中包含的头文件功能,如下
gcc -M main.c
它实际的作用是
main.o : main.c defs.h
这样就避免了手动写的麻烦和出错,当然,如果main.c中使用了标准库的头文件,那么使用gcc 的“-M”选项时,就会包含对标准库的头文件的依赖关系,如果不需要,则可以使用”-MM“
使用自动产生依赖功能时,所产生的规则明确指明了目标,在通过.c产生执行文件后,作为中间文件的main.o并不会被删除