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://blduan.top/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 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。