3.0 书写规则
# 书写规则
规则包含两个部分,一个是依赖关系 (target ... : prerequisites ...),一个是生成目标的方法 (recipe)。
# 在规则中使用通配符
make 支持三个通配符: * , ? 和 ~ 。这是和 Unix 的 B-Shell 是相同的。
波浪号( ~ )字符在文件名中也有比较特殊的用途:
- 如果是
~/test,这就表示当前用户的$HOME目录下的 test 目录。 - 而
~ningxu/test则表示用户 ningxu 的宿主目录下的 test` 目录。
参考链接 Meaning of Tilde in Linux Bash (opens new window)
通配符 * 代替了你一系列的文件,如 *.c 表示所有后缀为 c 的文件。一个需要我们注意的是,如果我们的文件名中有通配符,如: * ,那么可以用转义字符 \ ,如 \* 来表示真实的 * 字符,而不是任意长度的字符串。
clean:
rm -f *.o
2
值得注意的是:通配符同样可以用在变量中,但 *.o 并不会在变量中被展开:
objects = *.o
objects 的值就是 *.o 。Makefile 中的变量其实就是 C/C++ 中的宏。如果你要让通配符在变量中展开,也就是让 objects 的值是所有 .o 的文件名的集合,那么,你可以这样:
objects := $(wildcard *.o)
# 文件搜索
文件搜索路径的设置方法:
方法一:
如果没有指明变量 VPATH,make 只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,make 就会在当前目录找不到的情况下,到所指定的目录中去找寻文件了。
VPATH = src:../headers
上面的定义指定两个目录,“src” 和 “../headers”,make 会按照这个顺序进行搜索。目录由 “冒号” 分隔。(当然,当前目录永远是最高优先搜索的地方)
方法二:
vpath 可以指定不同的文件在不同的搜索目录中,它有三种使用方法:
vpath <pattern> <directories>
为符合模式 <pattern> 的文件指定搜索目录 <directories> 。
vpath <pattern>
清除符合模式 <pattern> 的文件的搜索目录。
vpath
清除所有已被设置好了的文件搜索目录。
vpath 使用方法中的 <pattern> 需要包含 % 字符。 % 的意思是匹配零或若干字符.
vpath %.h ../headers
该语句表示,要求 make 在 ../headers 目录下搜索所有以 .h 结尾的文件。(如果某文件在当前目录没有找到的话)
如果连续的 vpath 语句中出现了相同的 <pattern> ,或是被重复了的 <pattern> ,那么,make 会按照 vpath 语句的先后顺序来执行搜索。如:
vpath %.c foo
vpath % blish
vpath %.c bar
2
3
其表示 .c 结尾的文件,先在 “foo” 目录,然后是 “blish”,最后是 “bar” 目录。
# 伪目标
使用一个特殊的标记 “.PHONY” 来显式地指明一个目标是 “伪目标”,向 make 说明,不管是否有这个文件,这个目标就是 “伪目标”,伪目标永远会被执行。
伪目标最常见的使用方法就是声明 clean 为伪目标,这样无论是否有 clean 这个文件存在,目标都会被执行。
.PHONY : clean
clean :
rm *.o temp
2
3
伪目标的其他使用方法:
- 使用伪目标作为 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
2
3
4
5
6
7
8
9
10
11
Makefile 中的第一个目标会被作为其默认目标。我们声明了一个 “all” 的伪目标,其依赖于其它三个目标。因为伪目标总是被执行,所以其它三个目标的规则总是会被决议。也就达到了我们一口气生成多个目标的目的。
- 伪目标也可以成为依赖 (prerequisites):
.PHONY : cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff
rm program
cleanobj :
rm *.o
cleandiff :
rm *.diff
2
3
4
5
6
7
8
9
10
# 多目标
Makefile 的规则中的目标可以不止一个,其支持多目标,有可能我们的多个目标同时依赖于一个文件,并且其生成的命令大体类似。于是我们就能把其合并起来。
bigoutput littleoutput : text.g
generate text.g -$(subst output,,$@) > $@
2
$@ 表示目标的集合,就像一个数组, $@ 依次取出目标,并执于命令。
等价于:
bigoutput : text.g
generate text.g -big > bigoutput
littleoutput : text.g
generate text.g -little > littleoutput
2
3
4
# 静态模式
静态模式可以更容易的定义多目标,语法为:
<targets ...> : <target-pattern> : <prereq-patterns ...>
<commands>
...
2
3
- targets 定义了一系列的目标文件,可以有通配符。是目标的一个集合。
- target-pattern 是指明了 targets 的模式,也就是的目标集模式 (所有的目标都必须满足该模式)。
- prereq-patterns 是目标的依赖模式,它对 target-pattern 形成的模式再进行一次依赖目标的定义。
如果我们的 <target-pattern> 定义成 %.o ,意思是 <target> 集合的所有文件都是以 .o 结尾的,而如果我们的 <prereq-patterns> 定义成 %.c ,意思是对 <target-pattern> 所形成的目标集进行二次定义,其计算方法是,取 <target-pattern> 模式中的 % (也就是去掉了 .o 这个结尾),并为其加上 .c 这个结尾,形成的新集合。
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
2
3
4
5
6
- 目标模式
%.o表示 $(objects) 中.o所有目标都是以.o结尾,也就是foo.o,bar.o - 依赖模式
%.c表示%.o的%,也就是foo,bar,并为其加下.c的后缀,于是,我们的依赖目标就是foo.c,bar.c。 $<和$@则是自动化变量,$<表示第一个依赖文件,$@表示目标集(也就是foo.o,bar.o)
$@ 表示目标的集合,就像一个数组, $@ 依次取出目标,并执于命令,上面的规则展开后就得到:
foo.o : foo.c
$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
$(CC) -c $(CFLAGS) bar.c -o bar.o
2
3
4
“静态模式规则” 的用法很灵活,如果用得好,那会是一个很强大的功能。再看一个例子:
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 $<
2
3
4
5
6
$(filter %.o,$(files)) 表示调用 Makefile 的 filter 函数,过滤 $(files) 集,只要其中模式为 “%.o” 的内容
# 自动生成依赖性
TBD: 自动生成依赖性我后续会参考 nemu 中 makefile 的方法来添加.