Makefile文件有一些规则(一个目标所形成的结构称为一个规则(rule)),规则的格式如下:
:
[tab] commands
目标(target)…:先决条件(prerequisites,又叫依赖文件)…
命令(command)
…
…
通常"目标"对象通常是程序生成的一个文件的名称,例如是一个可执行文件或目标文件。目标也可以是所要采取活动的名字(伪目标),比如all、clean、install、uninstall
是具有特殊意义的目标(target),意义如下:
"先决条件"是一个或多个文件名,用作产生目标的输入条件。
而"命令"是make需要执行的操作。
一个规则可以有多个命令,每一个命令自成一行。注意需要在每个命令行之前键入一个Tab键。
make指令可以用来执行Makefile,在什么参数都不加的时候,make只会去执行Makefile中的第一个规则。如果要指定要执行哪个规则的话,可在make指令的第一个参数加上已经定义在Makefile中的目标文件名称,例如make all、make install或是make Hello.class。这也是为什么all这个目标通常会被放在第一个规则的原因,因为这样执行make就等于是执行make all。
预设写在Makefile的指令在使用make指令来执行的时候都会被显示出来,要隐藏的话可以加上@
Hello.class: Hello.java
@javac Hello.java
###略过执行失败的指令
在相同规则下,只要有某个指令执行失败,其接下来的指令就不会被执行。如果这个指令本身是允许失败的话,想要在其失败后继续执行之后的指令,可以在该指令前加上减号-
###变量
在读取Makefile所定义的变数或是外部的环境变数时,都可以使用以下的语法: $变量名称
但如果想要在指令中使用环境变量,却不想要透过GNU make来事先读取的话,可以使用以下的语法:$$环境变量名称(两个$$在Makefile中所代表的意义就是转义成一个$)
###万用字元%
在目标和依赖文件的路径中,可以使用%来表示任意数量的任意字元。由于%也可以包含空格,如果有多个文件,且%位于最后一个依赖文件前的文件的路径结尾,%后要加上一个冒号:来作分隔。
例如:
install: target/%: data/%
…
如果想定义一系列比较类似的文件,我们很自然地就想起使用通配符。make支持三种通配符:* ?和[]
这和UNIX的BShell是相同的。
通配符代替了一系列的文件,如“*.c"表示了所有以后级名为.c的文件。
如果文件名中含有通配符,那么可以用较义字符斜杠“\”。
下面是一个在命令中使用通配符的例子:
clean:
rm -f *.o
例子的含义是删除所有以.o为后缀名的文件,这是由操作系统的Shell所支特的通配符。
通配符也可以使用在Makefile的规则中,比如:
print: *.c
Ipr -p $?
touch print
目标print依赖于所有的.c文件。其中“$?”是一个自动化变量,表示所有比目标新的依赖文件的集合。
通配符还可以使用在变量中,比如:
objects = .o
需要注意的是,这和上面的两种情况不同,这里的.o不会展开!objects变量的值就是“*.o”
Makefile中的变量其实就是C中的宏。如果要让通配符在变量中展开,也就是让objects的值是所有.o的文件名的集合,那么,可以这样:
objects := $(wildcard *.o)
这种用法是通过Makefile的关键字"wildcard”(通配符)来指示的。
clean :
rm -f *o
这是一个伪目标,伪目标不是一个文件,而只是一个标签。
由于伪目标不是文件,所以make无法根据依赖关系来决定它是否要执行,只有显式地指明这个“目标”才能让其生效。
需要注意的是伪目标的取名不能与文件名重名,不然其就失去其伪目标的意义了。
当然,为了避免和文件重名的这种情况,我们可以使用一个特殊的标记“.PHONY”来显式地指明一个目标是伪目标,向make说明,不管是否有这个文件,这个目标都是伪目标。
.PHONY : clean
只要有这个声明,不管有没有“clean”文件,要运行“clean”这个目标。只要在命令行下输入命令“make clean”即可。整个过程可以这样写
.PHONY: clean
rm -f *.c
伪目标一般没有依赖文件,但是我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为默认目标,只要将它放在第一个。
比如,如果Makefile需要一口气生成若干可执行文件,但只想简单地输入一个“make”命令就完事,并且,所有的目标文件都写在一个Makefile中,那么便可以使用伪目标这个特性,比如如下所示的代码。
使用伪目标
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 proq3.o sort.o utils.o
我们知道,Makefile中的第一个目标会被作为其默认目标
在上述程序中声明了一个伪目标all,且依赖于其他三个目标prog1、prog2和prog3。
由于伪目标的特性是总是被执行的,所以其依赖的那三个目标总是不如“all”这个目标新,在进行make重编译时其他三个目标的规则就会总是被决议,也就达到了我们一口气生成多个目标的目的。
Makefile规则中的目标可以不止一个,其支持多目标,当Makefile中的多个目标同时依赖于同一个文件,并且其生成的命令大体类似,就能把它们合并起来。
当然,多个目标的生成规则的执行命令是同一个,这可能会给我们带来麻烦,不过好在我们可以使用一个自动化变量$@
,这个变量意味着目前规则中所有的目标的集合。
同时定义多个目标
bigoutput littleoutput: text.g
generate text.g-$(subst output,,$@)>;$@
等价于:
bigoutput: text.g generate text.g-big>;bigoutputlittleoutput: text.g generate text.g-little>; littleoutput
其中,$(subst output,,$@)
表示执行一个Makefile的函数,函数名为subst,后面的为参数。 subst函数是替换字符串的意思,$@
表示目标的集合,就像一个数组,$@
依次取出目标,并执行命令。
Makefile中,make会按顺序一条一条地执行命令,每条命令必须以"Tab"键开头,除非命令是紧跟在依赖规则后面的分号后的。在命令行之间的空格或者空行会被忽略,但是如果该空格或空行是以“Tab”键开头的,make便会认为其是一个空命令。
当依赖目标新于生成目标时,也就是当规则的最终目标需要被更新时,make会一条一条地执行其后的命令。如果要让上一条命令的结果应用在下一条命令,需要使用分号分隔这两条命令。
比如第一条命令是cd命令,希望第二条命令在cd之后的基础上运行,那么就不能把这两条命令写在两行上,而应该把这两条命令写在一行上,并用分号分隔比如下面这段代码:
exec: cd /home/zhangfan #进入/home/zhangfan目录 pwd #打印当前目录
执行“make exec”命令后;会进入/home/zhangfan目录,但是pwd命令打印出的结果仍是当前的 Makefile目录,如果改写成下面这个样子:
exec: cd /home/zhangfan; pwd#进入/home/zhangfan目录后,打印当前目录
则打印/home/zhangfan
Makefile的变量就像是CC++语言中的宏,代表了一个文本字串,在 Makefile执行的时候会自动地展开在所使用的地方。与C/C++的宏所不同的是, Makefile变量的值是可以改变的。在 Makefile中,变量可以使用在目标、依赖目标、命令或是 Makefile的其他部分中。
变量的命名字可以包含字符、数字,下划线(可以是数字开头),但不应该含有“:”、“#”、“=”或是空字符(空格、回车等)。变量是大小写敏感的,“foo”、“Foo”和“FOO”是三个不同的变量名。
变量在声明时需要给予初值,而在使用时,需要在变量名前加上$
符号,最好用小括号“()”或是大括号“{}”把变量给包括起来。如果要使用真实的$
字符,那么需要用$$
来表示。
Makefile中使用变量
objects= program.o foo.o utils.oprogram: $(objects) cc -o program $(objects) $(objects): defs.h
变量会在使用它的地方精确地展开,就像C/C++中的宏一样:
objects= program.o foo.o utils.oprogram:program. o foo.o utils.o cc-o programprogram.o foo.o utils.o program.o foo.o utils.o: defs.h
除了所示的代码那样,使用=符号给变量赋值外, Makefile还支持变量的嵌套定义,例如:
foo =$(bar) bar=$(ugh) ugh=Huh? all: echo $(foo)
执行make all将会输出变量foo的值是“Huh?”
x:= fooy:=$(x) barx:=later#其等价于:y:= foo barx:= later
使用的是“:=”操作符,使得前面的变量不能使用后面的变量,只能使用前面已定义好了的变量。
比如如果是这样:
y:=$(x) barx:=foo
那么,y的值是bar,而不是foo bar。
FOO?=bar
其含义是,如果变量FOO没有被定义过,那么变量FOO的值就是“bar”,如果FOO先前被定义过,那么这条语句将什么也不做,其等价于:
ifeq ($(origin FOO), undefined) FOO=barendif
这段代码中使用了ifeq条件判断语句和函数调用origin函数返回变量FOO的出处,ifeq则判断两个参数是否相等。
给变量追加值
objects= main. o foo. o bar. o utils.o objects+= another.o
于是,$( objects)值变成:“main.o foo. o bar. o utils.o another.o” ( another.
o被追加进去了),使用“=”操作可以等效为下面的形式
objects=main.o foo.o bar.o utils.o objects:=$(objects) another.o
所不同的是,用“+=”更为简洁。
如果变量之前没有定义过,那么,“+=”会自动变成“=”,如果前面有变量定义,那么“+=”会继承于前次操作的赋值符。如果前一次的是“:=”,那么“+=”会以“:=”作为其赋值符,如
variable:= value variable += more #等价于variable:=value variable: = $(variable) more
Makefile可以调用函数,从而让Makefie的书写变得更为灵活和简
。两数调用后,返回值可以当做变量来使用,并且函数的调用
也很像变量的使用,也是以“$”来标识的,其语法如下:
$( )或是:${ }
是函数名,
是两数的参数,参数问以逗号隔开,而两数名和参数之问以空格分隔。两数调用以“$”开头,
以圆括号或花括号把函数名和参数括起。
函数中的参数可以使用变量,为了风格的统一,函数和变量括号最好一样.
字符串替换函数 subst
格式: $(subst
功能:把字串
中的
字符串替换成
。
返回:函数返回被替换过后的字符串。
模式字符串替换函数 patsubst
格式: $(patsubst
功能:找
中的単词(単词以“空格”、“Tab”、“回车”或换行”分隔)是否符合模式
,如果匹配的话,则以
模式替换。这里,< pattern>
可以包括通配符%来表示任意长度的字串。如果
中也包含%”,那么,
中的这个“%”将是
的那个“%”所代表的字串。(可以用""来转义,以%来表示真实含义的“%”字符)
返回:函数返回被替换过后的字符串。
例:
$(patsubst %.C, %.o, x.c.c bar.c)
把字串“x.c.c bar.c”符合模式“%.c“的单词替换成 “%.o”,函数返回结果是
x.c.o bar. o
查找字符串函数findstring
格式:$(findstring
功能:在字符串
中查找
字符串。
返回:如果找到,则返回
字符串,否则返回空字符串。
示例:
$(findstring a,a b c)$(findstring a,b c)
第一个两数返回“a”字符串,第二个返回””字符串(空字符串)。
过滤函数filter
格式:$(filter
功能:
以
模式过滤
的单词。
可以有多个模式。
返回:返回符合模式
的字符串。
示例:
sources:=foo.c bar.c baz.s ugh.hfoo: $(sources) cc $(filter %.c %.s,$(sources)) -o foo
$(filter %.c %.s,$(sources))
返回的值是"Foo.c bar.c baz.s"
反过滤函数filter-out
格式:$filter-out
功能:以
模式过滤
字符串中的单词,去除符合模式
的单词。可以有多个模式。
返回:返回不符合模式
的字串。
示例:
objects=main1.o foo.o main2.o bar.o mains=main1.o main2.o$(filter-out $(mains),$(objects))
返回值是“foo.o bar.o”。
排序函数sort
格式:$(sort
)
功能:将字符串
中的单词按照字母排序(升序)进行排序,先比较单词的首字母,首字母相同,则比较下一个字母,以此类推。当遇到相同的单词时,sort函数会去自动删掉它们。
返回:返回排序后的字符串。
示例一:
$(sort lose foo bar lost)#返回值为:bar foo lose lost
示例二:
$(sort programing linux c programing)#返回值为:c linux programing
取单词函数word
格式:$(word
功能:从字符串
中取出第
个单词,单词计数从1开始。
返回:返回字符串
中的第
个单词。如果
值比
中的单词数要大,则返回空字符串
#示例一:$(word 2,programing linux c programing)#返回值是:linux#示例二:$(word 5,programing linux c programing)#返回值为“”(空字符串)。
取单词串函数wordlist
格式:$(wordlist
,
功能:从字符串
中取出从
开始到
的单词串,
和
是一个数字。
返回:返回字符串
中从
到
的单词字串。如果
比
中的单词数要大,那么返回空字符串。如果
大于
的单词数,那么返回从
开始,到
结束的单词串。
单词计数从1开始。
#示例一:$(wordlist 2,4,I like linux c programing)#返回值是:like linuxc#示例二:$(wordlist 6,8,I like linux c programing)#返回值为“”(空字符串)。#示例三:$(wordlist 2,8,I like linux c programing)#返回值是:like linux c programing
单词个数统计函数words
格式:$(words
功能:统计
字符串中的单词个数,计数从1开始。
返回:返回
中的单词数。
示例:
$(words,I like linux c programing)#返回值是“5”。
首单词函数firstword
格式:$(firstword
功能:取字符串
中的第一个单词。
返回:返回字符串
的第一个单词。
示例:
$(firstword I like linux c programing)#返回值是单词“I”。
取目录函数dir
格式:$(dir
功能:从文件名序列(一个或多个文件名)
中取出目录部分。目录部分是指最后一个反斜杠“/”之前的部分,如果没有反斜杠,则返回“./”。
返回:返回文件名序列
的目录部分。
示例:
$(dir usr/src/linux-2.4/Makefile hello.c)#返回值是“usr/src/linux-2.4 ./”。
取文件名函数notdir
格式:$(notdir
功能:从文件名序列(一个或多个文件名)
中取出非目录部分。非目录部分是指最后一个反斜杠"/”之后的部分,即文件名。
返回:返回文件名序列
的非目录部分。
示例:
$(notdir usr/src/linux-2.4/Makefile hello.c)#返回值是“Makefile hello.c”。
取后缀名函数sufix
格式:$(suffix
功能:从文件名序列
中取出各个文件名的后缀名。
返回:返回文件名序列
的后缀名序列,如果文件没有后缀名,则返回空字符串。
示例:
$(suffix usr/src/linux-2.4/Makefile hello.c foo.s)#返回值是“.c .s”(第一个为空字符)。
名称取前缀函数basename
格式:$(basename
功能:从文件名序列
中取出各个文件名的前缀部分。
返回:返回文件名序列
的前缀序列,如果文件没有前缀,则返回空字符串。
示例:
$(basename usr/src/linux-2.4/kernel/exit.c hello.o home/hacks)#返回值是“usr/src/linux-2.4/kernel/exit hello home/hacks"。
加后缀函数addsuffix
格式:$(addsuffix
功能:把后缀
加到
中的每个单词后面。
返回:返回已加后缀的文件名序列。
示例:
$(addsuffix .c,foo bar hello)#返回值是“foo.c bar.c hello.c”。
加前缀函数addprefix
格式:$(addprefix
功能:把前缀
加到
中的每个单词前面。
返回:返回加过前缀的文件名序列。
示例:
$(addprefix usr/src/linux-2.4/kernel/,exit.c time.c)#返回值是“usr/src/linux-2.4/kernel/exit.c usr/src/linux-2.4/kernel/time.c”。
foreach函数是用来做循环用的,Makefile中的foreach函数几乎是仿照Unix标准Shell(/bin/sh)中的for语句,或是C-Shell(/bin/csh)中的foreach语句而构建的。它的语法是:
$(foreach ,,)
这个函数的意思是,把参数
中的单词逐一取出放到参数所指定的变量中,然后再执行
所包含的表达式。每一次
会返回一个字符串,循环过程中,
所返回的每个字符串会以空格分隔,最后当整个循环结束时,
所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。所以,最好是一个变量名,
可以是一个表达式,而
中一般会使用这个参数来依次枚举
中的单词。
如:
names:=a b c d files:=$(foreach n,$(names),$(n).o)
上面的例子中,$(name)
中的单词会被挨个取出,并存到变量n
中,$(n).o
每次根据$(n)
计算出一个值,这些值以空格分隔,最后作为foreach函数的返回值,所以,$(files)
的值是“a.o b.o c.o d.o”。
下面要介绍的if函数很像make所支持的条件判断语句,if函数的语法是:
$(if ,)#或是:$(if ,,)
可见,if函数可以包含else部分,也可以不含。即if函数的参数可以是两个,也可以是三个。
参数是if的表达式,如果其返回的为非空字符串,那么这个表达式就相当于返回真,于是,
会被计算,否则
会被计算。
而if函数的返回值是,如果
为真(非空字符串),那么
会是整个函数的返回值,如果
为假(空字符串),那么
会是整个函数的返回值,此时如果
没有被定义,整个函数返回空字符串。所以,
和
只会有一个被执行。
关键字 | 典型表达式 | 含义 |
---|---|---|
ifeq | ifeq( |
比较参数arg1和arg2的值是否相等,相等时表达式为真。 |
ifneq | ifneq( |
比较参数arg1和arg2的值是否相等,不相等时表达式为真 |
ifdef | ifdef |
如果变量 的值非空,则表达式为真。 可以是一个函数的返回值。ifdef 只是测试一个变量是否有值,其并不会把变量扩展到当前位置。 |
ifndef | ifndef |
如果变量 的值为空,则表达式为真。 |
隐式规则即Makefile预先约定好了的,不用再写出来的规则。比如把.c文件编译成.o文件这一规则,不用写出来,make便会自动推导,并生成我们需要的.o文件。隐式规则会使用一些系统变量,可以改变这些系统变量的值来定制隐式规则运行时的参数。如系统变量“CFLAGS”可以控制编译时的编译器参数。还可以通过模式规则的方式自定义隐式规则。
如果要使用隐式规则来生成目标,就不需耍写出这个目标的规则,make会试图去自动推导产生这个目标的规则和命令,如果make可以自动推导生成这个目标的规则和命令,那么这个行为就是隐式规则的自动推导。当然,隐式规则是make事先约定好的一些东西。例如,有下面的Makefile文件:
foo:foo.o bar.o
cc -o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)
可以看到,这个Makefile中并没有写下如何生成foo.o和bar.o这两目标的规则和命令。因为make的隐式规则功能会自动去推导这两个目标的依赖目标和生成命令。也就是说,完全没有必要写出下面的两条规则:
foo.o:foo.c
cc -c foo.c $(CFLAGS)
bar.o:bar.c
cc -c bar.c $(CFLAGS)
因为这已经是“约定”好了的事,make使用约定好的C编译器"cc"生成.o文件,这就是隐式规则。
在隐式规则的命令中,基本上都是使用了一些预先设置的变量。可以在Makefile中改变这些变量的值,或是在make的命令行中传入这此值,或是在环境变量中设置这些值,无论怎么样,只要设置了这此特定的变量,那么它们就会对隐式规则起作用。当然,也可以利用make的“-R”或“–no-builtin-variables”参数来取消所定义的变量对隐式规则的作用。
例如,第一条隐式规则——编译C程序的隐式规则的命令是
$(CC) -c $(CFLAGS) $(CPPFLAGS)
Make默认的编译命令是“cc”,如果你把变量$(CC)
重定义成“gcc”,把变量$(CFLAGS)
重定义成“-g”,那么,隐式规则中的命令全部会以gcc-c-g $(CPPFLAGS)
的样子来执行了。
-g可执行程序包含调试信息
-o指定输出文件名
-c只编译不链接
可以把隐式规则中使用的变量分成两种:一种是命令相关的,如
“CC”;一种是参数相关的,如“CFLAGS”。
变量 | 含义 |
---|---|
AR | 函数库打包程序,默认命令是“ar" |
AS | 汇编语言编译程序,默认命令是“as” |
CC | C语言编译程序。默认命令是“cc” |
CXX | C++语言编译程序。默认命令是“g++” |
CO | 从RCS文件中扩展文件程序。默认命令是“co” |
CPP | c程序的预处理器(输出是标准输出设备)。默认命令是$(CC) -E |
FC | Fortran和Ratfor的编译器和预处理程序。默认命令是“f77” |
GET | 从SCCS文件中扩展文件的程序。默认命令是“get” |
LEX | Lex方法分析器程序(针对于C或Ratfor)。默认命令是“lex” |
PC | Pascal语言编译程序。默认命令是“pc” |
YACC | Yacc文法分析器(针对于C程序)。默认命令是“yacc” |
YACCR | Yacc文法分析器(针对于Ratfor程序)。默认命令是“yacc -r” |
MAKEINFO | 转换Texinfo源文件(.texi)到Info文件程序。默认命令是“makeinfo” |
TEXI2DVI | 从Texinfo 源文件创建TeXDVI文件的程序。默认命令是“texi2dvi” |
TANGLE | 转换Web到Pascal语言的程序。默认命令是“tangle” |
CTANGLE | 转换C Web到C。默认命令是“ctangle” |
RM | 删除文件命令。默认命令是“rm -f” |
可以使用模式规则来定义一个隐式规则。模式规则与一般的规则类似,只是在模式规则中,目标的定义需要有“%”字符。“%”定义对文件名的匹配,表示任意长度的非空字符串。在依赖目标中同样可以使用“%”,只是依赖目标中“%”的取值,取决于其目标。
模式规则中,至少在规则的目标中要包含“%”符号。例如:“%.C"表示以.c结尾的文件名(文件名的长度至少为3),“s.%.C”表示以s.开头,以.c结尾的文件名(文件名的长度至少为5)。如果“%”定义在目标中,那么目标中
“%”的值决定了依赖目标中的”%”的值,也就是说,目标中的模式的”%”决定了依赖目标中”%”的样子。例如有一个模式规则如下:
%.o:%.c;
其含义是,指出了从所有的.c文件生成相应的.o文件的规则。如果要生成的目标是“a.ob.o”,那么“%c”就是“a.c b.c”。
一旦依赖目标中的“%”模式被确定,make便会被要求去匹配当前目录下所有的文件名,一旦找到,make就会执行规则下的命令,所以在模式规则中,目标可能会是多个的,如果有模式匹配出多个目标,make就会产生所有的模式目标,此时,make关心的是依赖的文件名和生成目标的命令这两件事。
在上述的模式规则中,目标和依赖文件都是一系例的文件,那么如何书写一个命令来完成从不同的依赖文件生成相应的目标?因为在每一次对模式规则的解析时,都会是不同的目标和依赖文件。自动化变量就是完成这个功能的。
所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地逐个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。
Makefile中共有21个自动化变量,后面的14个变量只是在前面7个变量后分别加上了字母"D"和"F",字母"D"代表Directory,就是目录,“F"代表File,就是文件。
另外,对于变量$<$
,为了避免产生不必要的麻烦,最好在后面的那个特定字符加上圆括号,比如$(<)
”就要比$<
要好一些。
变量 | 含义 |
---|---|
$@ |
表示规则中的所有目标文件的集合。在模式规则中如果有多个目标,$@ 就是匹配于目标中模式定义的集合。 |
$% |
表示规则中的目标文件中的函数库名,如果目标不是函数库文件(Unix下是a,Windows下是lib),其值为空。 |
$< |
依赖中的第一个依赖文件的名字,如果依赖是以模式(即“%”)定义的,则$< 是符合模式的一系列的文件集。 |
$? |
所有比目标新的依赖的集合,以空格分隔。 |
$^ |
所有的依赖的集合,以空格分隔。如果在依赖中有多个重复的,则自动去除重复的依赖,只保留一份。 |
$+ |
同$^ ,也是所有依赖的集合,只是它不去除重复的依赖 |
$* |
目标模式中“%”及其之前的部分。 |
$(@D) |
$@ 的目录部分(不以斜杠作为结尾),如果$@ 中没有包含斜杠,其值为. (当前目录)。 |
$(@F) |
$@ 的文件部分,相当干函数$(notdir $@) 。 |
$(*D) |
取$* 文件的目录部分。 |
$*F |
取$* 文件部分,但不取后缀名。 |
$(%D) |
函数库文件成员的目录部分。 |
$(%F) |
函数库文件成员的文件名部分。 |
$( |
依赖中的第一个目标的目录部分。 |
$( |
依赖中的第一个目标的文件名部分。 |
$(^D) |
所有依赖文件中的目录部分(无相同的) |
$(^F) |
所有依赖文件中的文件名部分(无相同的) |
$(+D) |
所有依赖文件中的目录部分(可以有相同的) |
$(+F) |
所有依赖文件中的文件名部分(可以有相同的) |
$(?D) |
所有被更新文件的目录部分 |
$(?F) |
所有被更新文件的文件名部分 |