Makefile规则
Makefile中的规则是为了说明何时以及如何生成target,规则列出了target的prerequisites和recipe。
除了第一条规则作为默认规则,其他规则的顺序并不重要。如果第一条规则有多个target,那仅有第一个target作为默认target。以“.”开头的target一定不会是默认target。
因此当Makefile编译多个程序时,会使第一条规则的默认target为all,prerequsities为依赖的多个程序。
规则示例
下面是一个规则示例
foo.o : foo.c defs.h # module for twiddling the frobs
cc -c -g foo.c
target是foo.o,prerequisites是foo.c def.c,recipe中有一条命令cc -c -g foo.c。 recipe是以tab开头的。
这条规则说明两件事,一是如何判断foo.o是否过期;二是如果更新foo.o。
规则语法
targets : prerequisites
recipe
…
targets : prerequisites ; recipe
recipe
…
targets是以空格分隔的文件名,也可以是通配符表示的文件名。正常情况下,targes唯一。
recipe默认以tab开头,但是也可以使用.RECIPEPREFIX变量指定。第一行recipe可以以;跟在prerequisites后,也可以以tab开头另起一行。
$被用于变量引用,需要使用$本身,语法$$。
规格告诉make两件事情:targets何时过期;如果更新targets。
Prerequisites的类型
prerequisites有两种类型,一是normal prerequisites,另一种时order-only prerequisites。
normal prerequisites有两个功能,分别是:
- 规定了
prerequisites的recipe必须在target之前执行。 - 当
prerequisites发生变化时,target必须更新。
order-only prerequisites和normal prerequisites的不同点在于,不执行第2条,也就是说order-only prerequisites发生变化时,target不会更新。
使用order-only prerequisites的语法如下:
targets: normal-prerequisites | order-only-prerequisites
示例:
objdir := objdir
# 规则1
all: test.c | $(objdir)
@echo "excute recipe"
# 规则2
$(objdir):
@echo "mkdir objdir"
mkdir $(objdir)
在这个例子中,第一次make时会先执行规则2->规则1,但是修改objdir文件夹中的内容后再次make时仅会执行规则1,而不会执行规则2。因为order-only prerequisites发生变化时,targets是不会更新的。
文件名中使用通配符
使用通配符的单个文件名可以指定多个文件。make中的通配符有*、?、[...]。*.c指代一系列以.c结尾的文件名。
以~开头的文件名具有特殊含义,如果后面直接跟/则表示当前用户目录。如果后面跟单词则代表该单词所表示的用户目录,例如~john/bin表示/home/john/bin。和环境变量HOME含义一致。
targets和prerequisites中的通配符会被make扩展,recipes中的通配符由shell来扩展。其他情况下只有使用通配符函数才会被扩展。
要使用通配符自身,需要使用\转移。
下面是在recipes中使用通配符,由shell扩展。
clean:
rm -rf *.o
下面是在prerequisites中使用通配符,由make进行扩展。
print: *.c
lpr -p $?
touch print
该条规则会打印更新的所有.c文件。
在变量中直接使用通配符不会被扩展,例如objects=*.o。但是如果该变量用到prerequisites或recipes时或使用通配符函数会被扩展,例如objects:=$(wildcard *.c)。
wildcard函数
规则中的通配符会自动扩展,但是定义变量或函数参数中的通配符不会自动替换。如果需要在这些扩展通配符,需要wildcard函数。函数原型如下:
$(wildcard pattern...)
这个函数可以用在makefile的任何地方,会被模式规则匹配的一系列文件进行替代。
# 当前目录下所有.c文件
$(wildcard *.c)
# 所有.c文件对应的.o文件
$(patsubst %.c, %.o, $(wildcard *.c))
# 编译所有源文件并链接如下所示(采用隐含规则)
objects := $(patsubst %.c, %.o, $(wildcard *.c))
foo: $(objects)
cc -o foo $(objects)
在目录中查找prerequisites
在大型过程中,通常会将源码和二进制文件放在不同目录。make可以自动搜索多个目录来查找prerequisites来支持这一点。
VPATH变量为所有prerequisites指定搜索目录。make会在VPATH指定的目录中查找当前目录中不存在prerequisites和targets。
VPATH变量中,目录名称由冒号或空格分隔。列出的目录顺序是make的搜索顺序。
# 列出了两个目录,src和../headers, make查找的顺序一致
VPATH = src:../headers
vpath指令
- 类似于
VPATH变量,但是更具选择性,因为它可以为模式匹配的文件指定搜索路径。 - 使用语法如下:
vpath pattern directories为pattern匹配的文件查找指定的directories。
vpath pattern清除与pattern关联的目录。
vpath清除之前使用的所有目录。
# 在../headers目录中查找头文件
vpath %.h ../headers
vpath %.c foo
vpath % blish
vpath %.c bar
目录搜索执行规则:
- 如果在
makefile指定的路径中不存在target文件时,则执行目录搜索。 - 如果目录搜索成功,则保留该路径,并将此文件暂时作为目标。
- 该
target的prerequisites搜索方法同上。 - 如果
target不用更新,则使用目录搜索的路径;如果target需要更新,那么target会在当前目录生成而不是在目录搜索产生的路径中。
使用目录搜索时写recipe
当通过目录搜索在另一个目录中找到prerequisites时,写recipe务必细心,要保证make能够找到prerequisites。
自动变量:$^表示列出的所有prerequisites(包括从目录搜索中找到文件);$@表示target;$<表示第一个prerequisites。
# CFLAGS变量能够指定C编译参数。
foo.o : foo.c
cc -c $(CFLAGS) $^ -o $@
# $<表示第一个prerequisites,不会包含头文件
VPATH = src:../headers
foo.o : foo.c defs.h hack.h
cc -c $(CFLAGS) $< -o $@
伪target
伪target不是一个文件名,而是要执行的recipe的名称。使用伪target有两个原因:1是避免和同名文件冲突;2是提高性能。
# 该规则不会创建clean文件,所有每次执行make clean时rm命令都会被执行。并且如果目录中存在clean文件时,这条规则不会正常执行。
clean:
rm *.o temp
# 下面这条规则,无论当前目录中是否存在clean,都会被执行
.PHONY: clean
clean:
rm *.o temp
将伪target和make递归调用结合使用是很有用的。
# 在这个例子中,SUBDIRS变量列出所有要构建的目录。
# 并且采用循环遍历子目录的方式构建子目录
SUBDIRS = foo bar baz
subdirs:
for dir in $(SUBDIRS); do \
$(MAKE) -C $$dir; \
done
# 这种方式存在两个问题:
# 一是如果其中一个子目录编译出现问题,其他子目录会继续编译。
# 二是不能使用make的并行构建方式。
# 为了解决上述问题,可以采用下面的构建方式
SUBDIRS = foo bar baz
.PHONY: subdirs $(SUBDIRS)
subdirs: $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $@
# 定义foo必须在baz构建之后
foo: baz
**伪target会自动跳过隐式规则搜索。**这也是伪target性能高的原因。
伪target不应该是target的prerequisites,否则的话每次运行make都会更新target。
伪target也可以拥有prerequisites。
# 如果Makefile同时生成多个程序时,通常采用如下形式。
# 将第一个target定义为伪target,依赖其他所有需要生成的程序。
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
如果一个规则没有prerequisites或recipe,并且target是一个不存在的文件,那么这条规则无论何时运行都会更新target,因此所有依赖这个target的规则recipe总是会被执行。
# FORCE没有prerequisites和recipe,依赖于FORCE的clean的recipe总是会被执行
# 这种方式和.PHONY异曲同工。
clean: FORCE
rm $(objects)
FORCE:
# ECHO没有prerequisites,依赖于ECHO的$(SUBDIRS)的recipe每次都会被执行。
$(SUBDIRS):ECHO
make -C $@
ECHO:
@echo $(SUBDIRS)
多target规则
当规则中拥有多个target时,target会分为两类:独立target和分组target。具体是哪种target,由target后面的分隔符决定。
具有独立target的规则
使用标准target分隔符(“:”)的规则为独立target的规则,“:”用来定义独立targets。这等价于为每个target编写具有相同prerequisites和recipe的规则。recipe能够使用自动变量$@来指定正在构建的target。
具有独立target的规则有两个使用场景:
# 1、所有target仅有prerequisites,没有recipe
kbd.o command.o files.o: command.h
# 上面这条规则等价于
kbd.o: command.h
command.o: command.h
files.o: command.h
# 2、所有target具有相似的recipes,通过$@变量来指定将要构建的target
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
上面的示例展示如果对不同的target使用相同的prerequisites和recipes,如果想要对不同的target使用不同的prerequisites,可以采用静态模式规则。
具有分组target的规则
多个target和prerequisites使用&:分隔则是具有分组target的目标。
分组target的规则的使用场景是组内的每个target都是由规则中的recipe生成。
当make构建任何一个分组target时,他知道组内的其他target也是由recipe构建的,因此当其他目标不存在或过时,会自动进行更新。
具有多条规则的target
单个文件可以是多个规则的target。make生成该target,所有这些规则的prerequisites会进行合并。如果任何一个prerequisites比target新,则会重新生成target。
单个文件尽可以使用一个recipe生成,如果多条规则具有多个recipe,则会执行最后一个并打印错误。
静态模式规则
静态模式规则是指定多个target并根据target名称为每个target构造prerequisites的规则。这比具有多个target的规则更通用,因为prerequisites不要求相同,相似即可。
静态模式规则语法
targets...: target-pattern: prereq-patterns...
recipe
...
targets列表指定规则适用的target。可以和普通规则的target一致包含通配符。
target-pattern和prereq-pattern说明了如何生成每个target的prerequisites。每个target都与target-pattern进行匹配用来提取target匹配的部分作为词干,这个词干替换到prereq-pattern用以生成每个target的prerequisites。
每个模式通常仅会包含一个%。当target-pattern匹配target时,%会匹配target名称的一部分,这部分成为词干。剩余的部分也必须完全匹配。例如foo.o匹配%.o而foo.c不匹配%.o。
每个target的prerequisites都是将名称中%替换为词干。例如如果词干是foo,而prereq-pattern是%.c,则prerequisites是foo.c。
prerequisites中可以不包含%,那么这个prerequisites对每个target都是一致的。
# 从对应的.c文件生成foo.o和bar.o
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
# $<是自动变量表示prerequisites,$@表示target。
**每个target必须匹配target-pattern,否则会提示警告。**如果由多个文件,其中由部分文件不匹配target-pattern,可以使用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 $<
静态模式规则和隐式规则
- 原文作者:生如夏花
- 原文链接:https://DBL2017.github.io/post/%E5%B7%A5%E5%85%B7%E4%BD%BF%E7%94%A8/%E6%9E%84%E5%BB%BA%E5%B7%A5%E5%85%B7/makefile/makefile%E8%A7%84%E5%88%99/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。