makefile语法注意点
1.makefile基础规则
格式如下:
**target ... : prerequisites ...
command
...**
target:可以是一个object目标文件,也可以是一个执行文件,还可以是一个标签
prerequisites:生成该target所依赖的文件或target
command:该target要执行的命令
重点:prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。
2.make是如何工作的?
(1)一个示例
edit : main.o kbd.o command.o display.o insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o
(2)make的工作方式
在默认方式,我们只输入一个make命令。
(1)make会在当前目录下找名字Makefile或者makefile的文件。
(2)如果找到,它会找文件中第一个目标文件,在上面的例子中,他会找到edit这个文件,并把这个文件作为最终的目标文件。
(3)如果edit文件不存在,或是edit所依赖的后面的.o文件的文件修改时间要比edit这个文件新,那么他就会执行后面所定义的命令来生成edit这个文件。
(4)如果edit所依赖的.o文件也不存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。
(5)当然,你的C文件和H文件是存在的,于是make会生成.o文件,然后再用.o文件生成make的终极任务,也就是执行文件edit了。
这就是整个make的依赖性,make会一层一层地去找文件的依赖关系,知道最终编译出第一个目标文件。在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。make只管文件的依赖性,即,如果在我找了依赖关系以后,冒号后面的文件还是不在,那么停止make工作。
通过上述分析,我们知道,像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过我们可以显示要make执行,即命令-make clean,以此来清楚所有目标文件。
3.makefile中的变量
声明变量
objects = main.o kbd.o command.o display.o insert.o search.o files.o utils.o
使用变量:通过$(objects)取得变量的内容,如果不加$,那么objects就仅仅只是objects本身。
objects = main.o kbd.o command.o display.o insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit $(objects)
5.make的自动推导
只要make看到一个.o文件,它就会自动把.c文件加在依赖关系中,如果make找到一个wahtever.o,那么whatever.c就会使whatever.o的依赖文件。并且cc -c whatever.c也会被推导出来,于是我们的makefile不用写得那么复杂,我们的新的makefile:
objects = main.o kbd.o command.o display.o insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h
.PHONY : clean
clean :
rm edit $(objects)
6.makefile包含的内容
(1)显示规则。显示规则说明了如何生成一个或多个目标文件,这是由make的书写者明显指出要生成的文件
(2)隐晦规则。由于我们的make有自动推导功能,所以隐晦的规则可以让我们比较简略地书写makefile,这是由于make所支持的
(3)变量的定义
(4)文件指示。例如引用另一个makefile,指定makefile有效部分,定义一个多行命令
(5)注释
7.makefile的文件名
默认的情况下,make命令会在当前目录下按顺序寻找文件名为"GNUmakefile",“makefile”,“Makefile”的文件,找到了解释这个文件。
如果要指定特定的Makefile,可以使用make -f 和–file参数,
如make -f Make.Linux 或者make --file Maxe.AIX
8.引用其他Makefile
include<filename>
filename可以是当前操作系统shell的文件模式。
在include前面可以有一些空字符,但是不能是Tab。
示例:
include foo.make a.mk b.mk c.mk e.mk f.mk
make命令开始时,会找寻include所指出的其他Makefile,并把内容安置在当前的位置。如果文件没有指定绝对路径或者相对路径,make会在当前目录首先寻找,如果当前目录下没有找到,那么,make还会在下面几个目录下找
(1)make执行时,有-I或者–include-dir参数,那么make就会在这个参数所指定的目录下去寻找
(2)如果目录<prefix>/include存在的话(一般是/usr/local/bin或者/usr/include),make也会去找.
9.环境变量MAKEFILES
如果你的当前环境中定义了环境变量MAKEFILES ,那么,make 会把这个变量中的值做一个类似于
include 的动作。这个变量中的值是其它的Makefile,用空格分隔。只是,它和include 不同的是,从
这个环境变量中引入的Makefile 的“目标”不会起作用,如果环境变量中定义的文件发现错误,make
也会不理。
但是在这里我还是建议不要使用这个环境变量,因为只要这个变量一被定义,那么当你使用make
时,所有的Makefile 都会受到它的影响,这绝不是你想看到的。在这里提这个事,只是为了告诉大家,
也许有时候你的Makefile 出现了怪事,那么你可以看看当前环境中有没有定义这个变量。
10.在make规则中使用通配符
make支持三个通配符* ,?,~。
波浪号~字符在文件名中有比较特殊的用途。如果是~ /test,这就表示当前用户$HOME目录下的test目录。而~hchen/test则表示用户hchen的宿主目录下的test目录。*.c表示所有后缀为c的文件,$?是一个自动化变量。
注意点:
通配符就是通配符,不会自行展开,例如objects=*.o,objects的值就是*.o。如果要展开通配符指代的变量值,也就是objects所指的文件名集合,可以这样
(1)列出以确定的文件夹中的所有.c文件
objects := $(wildcard *.o)
(2)列出(1)中的文件对应的.o文件
$(patsubst %.c,%.o,$(wildcard *.c))
11.文件搜寻
如果没有指明变量VPATH,那么make只会在当前的目录中去找寻依赖文件和目标文件。如果指明了VPATH,make就会在当前目录找不到的情况下,到VPATH所指定的目录中去寻找文件了。
VPATH=src:../headers
12.伪目标
最早先的一个例子中,我们提到过一个“clean”的目标,这是一个“伪目标”,
clean:
rm *.o temp
正像我们前面例子中的“clean”一样,既然我们生成了许多文件编译文件,我们也应该提供一个清除它们的“目标”以备完整地重编译而用。(以“make clean”来使用该目标)。因为,我们并不生成“clean”这个文件。“伪目标”并不是一个文件,只是一个标签,由于“伪目标”不是文件,所以make 无法生成它的依赖关系和决定它是否要执行。我们只有通过显式地指明这个“目标”才能让其生效。当然,“伪目标”的取名不能和文件名重名,不然其就失去了“伪目标”的意义了。当然,为了避免和文件重名的这种情况,我们可以使用一个特殊的标记“.PHONY”来显式地指明一个目标是“伪目标”,向make 说明,不管是否有这个文件,这个目标就是“伪目标”。
.PHONY : clean
只要有这个声明,不管是否有“clean”文件,要运行“clean”这个目标,只有“make clean”这样。于是整个过程可以这样写:
.PHONY : clean
clean :
rm *.o temp
伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为“默认目标”,只要将其放在第一个。一个示例就是,如果你的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 prog3.o sort.o utils.o
我们知道,Makefile 中的第一个目标会被作为其默认目标。我们声明了一个“all”的伪目标,其依赖于其它三个目标。由于默认目标的特性是,总是被执行的,但由于“all”又是一个伪目标,伪目标只是一个标签不会生成文件,所以不会有“all”文件产生。于是,其它三个目标的规则总是会被决议。也就达到了我们一口气生成多个目标的目的。.PHONY : all 声明了“all”这个目标为“伪目标”。(注:这里的显式“.PHONY : all”不写的话一般情况也可以正确的执行,这样make 可通过隐式规则推导出,“all”是一个伪目标,执行make 不会生成“all”文件,而执行后面的多个目标。建议:显式写出是一个好习惯。)
随便提一句,从上面的例子我们可以看出,目标也可以成为依赖。所以,伪目标同样也可成为依赖。
看下面的例子:
.PHONY : cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff
rm program
cleanobj :
rm *.o
cleandiff :
rm *.diff
“make cleanall”将清除所有要被清除的文件。“cleanobj”和“cleandiff”这两个伪目标有点像
“子程序”的意思。我们可以输入“make cleanall”和“make cleanobj”和“make cleandiff”命令
来达到清除不同种类文件的目的。
13.多目标
Makefile的规则中的目标可以不止一个,其支持多目标,有可能我们的多个目标同时依赖于一个文件,并且其生成大体类似。于是我们就能把其合并起来。当然,多个目标的生成规则的执行命令不是同一个,这可能会给我们带来麻烦,不过好在我们可以使用一个自动化变量$@,这个变量表示目前规则中所有目标的集合。
等价的两种情况
(1)
bigoutput littleoutput:text.g
generate test.g -$(subst, output, $@) > $@
(2)
bigoutput : test.g
generate text.g -big > bigoutput
littleoutput : test.g
generate text.g -little > littleoutput
14.静态模式
静态模式可以更加容易地定义多目标的规则,可以让我们的规则变得更加的有弹性和灵活。我们还
是先来看一下语法:
<targets ...> : <target-pattern> : <prereq-patterns ...>
<commands>
. ..
targets 定义了一系列的目标文件,可以有通配符。是目标的一个集合。
target-parrtern 是指明了targets 的模式,也就是的目标集模式。
prereq-parrterns 是目标的依赖模式,它对target-parrtern 形成的模式再进行一次依赖目标
的定义。
15依赖性输出
查看main.c文件对应的依赖关系
gcc -MM main.c
结果示例:
main.o:main.c head.h
15.显示命令
(1)make默认会把执行的命令行在命令执行前输出到屏幕上
(2)命令行前加@,则不显示
(3)echo命令,主动显示自己想显示的信息
(4)make执行时,带入make参数-n或者–just-print,那么只是显示命令,但不会执行命令
16.执行命令
当依赖目标新于目标时,也就是当规则的目标需要被更新时,make 会一条一条的执行其后的命令。
需要注意的是,如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令。比
如你的第一条命令是cd 命令,你希望第二条命令得在cd 之后的基础上运行,那么你就不能把这两条
命令写在两行上,而应该把这两条命令写在一行上,用分号分隔。如:
• 示例一:
exec:
cd /home/hchen
pwd
• 示例二:
exec:
cd /home/hchen; pwd
当我们执行make exec 时,第一个例子中的cd 没有作用,pwd 会打印出当前的Makefile 目录,
而第二个例子中,cd 就起作用了,pwd 会打印出“/home/hchen”。
17.命令出错
我们可以在Makefile 的命令行前加一个减号- (在Tab 键
之后),标记为不管命令出不出错都认为是成功的。如:
clean:
-rm -f *.o
18.嵌套执行make
在一些大工程,我们会把不同模块或是不同功能的源文件放在不同的目录中,我们可以在每个目录中都书写一个该目录的Makefile,这有利于让我们的Makefile变得更加简洁,而不至于把所有的东西全部写在一个Makefile中,这样会很难维护我们的Makefile,这个技术对于我们模块编译和分段编译有着非常大的好处。
例如,我们有一个子目录叫subdir,这个目录下有个Makefile 文件,来指明了这个目录下文件的
编译规则。那么我们总控的Makefile 可以这样书写:
subsystem:
cd subdir && $(MAKE)
其等价于:
subsystem:
$(MAKE) -C subdir
定义$(MAKE) 宏变量的意思是,也许我们的make 需要一些参数,所以定义成一个变量比较利于
维护。这两个例子的意思都是先进入“subdir”目录,然后执行make 命令。
我们把这个Makefile叫做“总控Makefile”,总控Makefile的变量可以传递到下级的Makefile中,但是不会覆盖下层的Makefile中所定义的变量,除非指定了-e参数。
如果要传递变量到下级Makefile中,那么可以使用这样的声明:
export \<variable ...>
如果不想让某些变量传递到下级Makefile中,那么可以这样声明:
unexport \<variable ... >
例如:
export variable = value
等价于
variable = value
export variable
等价于
export variable := value
如果要传递所有变量,那么只要一个export就行了,后面什么也不同跟,表示传递所有变量。
注意点:
(1)有两个变量,一个是SHELL,一个是MAKEFLAGS,这两个变量不管是否export,总是传递到下层Makefile中,特别是MAKEFLAGS,其中包含了make的参数信息,如果我们执行"总控Makefile"时有make参数或是在上层Makefile中定义了这个变量,那么MAKEFLAGS变量将会是这些参数,并会传递到下层Makefile中,这是一个系统级别的环境变量。
19.定义命令包
如果Makefile中出现一些相同命令序列,那么我们可以为这些相同的命令序列定义一个变量。定义这种命令序列的语法以define开始,以endef结束,如:
define run-yacc
yacc & (firstword $^)
mv y.tab.c $@
endef
这里run-yacc是这个命令包的名字,其不要和Makefile中的变量重名。在define和endef中的两行就是命令序列。这个命令包中的第一个命令是运行yacc程序,因为yacc程序总是生成y.tab.c文件,所以第二行的命令就是把这个文件改改名字。
使用示例:
foo.c : foo.y
$(run-yacc)
20.变量
(1)使用时,在变量名前加上$符号,最好用小括号包起来
(2)使用真实的$时,用$$表示
(3)变量的定义
1.简单使用=号,在=左侧是变量,右侧是变量的值,右侧变量的值可以定义在文件的任何一处,也就是说,右侧的变量不一定非要是已经定义好的值,也可以使用后面定义的值
2.使用 := 号,使用这个号,定义时,只能使用前面的已经定义好的变量
3.使用?=号,表示如果该变量没有被定义过,那么直接赋值这个变量,如果之前已经定义过,那么这条语句什么也不做。
4.使用+=号,表示给变量追加值,相当于字符串拼凑
(4)定义一个变量,其值是一个空格:
nullstring :=
space := $(nullstring) #
nullstring 是一个Empty 变量,其中什么也没有,而我们的space 的值是一个空格。因为在操作符的右边是很难描述一个空格的,这里采用的技术很管用,先用一个Empty 变量来标明变量的值开始了,而后面采用“#”注释符来表示变量定义的终止,这样,我们可以定义出其值是一个空格的变量。请注意这里关于“#”的使用,注释符“#”的这种特性值得我们注意
(5)变量高级用法
这里介绍两种变量的高级使用方法,第一种是变量值的替换。我们可以替换变量中的共有的部分,其格式是
(
v
a
r
:
a
=
b
)
或是
(var:a=b) 或是
(var:a=b)或是{var:a=b} ,其意思是,把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字串。这里的“结尾”意思是“空格”或是“结束
符”。
还是看一个示例吧:
foo := a.o b.o c.o
bar := $(foo:.o=.c)
这个示例中,我们先定义了一个
(
f
o
o
)
变量,而第二行的意思是把
(foo) 变量,而第二行的意思是把
(foo)变量,而第二行的意思是把(foo) 中所有以.o 字串“结尾”全部替换成.c ,所以我们的$(bar) 的值就是“a.c b.c c.c”。
另外一种变量替换的技术是以“静态模式”(参见前面章节)定义的,如:
foo := a.o b.o c.o
bar := $(foo:%.o=%.c)
这依赖于被替换字串中的有相同的模式,模式中必须包含一个% 字符,这个例子同样让$(bar) 变量的值为“a.c b.c c.c”。
第二种高级用法是——“把变量的值再当成变量”。先看一个例子:
x = y
y = z
a := $($(x))
在这个例子中,$(x) 的值是“y”,所以$($(x)) 就是$(y),于是$(a) 的值就是“z”。(注意,是“x=y”,而不是“x=$(y)”)
(6)override指示符
如果有变量是通常make 的命令行参数设置的,那么Makefile 中对这个变量的赋值会被忽略。如果你想在Makefile 中设置这类参数的值,那么,你可以使用“override”指示符。其语法是:
override <variable>; = <value>;
override <variable>; := <value>;
当然,你还可以追加:
override <variable>; += <more text>;
(7)目标变量
可以为某个目标设置局部变量,这种变量被称为“Target-specific Variable”,它可以和“全局变量”同名,因为它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效。而不会影响规则链以外的全局变量的值。
其语法是:
<target ...> : <variable-assignment>;
<target ...> : overide <variable-assignment>
<variable-assignment>; 可以是前面讲过的各种赋值表达式,如= 、:= 、+= 或是
?= 。第二个语法是针对于make 命令行带入的变量,或是系统环境变量。这个特性非常的有用,当我们设置了这样一个变量,这个变量会作用到由这个目标所引发的所有的
规则中去。如:
prog : CFLAGS = -g
prog : prog.o foo.o bar.o
$(CC) $(CFLAGS) prog.o foo.o bar.o
prog.o : prog.c
$(CC) $(CFLAGS) prog.c
foo.o : foo.c
$(CC) $(CFLAGS) foo.c
bar.o : bar.c
$(CC) $(CFLAGS) bar.c
在这个示例中,不管全局的$(CFLAGS) 的值是什么,在prog 目标,以及其所引发的所有规则中
(prog.o foo.o bar.o 的规则),$(CFLAGS) 的值都是-g
(8)模式变量
在GNU 的make 中,还支持模式变量(Pattern-specific Variable),通过上面的目标变量中,我们知道,变量可以定义在某个目标上。模式变量的好处就是,我们可以给定一种“模式”,可以把变量定义在符合这种模式的所有目标上。
我们知道,make 的“模式”一般是至少含有一个% 的,所以,我们可以以如下方式给所有以.o 结尾的目标定义目标变量:
%.o : CFLAGS = -O
同样,模式变量的语法和“目标变量”一样:
<pattern ...>; : <variable-assignment>;
<pattern ...>; : override <variable-assignment>;
override 同样是针对于系统环境传入的变量,或是make 命令行指定的变量。
21.条件判断
(1)ifeq,ifneq:是否相等,是否不相等
ifeq (<arg1>, <arg2>)
ifeq '<arg1>' '<arg2>'
ifeq "<arg1>" "<arg2>"
ifeq "<arg1>" '<arg2>'
ifeq '<arg1>' "<arg2>"
ifneq (<arg1>, <arg2>)
ifneq '<arg1>' '<arg2>'
ifneq "<arg1>" "<arg2>"
ifneq "<arg1>" '<arg2>'
ifneq '<arg1>' "<arg2>"
(2)ifdef,ifndef:变量值是否非空,空
ifdef <variable-name>
ifndef <variable-name>
22.函数调用语法
(1)函数调用,很像变量的使用,也是以$来标识的
$(<function> <arguments>)
或者
${<function> <arguments>}
function就是函数名,make支持的函数不多。arguments为函数的参数,参数间以逗号","分隔,而函数名和参数之间用空格分隔。函数调用以$开头,以圆括号或者花括号把函数名和参数括起来。
(2)subst函数
$(subst <from>,<to>,<text>)
• 名称:字符串替换函数
• 功能:把字串
• 返回:函数返回被替换过后的字符串。
• 示例:
$(subst ee,EE,feet on the street)
把feet on the street 中的ee 替换成EE ,返回结果是fEEt on the strEEt 。
(3)patsubst函数
$(patsubst <pattern>,<replacement>,<text>)
• 名称:模式字符串替换函数。
• 功能:查找<text> 中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式<pattern> ,如果匹配的话,则以<replacement> 替换。这里,<pattern> 可以包括通配符% ,表示任意长度的字串。如果<replacement> 中也包含% ,那么,<replacement> 中的这个% 将是<pattern> 中的那个% 所代表的字串。(可以用\ 来转义,以% 来表示真实含义的% 字
符)
• 返回:函数返回被替换过后的字符串。
• 示例:
$(patsubst %.c,%.o,x.c.c bar.c)
把字串x.c.c bar.c 符合模式%.c 的单词替换成%.o ,返回结果是x.c.o bar.o
(4)strip
$(strip <string>)
• 名称:去空格函数。
• 功能:去掉 字串中开头和结尾的空字符。
• 返回:返回被去掉空格的字符串值。
(5)findstring
$(findstring <find>,<in>)
• 名称:查找字符串函数
• 功能:在字串 中查找 字串。
• 返回:如果找到,那么返回 ,否则返回空字符串。
• 示例:
$(findstring a,a b c)
$(findstring a,b c)
第一个函数返回a 字符串,第二个返回空字符串
(6)filter
$(filter <pattern...>,<text>)
• 名称:过滤函数
• 功能:以<pattern> 模式过滤<text> 字符串中的单词,保留符合模式<pattern> 的单词。可
以有多个模式。
• 返回:返回符合模式<pattern> 的字串。
• 示例:
sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
cc $(filter %.c %.s,$(sources)) -o foo
$(filter %.c %.s,$(sources)) 返回的值是foo.c bar.c baz.s 。
(7)filter-out
$(filter-out <pattern...>,<text>)
• 名称:反过滤函数
• 功能:以<pattern> 模式过滤<text> 字符串中的单词,去除符合模式<pattern> 的单词。可
以有多个模式。
• 返回:返回不符合模式<pattern> 的字串。
• 示例:
objects=main1.o foo.o main2.o bar.o
mains=main1.o main2.o
$(filter-out $(mains),$(objects)) 返回值是foo.o bar.o 。
(8)sort
$(sort <list>)
• 名称:排序函数
• 功能:给字符串 中的单词排序(升序)。
• 返回:返回排序后的字符串。
• 示例:$(sort foo bar lose) 返回bar foo lose 。
• 备注:sort 函数会去掉 中相同的单词。
(9)word
$(word <n>,<text>)
• 名称:取单词函数
• 功能:取字符串<text> 中第<n> 个单词。(从一开始)
• 返回:返回字符串<text> 中第<n> 个单词。如果<n> 比<text> 中的单词数要大,那么返回空字符串。
• 示例:$(word 2, foo bar baz) 返回值是bar 。
(10)worldlist
$(wordlist <ss>,<e>,<text>)
• 名称:取单词串函数
• 功能:从字符串<text> 中取从<ss> 开始到<e> 的单词串。<ss> 和<e> 是一个数字。
• 返回:返回字符串<text> 中从<ss> 到<e> 的单词字串。如果<ss> 比<text> 中的单词数要大,那么返回空字符串。如果<e> 大于<text> 的单词数,那么返回从<ss> 开始,到<text>结束的单词串。
• 示例:$(wordlist 2, 3, foo bar baz) 返回值是bar baz 。
(11)words
$(words <text>)
• 名称:单词个数统计函数
• 功能:统计<text> 中字符串中的单词个数。
• 返回:返回<text> 中的单词数。
• 示例:$(words, foo bar baz) 返回值是3 。
• 备注:如果我们要取<text> 中最后的一个单词,我们可以这样:$(word $(words <text>),<text>) 。
(12)firstword
$(firstword <text>)
• 名称:首单词函数——firstword。
• 功能:取字符串
• 返回:返回字符串
• 示例:$(firstword foo bar) 返回值是foo。
• 备注:这个函数可以用word 函数来实现:$(word 1,<text>) 。
以上,是所有的字符串操作函数,如果搭配混合使用,可以完成比较
23.文件名操作函数
(1)dir函数
$(dir <names...>)
• 名称:取目录函数——dir。
• 功能:从文件名序列<names> 中取出目录部分。目录部分是指最后一个反斜杠(/ )之前的部分。如果没有反斜杠,那么返回./ 。
• 返回:返回文件名序列<names> 的目录部分。
• 示例:$(dir src/foo.c hacks) 返回值是src/ ./ 。
(2)notdir
$(notdir <names...>)
• 名称:取文件函数——notdir。
• 功能:从文件名序列<names> 中取出非目录部分。非目录部分是指最後一个反斜杠(/ )之后的部分。
• 返回:返回文件名序列<names> 的非目录部分。
• 示例: $(notdir src/foo.c hacks) 返回值是foo.c hacks 。
(3)suffix
$(suffix <names...>)
• 名称:取後缀函数——suffix。
• 功能:从文件名序列<names> 中取出各个文件名的后缀。
• 返回:返回文件名序列<names> 的后缀序列,如果文件没有后缀,则返回空字串。
• 示例:$(suffix src/foo.c src-1.0/bar.c hacks) 返回值是.c .c。
(4)basename
$(basename <names...>)
• 名称:取前缀函数——basename。
• 功能:从文件名序列<names> 中取出各个文件名的前缀部分。
• 返回:返回文件名序列<names> 的前缀序列,如果文件没有前缀,则返回空字串。
• 示例:$(basename src/foo.c src-1.0/bar.c hacks) 返回值是src/foo src-1.0/bar hacks
(5)addsuffix
$(addsuffix <suffix>,<names...>)
• 名称:加后缀函数——addsuffix。
• 功能:把后缀<suffix> 加到<names> 中的每个单词后面。
• 返回:返回加过后缀的文件名序列。
• 示例:$(addsuffix .c,foo bar) 返回值是foo.c bar.c 。
(6)addprefix
$(addprefix <prefix>,<names...>)
• 名称:加前缀函数——addprefix。
• 功能:把前缀<prefix> 加到<names> 中的每个单词后面。
• 返回:返回加过前缀的文件名序列。
• 示例:$(addprefix src/,foo bar) 返回值是src/foo src/bar 。
(7)join
$(join <list1>,<list2>)
• 名称:连接函数——join。
• 功能:把<list2> 中的单词对应地加到<list1> 的单词后面。如果<list1> 的单词个数要比<list2> 的多,那么,<list1> 中的多出来的单词将保持原样。如果<list2> 的单词个数要比<list1> 多,那么,<list2> 多出来的单词将被复制到 中。
• 返回:返回连接过后的字符串。
• 示例:$(join aaa bbb , 111 222 333) 返回值是aaa111 bbb222 333 。
24控制函数
(1)foreach
$(foreach <var>,<list>,<text>)
这个函数的意思是,把参数<list> 中的单词逐一取出放到参数<var> 所指定的变量中,然后再执行<text> 所包含的表达式。每一次<text> 会返回一个字符串,循环过程中,<text> 的所返回的每个字符串会以空格分隔,最后当整个循环结束时,<text> 所返回的每个字符串所组成的整个字符串(以空格分隔)将会是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。注意,foreach 中的<var> 参数是一个临时的局部变量,foreach 函数执行完后,参数<var> 的变量将不在作用,其作用域只在foreach 函数当中。
(2)if函数
if 函数很像GNU 的make 所支持的条件语句——ifeq(参见前面所述的章节),if 函数的语法是:
$(if <condition>,<then-part>)
或是
$(if <condition>,<then-part>,<else-part>)
可见,if 函数可以包含“else”部分,或是不含。即if 函数的参数可以是两个,也可以是三个。<condition> 参数是if 的表达式,如果其返回的为非空字符串,那么这个表达式就相当于返回真,于是,<then-part> 会被计算,否则<else-part> 会被计算。而if 函数的返回值是,如果<condition> 为真(非空字符串),那个<then-part> 会是整个函数的返回值,如果<condition> 为假(空字符串),那么<else-part> 会是整个函数的返回值,此时如果<else-part> 没有被定义,那么,整个函数返回空字串。所以,<then-part> 和<else-part> 只会有一个被计算。
(3)call函数
call 函数是唯一一个可以用来创建新的参数化的函数。你可以写一个非常复杂的表达式,这个表达式中,你可以定义许多参数,然后你可以call 函数来向这个表达式传递参数。其语法是:
$(call <expression>,<parm1>,<parm2>,...,<parmn>)
当make 执行这个函数时,<expression> 参数中的变量,如$(1) 、$(2) 等,会被参数<parm1>、<parm2> 、<parm3> 依次取代。而<expression> 的返回值就是call 函数的返回值。例如:
reverse = $(1) $(2)
foo = $(call reverse,a,b)
那么,foo 的值就是a b 。当然,参数的次序是可以自定义的,不一定是顺序的,如:
reverse = $(2) $(1)
foo = $(call reverse,a,b)
此时的foo 的值就是b a 。
(4)origin函数
origin 函数不像其它的函数,他并不操作变量的值,他只是告诉你你的这个变量是哪里来的?其语法是:
$(origin <variable>)
注意,<variable> 是变量的名字,不应该是引用。所以你最好不要在<variable> 中使用$ 字符。
Origin 函数会以其返回值来告诉你这个变量的“出生情况”,下面,是origin 函数的返回值:
undefined: 如果<variable> 从来没有定义过,origin 函数返回这个值undefined
default: 如果<variable>是一个默认的定义,比如“CC”这个变量,这种变量我们将在后面讲述。
environment: 如果<variable>是一个环境变量,并且当Makefile 被执行时,-e 参数没有被打开。
file: 如果<variable> 这个变量被定义在Makefile 中。
command line: 如果<variable> 这个变量是被命令行定义的。
override: 如果<variable> 是被override 指示符重新定义的。
automatic: 如果<variable>是一个命令运行中的自动化变量。关于自动化变量将在后面讲述。
(5)shell函数
shell 函数也不像其它的函数。顾名思义,它的参数应该就是操作系统Shell 的命令。它和反引号“`”是相同的功能。这就是说,shell 函数把执行操作系统命令后的输出作为函数返回。于是,我们可以用操作系统命令以及字符串处理命令awk,sed 等等命令来生成一个变量,如:
contents := $(shell cat foo)
files := $(shell echo *.c)
注意,这个函数会新生成一个Shell 程序来执行命令,所以你要注意其运行性能,如果你的Makefile 中有一些比较复杂的规则,并大量使用了这个函数,那么对于你的系统性能是有害的。特别是Makefile 的隐晦的规则可能会让你的shell 函数执行的次数比你想像的多得多。
(6)控制make的函数
make 提供了一些函数来控制make 的运行。通常,你需要检测一些运行Makefile 时的运行时信息,并且根据这些信息来决定,你是让make 继续执行,还是停止。
$(error <text ...>)
产生一个致命的错误,<text …> 是错误信息。注意,error 函数不会在一被使用就会产生错误信息,所以如果你把其定义在某个变量中,并在后续的脚本中使用这个变量,那么也是可以的。例如:
示例一:
ifdef ERROR_001
$(error error is $(ERROR_001))
endif
示例二:
ERR = $(error found an error!)
.PHONY: err
err: $(ERR)
26.make的运行
(1)一般来说,最简单就是输入make命令,也可也make 目标
(2)书写规则的习惯
• all: 这个伪目标是所有目标的目标,其功能一般是编译所有的目标。
• clean: 这个伪目标功能是删除所有被make 创建的文件。
• install: 这个伪目标功能是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中 去。
• print: 这个伪目标的功能是例出改变过的源文件。
• tar: 这个伪目标功能是把源程序打包备份。也就是一个tar 文件。
• dist: 这个伪目标功能是创建一个压缩文件,一般是把tar 文件压成Z 文件。或是gz 文件。
• TAGS: 这个伪目标功能是更新所有的目标,以备完整地重编译使用。
• check 和test: 这两个伪目标一般用来测试makefile 的流程。
(3)检查规则
有时候,我们不想让我们的makefile 中的规则执行起来,我们只想检查一下我们的命令,或是执
行的序列。于是我们可以使用make 命令的下述参数:
-n, --just-print, --dry-run, --recon 不执行参数,这些参数只是打印命令,不管目标是否更新,把
规则和连带规则下的命令打印出来,但不执行,这些参数对于我们调试makefile 很有用处。
-t, --touch 这个参数的意思就是把目标文件的时间更新,但不更改目标文件。也就是说,make 假装编
译目标,但不是真正的编译目标,只是把目标变成已编译过的状态。
-q, --question 这个参数的行为是找目标的意思,也就是说,如果目标存在,那么其什么也不会输出,
当然也不会执行编译,如果目标不存在,其会打印出一条出错信息。
-W , --what-if=, --assume-new=, --new-file= 这个参数需要指定一
个文件。一般是是源文件(或依赖文件),Make 会根据规则推导来运行依赖于这个文件的命令,
一般来说,可以和“-n”参数一同使用,来查看这个依赖文件所发生的规则命令。
另外一个很有意思的用法是结合-p 和-v 来输出makefile 被执行时的信息(这个将在后面讲
述)。
(4)make的参数
下面列举了所有GNU make 3.80 版的参数定义。其它版本和产商的make 大同小异,不过其它产
商的make 的具体参数还是请参考各自的产品文档。
-b, -m 这两个参数的作用是忽略和其它版本make 的兼容性。
-B, --always-make 认为所有的目标都需要更新(重编译)。
-C <dir>, --directory=<dir> 指定读取makefile 的目录。如果有多个“-C”参数,make 的解释是
后面的路径以前面的作为相对路径,并以最后的目录作为被指定目录。如:“make -C ~hchen/test
-C prog”等价于“make -C ~hchen/test/prog”。
-debug[=<options>] 输出make 的调试信息。它有几种不同的级别可供选择,如果没有参数,那就是
输出最简单的调试信息。下面是<options> 的取值:
• a: 也就是all,输出所有的调试信息。(会非常的多)
• b: 也就是basic,只输出简单的调试信息。即输出不需要重编译的目标。
• v: 也就是verbose,在b 选项的级别之上。输出的信息包括哪个makefile 被解析,不需
要被重编译的依赖文件(或是依赖目标)等。
• i: 也就是implicit,输出所以的隐含规则。
• j: 也就是jobs,输出执行规则中命令的详细信息,如命令的PID、返回码等。
• m: 也就是makefile,输出make 读取makefile,更新makefile,执行makefile 的信息。
-d 相当于“–debug=a”。
-e, --environment-overrides 指明环境变量的值覆盖makefile 中定义的变量的值。
-f=<file>, --file=<file>, --makefile=<file> 指定需要执行的makefile。
-h, --help 显示帮助信息。
-i , --ignore-errors 在执行时忽略所有的错误。
-I <dir>, --include-dir=<dir> 指定一个被包含makefile 的搜索目标。可以使用多个“-I”参数
来指定多个目录。
-j [<jobsnum>], --jobs[=<jobsnum>] 指同时运行命令的个数。如果没有这个参数,make 运行命令
时能运行多少就运行多少。如果有一个以上的“-j”参数,那么仅最后一个“-j”才是有效的。(注
意这个参数在MS-DOS 中是无用的)
-k, --keep-going 出错也不停止运行。如果生成一个目标失败了,那么依赖于其上的目标就不会被执
行了。
-l <load>, --load-average[=<load>], -max-load[=<load>] 指定make 运行命令的负载。
-n, --just-print, --dry-run, --recon 仅输出执行过程中的命令序列,但并不执行。
-o <file>, --old-file=<file>, --assume-old=<file> 不重新生成的指定的<file>,即使这个目
标的依赖文件新于它。
-p, --print-data-base 输出makefile 中的所有数据,包括所有的规则和变量。这个参数会让一个
简单的makefile 都会输出一堆信息。如果你只是想输出信息而不想执行makefile,你可以使用
“make -qp”命令。如果你想查看执行makefile 前的预设变量和规则,你可以使用“make –p –f
/dev/null”。这个参数输出的信息会包含着你的makefile 文件的文件名和行号,所以,用这个
参数来调试你的makefile 会是很有用的,特别是当你的环境变量很复杂的时候。
-q, --question 不运行命令,也不输出。仅仅是检查所指定的目标是否需要更新。如果是0 则说明要
更新,如果是2 则说明有错误发生。
-r, --no-builtin-rules 禁止make 使用任何隐含规则。
-R, --no-builtin-variabes 禁止make 使用任何作用于变量上的隐含规则。
-s, --silent, --quiet 在命令运行时不输出命令的输出。
-S, --no-keep-going, --stop 取消“-k”选项的作用。因为有些时候,make 的选项是从环境变量
“MAKEFLAGS”中继承下来的。所以你可以在命令行中使用这个参数来让环境变量中的“-k”选项
失效。
-t, --touch 相当于UNIX 的touch 命令,只是把目标的修改日期变成最新的,也就是阻止生成目标
的命令运行。
-v, --version 输出make 程序的版本、版权等关于make 的信息。
-w, --print-directory 输出运行makefile 之前和之后的信息。这个参数对于跟踪嵌套式调用make
时很有用。
--no-print-directory 禁止“-w”选项。
-W <file>, --what-if=<file>, --new-file=<file>, --assume-file=<file> 假定目标<file>;
需要更新,如果和“-n”选项使用,那么这个参数会输出该目标更新时的运行动作。如果没有“-n”
那么就像运行UNIX 的“touch”命令一样,使得<file>; 的修改时间为当前时间。
--warn-undefined-variables 只要make 发现有未定义的变量,那么就输出警告信息。
27.自动化变量
在上述的模式规则中,目标和依赖文件都是一系例的文件,那么我们如何书写一个命令来完成从不同的依赖文件生成相应的目标?因为在每一次的对模式规则的解析时,都会是不同的目标和依赖文件。自动化变量就是完成这个功能的。在前面,我们已经对自动化变量有所提涉,相信你看到这里已对它有一个感性认识了。所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。下面是所有的自动化变量及其说明:
• $@ : 表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,$@ 就是匹配于目标中模
式定义的集合。
• $% : 仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是foo.a(bar.o)
,那么,$% 就是bar.o ,$@ 就是foo.a 。如果目标不是函数库文件(Unix 下是.a ,Windows
下是.lib ),那么,其值为空。
• $< : 依赖目标中的第一个目标名字。如果依赖目标是以模式(即% )定义的,那么$< 将是符合
模式的一系列的文件集。注意,其是一个一个取出来的。
• $? : 所有比目标新的依赖目标的集合。以空格分隔。
• $^ : 所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去
除重复的依赖目标,只保留一份。
• $+ : 这个变量很像$^ ,也是所有依赖目标的集合。只是它不去除重复的依赖目标。
• $* : 这个变量表示目标模式中% 及其之前的部分。如果目标是dir/a.foo.b ,并且目标的模式
是a.%.b ,那么,$* 的值就是dir/a.foo 。这个变量对于构造有关联的文件名是比较有较。如
果目标中没有模式的定义,那么$* 也就不能被推导出,但是,如果目标文件的后缀是make 所识
别的,那么$* 就是除了后缀的那一部分。例如:如果目标是foo.c ,因为.c 是make 所能识
别的后缀名,所以,$* 的值就是foo 。这个特性是GNU make 的,很有可能不兼容于其它版本的
make,所以,你应该尽量避免使用$* ,除非是在隐含规则或是静态模式中。如果目标中的后缀是
make 所不能识别的,那么$* 就是空值。
在上述所列出来的自动量变量中。四个变量($@ 、$< 、$% 、$* )在扩展时只会有一个文件,而另三个的值是一个文件列表。这七个自动化变量还可以取得文件的目录名或是在当前目录下的符合模式的文件名,只需要搭配上D 或F 字样。这是GNU make 中老版本的特性,在新版本中,我们使用函数dir 或notdir 就可以做到了。D 的含义就是Directory,就是目录,F 的含义就是File,就是文件。下面是对于上面的七个变量分别加上D 或是F 的含义:
$(@D) 表示$@ 的目录部分(不以斜杠作为结尾),如果$@ 值是dir/foo.o ,那么$(@D) 就是dir
,而如果$@ 中没有包含斜杠的话,其值就是. (当前目录)。
$(@F) 表示$@ 的文件部分,如果$@ 值是dir/foo.o ,那么$(@F) 就是foo.o ,$(@F) 相当于函
数$(notdir $@) 。
$(*D), $(*F) 和上面所述的同理,也是取文件的目录部分和文件部分。对于上面的那个例子,$(*D) 返
回dir ,而$(*F) 返回foo
$(%D), $(%F) 分别表示了函数包文件成员的目录部分和文件部分。这对于形同archive(member) 形
式的目标中的member 中包含了不同的目录很有用。
$(<D), $(<F) 分别表示依赖文件的目录部分和文件部分。
$(^D), $(^F) 分别表示所有依赖文件的目录部分和文件部分。(无相同的)
$(+D), $(+F) 分别表示所有依赖文件的目录部分和文件部分。(可以有相同的)
$(?D), $(?F) 分别表示被更新的依赖文件的目录部分和文件部分。
最后想提醒一下的是,对于$< ,为了避免产生不必要的麻烦,我们最好给$ 后面的那个特定字符都加上圆括号,比如,$(<) 就要比$< 要好一些。