??xml version="1.0" encoding="utf-8" standalone="yes"?>
———?/strong>
在Makefile中的定义的变量,像是C/C++语言中的宏一P他代表了一个文本字Ԍ 在Makefile中执行的时候其会自动原模原样地展开在所使用的地斏V其与C/C++所不同的是Q你可以在Makefile中改变其倹{在 Makefile中,变量可以使用?#8220;目标”Q?#8220;依赖目标”Q?#8220;命o”或是Makefile的其它部分中?/font>
变量的命名字可以包含字符、数字,下划U(可以是数字开_Q但不应该含?#8220;:”?#8220;#”? “=”或是I字W(I格、回车等Q。变量是大小写敏感的Q?#8220;foo”?#8220;Foo”?#8220;FOO”是三个不同的变量名。传l的Makefile的变量名是全? 写的命名方式Q但我推荐用大写搭配的变量名Q如QMakeFlags。这样可以避免和pȝ的变量冲H,而发生意外的事情?/font>
有一些变量是很奇怪字Ԍ?#8220;$<”?#8220;$@”{,q些是自动化变量Q我会在后面介绍?/font>
一、变量的基础
变量在声明时需要给予初|而在使用Ӟ需要给在变量名前加?#8220;$”W号Q但最好用括?#8220;Q)”或是大括?#8220;{}”把变量给包括h。如果你要用真实的“$”字符Q那么你需要用“$$”来表C?/font>
变量可以使用在许多地方,如规则中?#8220;目标”?#8220;依赖”?#8220;命o”以及新的变量中。先看一个例子:
objects = program.o foo.o utils.o
program : $(objects)
cc -o program $(objects)
$(objects) : defs.h
变量会在使用它的地方_地展开Q就像C/C++中的宏一P例如Q?/font>
foo = c
prog.o : prog.$(foo)
$(foo)$(foo) -$(foo) prog.$(foo)
展开后得刎ͼ
prog.o : prog.c
cc -c prog.c
当然Q千万不要在你的Makefile中这样干Q这里只是D个例子来表明Makefile中的变量在用处展开的真实样子。可见其是一?#8220;替代”的原理?/font>
另外Q给变量加上括号完全是ؓ了更加安全地使用q个变量Q在上面的例子中Q如果你不想l变量加上括P那也可以Q但我还是强烈徏议你l变量加上括受?/font>
二、变量中的变?/strong>
在定义变量的值时Q我们可以用其它变量来构造变量的|在Makefile中有两种方式来在用变量定义变量的倹{?/font>
先看W一U方式,也就是简单的使用“=”P?#8220;=”左侧是变量,右侧是变量的|右侧变量的值可以定义在文g的Q何一处,也就是说Q右侧中的变量不一定非要是已定义好的|其也可以使用后面定义的倹{如Q?/font>
foo = $(bar)
bar = $(ugh)
ugh = Huh?
all:
echo $(foo)
我们执行“make all”会打出变量$(foo)的值是“Huh?”Q?$(foo)的值是$(bar)Q?(bar)的值是$(ugh)Q?(ugh)的值是“Huh?”Q可见,变量是可以用后面的变量来定义的?/font>
q个功能有好的地方,也有不好的地方,好的地方是,我们可以把变量的真实值推到后面来定义Q如Q?/font>
CFLAGS = $(include_dirs) -O
include_dirs = -Ifoo -Ibar
?#8220;CFLAGS”在命令中被展开Ӟ会是“-Ifoo -Ibar -O”。但q种形式也有不好的地方,那就是递归定义Q如Q?/font>
CFLAGS = $(CFLAGS) -O
或:
A = $(B)
B = $(A)
q会让make陷入无限的变量展开q程中去Q当Ӟ我们的make是有能力这L定义Qƈ 会报错。还有就是如果在变量中用函敎ͼ那么Q这U方式会让我们的makeq行旉常慢Q更p糕的是Q他会用得两个make的函?#8220;wildcard” ?#8220;shell”发生不可预知的错误。因Z不会知道q两个函C被调用多次?/font>
Z避免上面的这U方法,我们可以使用make中的另一U用变量来定义变量的Ҏ。这U方法用的?#8220;:=”操作W,如:
x := foo
y := $(x) bar
x := later
其等价于Q?/font>
y := foo bar
x := later
值得一提的是,q种ҎQ前面的变量不能使用后面的变量,只能使用前面已定义好了的变量。如果是q样Q?/font>
y := $(x) bar
x := foo
那么Qy的值是“bar”Q而不?#8220;foo bar”?/font>
上面都是一些比较简单的变量使用了,让我们来看一个复杂的例子Q其中包括了make的函数、条件表辑ּ和一个系l变?#8220;MAKELEVEL”的用:
ifeq (0,${MAKELEVEL})
cur-dir := $(shell pwd)
whoami := $(shell whoami)
host-type := $(shell arch)
MAKE := ${MAKE} host-type=${host-type} whoami=${whoami}
endif
关于条g表达式和函数Q我们在后面再说Q对于系l变?#8220;MAKELEVEL”Q其意思是Q如果我们的make有一个嵌套执行的动作Q参见前面的“嵌套使用make”Q,那么Q这个变量会记录了我们的当前Makefile的调用层数?/font>
下面再介l两个定义变量时我们需要知道的Q请先看一个例子,如果我们要定义一个变量,其值是一个空|那么我们可以q样来:
nullstring :=
space := $(nullstring) # end of the line
nullstring是一个Empty变量Q其中什么也没有Q而我们的space的值是一个空 根{因为在操作W的双是很难描qC个空格的Q这里采用的技术很用Q先用一个Empty变量来标明变量的值开始了Q而后面采?#8220;#”注释W来表示变量? 义的l止Q这P我们可以定义出其值是一个空格的变量。请注意q里关于“#”的用,注释W?#8220;#”的这U特性值得我们注意Q如果我们这样定义一个变量:
dir := /foo/bar # directory to put the frobs in
dirq个变量的值是“/foo/bar”Q后面还跟了4个空|如果我们q样使用q样变量来指定别的目录—?#8220;$(dir)/file”那么完蛋了?/font>
q有一个比较有用的操作W是“?=”Q先看示例:
FOO ?= bar
其含义是Q如果FOO没有被定义过Q那么变量FOO的值就?#8220;bar”Q如果FOO先前被定义过Q那么这条语什么也不做Q其{h于:
ifeq ($(origin FOO), undefined)
FOO = bar
endif
三、变量高U用?/strong>
q里介绍两种变量的高U用方法,W一U是变量值的替换?/font>
我们可以替换变量中的共有的部分,其格式是“$(var:a=b)”或是“${var:a=b}”Q其意思是Q把变量“var”中所有以“a”字串“l尾”?#8220;a”替换?#8220;b”字串。这里的“l尾”意思是“I格”或是“l束W?#8221;?/font>
q是看一个示例吧Q?/font>
foo := a.o b.o c.o
bar := $(foo:.o=.c)
q个CZ中,我们先定义了一?#8220;$(foo)”变量Q而第二行的意思是?#8220;$(foo)”中所有以“.o”字串“l尾”全部替换?#8220;.c”Q所以我们的“$(bar)”的值就?#8220;a.c b.c c.c”?/font>
另外一U变量替换的技术是?#8220;静态模?#8221;Q参见前面章节)定义的,如:
foo := a.o b.o c.o
bar := $(foo:%.o=%.c)
q依赖于被替换字串中的有相同的模式,模式中必d含一?#8220;%”字符Q这个例子同栯$(bar)变量的gؓ“a.c b.c c.c”?
W二U高U用法是—?#8220;把变量的值再当成变量”。先看一个例子:
x = y
y = z
a := $($(x))
在这个例子中Q?(x)的值是“y”Q所?($(x))是$(y)Q于?(a)的值就?#8220;z”。(注意Q是“x=y”Q而不?#8220;x=$(y)”Q?/font>
我们q可以用更多的层次Q?/font>
x = y
y = z
z = u
a := $($($(x)))
q里?(a)的值是“u”Q相关的推导留给读者自己去做吧?/font>
让我们再复杂一点,使用?#8220;在变量定义中使用变量”的第一个方式,来看一个例子:
x = $(y)
y = z
z = Hello
a := $($(x))
q里?($(x))被替换成?($(y))Q因?(y)值是“z”Q所以,最l结果是Qa:=$(z)Q也是“Hello”?/font>
再复杂一点,我们再加上函敎ͼ
x = variable1
variable2 := Hello
y = $(subst 1,2,$(x))
z = y
a := $($($(z)))
q个例子中,“$($($(z)))”扩展?#8220;$($(y))”Q而其再次被扩展ؓ “$($(subst 1,2,$(x)))”?(x)的值是“variable1”Qsubst函数?#8220;variable1”中的所?#8220;1”字串替换?#8220;2”字串Q于 是,“variable1”变成“variable2”Q再取其|所以,最l,$(a)的值就?(variable2)的值—? “Hello”。(喔,好不ҎQ?/font>
在这U方式中Q或要可以用多个变量来l成一个变量的名字Q然后再取其|
first_second = Hello
a = first
b = second
all = $($a_$b)
q里?#8220;$a_$b”l成?#8220;first_second”Q于是,$(all)的值就?#8220;Hello”?/font>
再来看看l合W一U技术的例子Q?/font>
a_objects := a.o b.o c.o
1_objects := 1.o 2.o 3.o
sources := $($(a1)_objects:.o=.c)
q个例子中,如果$(a1)的值是“a”的话Q那么,$(sources)的值就?#8220;a.c b.c c.c”Q如?(a1)的值是“1”Q那?(sources)的值是“1.c 2.c 3.c”?/font>
再来看一个这U技术和“函数”?#8220;条g语句”一同用的例子Q?/font>
ifdef do_sort
func := sort
else
func := strip
endif
bar := a d b g q c
foo := $($(func) $(bar))
q个CZ中,如果定义?#8220;do_sort”Q那么:foo := $(sort a d b g q c)Q于?(foo)的值就?#8220;a b c d g q”Q而如果没有定?#8220;do_sort”Q那么:foo := $(sort a d b g q c)Q调用的是strip函数?/font>
当然Q?#8220;把变量的值再当成变量”q种技术,同样可以用在操作W的左边Q?/font>
dir = foo
$(dir)_sources := $(wildcard $(dir)/*.c)
define $(dir)_print
lpr $($(dir)_sources)
endef
q个例子中定义了三个变量Q?#8220;dir”Q?#8220;foo_sources”?#8220;foo_print”?/font>
四、追加变量?/strong>
我们可以使用“+=”操作W给变量q加|如:
objects = main.o foo.o bar.o utils.o
objects += another.o
于是Q我们的$(objects)值变成:“main.o foo.o bar.o utils.o another.o”Qanother.o被追加进MQ?/font>
使用“+=”操作W,可以模拟Z面的q种例子Q?/font>
objects = main.o foo.o bar.o utils.o
objects := $(objects) another.o
所不同的是Q用“+=”更ؓz?/font>
如果变量之前没有定义q,那么Q?#8220;+=”会自动变?#8220;=”Q如果前面有变量定义Q那?#8220;+=”会承于前次操作的赋值符。如果前一ơ的?#8220;:=”Q那?#8220;+=”会以“:=”作ؓ其赋值符Q如Q?/font>
variable := value
variable += more
{h于:
variable := value
variable := $(variable) more
但如果是q种情况Q?
variable = value
variable += more
׃前次的赋值符?#8220;=”Q所?#8220;+=”也会?#8220;=”来做|那么岂不会发生变量的递补归定义,q是很不好的Q所以make会自动ؓ我们解决q个问题Q我们不必担心这个问题?/font>
五、override 指示W?/strong>
如果有变量是通常make的命令行参数讄的,那么Makefile中对q个变量的赋g被忽略。如果你惛_Makefile中设|这cd数的|那么Q你可以使用“override”指示W。其语法是:
override <variable> = <value>
override <variable> := <value>
当然Q你q可以追加:
override <variable> += <more text>
对于多行的变量定义,我们用define指示W,在define指示W前Q也同样可以使用ovveride指示W,如:
override define foo
bar
endef
六、多行变?/strong>
q有一U设|变量值的Ҏ是用define关键字。用define关键字设|变量的值可以有换行Q这有利于定义一pd的命令(前面我们讲过“命o?#8221;的技术就是利用这个关键字Q?/font>
define指示W后面跟的是变量的名字,而重起一行定义变量的|定义是以endef关键? l束。其工作方式?#8220;=”操作W一栗变量的值可以包含函数、命令、文字,或是其它变量。因为命令需要以[Tab]键开_所以如果你用define定义 的命令变量中没有以[Tab]键开_那么make׃会把其认为是命o?/font>
下面的这个示例展CZdefine的用法:
define two-lines
echo foo
echo $(bar)
endef
七、环境变?/strong>
makeq行时的pȝ环境变量可以在make开始运行时被蝲入到Makefile文g中,但是 如果Makefile中已定义了这个变量,或是q个变量由make命o行带入,那么pȝ的环境变量的值将被覆盖。(如果make指定?#8220;-e”参数Q那 么,pȝ环境变量覆盖Makefile中定义的变量Q?/font>
因此Q如果我们在环境变量中设|了“CFLAGS”环境变量Q那么我们就可以在所有的 Makefile中用这个变量了。这对于我们使用l一的编译参数有比较大的好处。如果Makefile中定义了CFLAGSQ那么则会? Makefile中的q个变量Q如果没有定义则使用pȝ环境变量的|一个共性和个性的l一Q很?#8220;全局变量”?#8220;局部变?#8221;的特性?/font>
当make嵌套调用Ӟ参见前面?#8220;嵌套调用”章节Q,上层Makefile中定义的变量会以 pȝ环境变量的方式传递到下层的Makefile中。当Ӟ默认情况下,只有通过命o行设|的变量会被传递。而定义在文g中的变量Q如果要向下? Makefile传递,则需要用exprot关键字来声明。(参见前面章节Q?/font>
当然Q我q不推荐把许多的变量都定义在pȝ环境中,q样Q在我们执行不用的MakefileӞ拥有的是同一套系l变量,q可能会带来更多的麻烦?/font>
八、目标变?/strong>
前面我们所讲的在Makefile中定义的变量都是“全局变量”Q在整个文gQ我们都可以讉Kq些变量。当Ӟ“自动化变?#8221;除外Q如“$<”{这U类量的自动化变量就属于“规则型变?#8221;Q这U变量的g赖于规则的目标和依赖目标的定义?/font>
当然Q我样同样可以ؓ某个目标讄局部变量,q种变量被称?#8220;Target-specific Variable”Q它可以?#8220;全局变量”同名Q因为它的作用范围只在这条规则以及连带规则中Q所以其g只在作用范围内有效。而不会媄响规则链以外的全局变量的倹{?/font>
其语法是Q?/font>
<target ...> : <variable-assignment>
<target ...> : overide <variable-assignment>
<variable-assignment>可以是前面讲q的各种赋D辑ּQ如“=”?#8220;:=”?#8220;+=”或是“Q?”。第二个语法是针对于make命o行带入的变量Q或是系l环境变量?/font>
q个Ҏ非常的有用Q当我们讄了这样一个变量,q个变量会作用到p个目标所引发的所有的规则中去。如Q?/font>
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
在这个示例中Q不全局?(CFLAGS)的值是什么,在prog目标Q以及其所引发的所有规则中Qprog.o foo.o bar.o的规则)Q?(CFLAGS)的值都?#8220;-g”
九、模式变?/strong>
在GNU的make中,q支持模式变量(Pattern-specific VariableQ,通过上面的目标变量中Q我们知道,变量可以定义在某个目标上。模式变量的好处是Q我们可以给定一U?#8220;模式”Q可以把变量定义在符合这U模式的所有目标上?/font>
我们知道Qmake?#8220;模式”一般是臛_含有一?#8220;%”的,所以,我们可以以如下方式给所有以[.o]l尾的目标定义目标变量:
%.o : CFLAGS = -O
同样Q模式变量的语法?#8220;目标变量”一P
<pattern ...> : <variable-assignment>
<pattern ...> : override <variable-assignment>
override同样是针对于pȝ环境传入的变量,或是make命o行指定的变量?br>
使用条g判断
—————?/strong>
使用条g判断Q可以让makeҎq行时的不同情况选择不同的执行分支。条件表辑ּ可以是比较变量的|或是比较变量和常量的倹{?/font>
一、示?/strong>
下面的例子,判断$(CC)变量是否“gcc”Q如果是的话Q则使用GNU函数~译目标?/font>
libs_for_gcc = -lgnu
normal_libs =
foo: $(objects)
ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif
可见Q在上面CZ的这个规则中Q目?#8220;foo”可以Ҏ变量“$(CC)”值来选取不同的函数库来编译程序?/font>
我们可以从上面的CZ中看C个关键字Qifeq、else和endif。ifeq的意思表C? 条g语句的开始,q指定一个条件表辑ּQ表辑ּ包含两个参数Q以逗号分隔Q表辑ּ以圆括号括v。else表示条g表达式ؓ假的情况。endif表示一个条 件语句的l束QQ何一个条件表辑ּ都应该以endifl束?/font>
当我们的变量$(CC)值是“gcc”Ӟ目标foo的规则是Q?/font>
foo: $(objects)
$(CC) -o foo $(objects) $(libs_for_gcc)
而当我们的变?(CC)g?#8220;gcc”Ӟ比如“cc”Q,目标foo的规则是Q?/font>
foo: $(objects)
$(CC) -o foo $(objects) $(normal_libs)
当然Q我们还可以把上面的那个例子写得更简z一些:
libs_for_gcc = -lgnu
normal_libs =
ifeq ($(CC),gcc)
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif
foo: $(objects)
$(CC) -o foo $(objects) $(libs)
二、语?/strong>
条g表达式的语法为:
<conditional-directive>
<text-if-true>
endif
以及Q?/font>
<conditional-directive>
<text-if-true>
else
<text-if-false>
endif
其中<conditional-directive>表示条g关键字,?#8220;ifeq”。这个关键字有四个?/font>
W一个是我们前面所见过?#8220;ifeq”
ifeq (<arg1>, <arg2>)
ifeq '<arg1>' '<arg2>'
ifeq "<arg1>" "<arg2>"
ifeq "<arg1>" '<arg2>'
ifeq '<arg1>' "<arg2>"
比较参数“arg1”?#8220;arg2”的值是否相同。当Ӟ参数中我们还可以使用make的函数。如Q?/font>
ifeq ($(strip $(foo)),)
<text-if-empty>
endif
q个CZ中用了“strip”函数Q如果这个函数的q回值是I(EmptyQ,那么<text-if-empty>q效?/font>
W二个条件关键字?#8220;ifneq”。语法是Q?/font>
ifneq (<arg1>, <arg2>)
ifneq '<arg1>' '<arg2>'
ifneq "<arg1>" "<arg2>"
ifneq "<arg1>" '<arg2>'
ifneq '<arg1>' "<arg2>"
其比较参?#8220;arg1”?#8220;arg2”的值是否相同,如果不同Q则为真。和“ifeq”cM?/font>
W三个条件关键字?#8220;ifdef”。语法是Q?/font>
ifdef <variable-name>
如果变量<variable-name>的值非I,那到表达式ؓ真。否则,表达? 为假。当Ӟ<variable-name>同样可以是一个函数的q回倹{注意,ifdef只是试一个变量是否有|其ƈ不会把变量扩展到 当前位置。还是来看两个例子:
CZ一Q?br> bar =
foo = $(bar)
ifdef foo
frobozz = yes
else
frobozz = no
endif
CZ二:
foo =
ifdef foo
frobozz = yes
else
frobozz = no
endif
W一个例子中Q?#8220;$(frobozz)”值是“yes”Q第二个则是“no”?/font>
W四个条件关键字?#8220;ifndef”。其语法是:
ifndef <variable-name>
q个我就不多说了Q和“ifdef”是相反的意思?/font>
?lt;conditional-directive>q一行上Q多余的I格是被允许的,但是不能以[Tab]键做为开始(不然p认ؓ是命令)。而注释符“#”同样也是安全的?#8220;else”?#8220;endif”也一P只要不是以[Tab]键开始就行了?/font>
特别注意的是Qmake是在dMakefile时就计算条g表达式的|q根据条件表辑ּ的值来选择语句Q所以,你最好不要把自动化变量(?#8220;$@”{)攑օ条g表达式中Q因动化变量是在q行时才有的?/font>
而且Qؓ了避免乱,make不允许把整个条g语句分成两部分放在不同的文g中?/font>
使用函数
———?/strong>
在Makefile中可以用函数来处理变量Q从而让我们的命令或是规则更为的灉|和具有智能。make所支持的函C不算很多Q不q已l够我们的操作了。函数调用后Q函数的q回值可以当做变量来使用?/font>
一、函数的调用语法
函数调用Q很像变量的使用Q也是以“$”来标识的Q其语法如下Q?/font>
$(<function> <arguments>)
或是
${<function> <arguments>}
q里Q?lt;function>是函数名,make支持的函C 多?lt;arguments>是函数的参数Q参数间以逗号“,”分隔Q而函数名和参C间以“I格”分隔。函数调用以“$”开_以圆括号或花 括号把函数名和参数括赗感觉很像一个变量,是不是?函数中的参数可以使用变量Qؓ了风格的l一Q函数和变量的括h好一P如?#8220;$(subst a,b,$(x))”q样的Ş式,而不?#8220;$(subst a,b,${x})”的Ş式。因为统一会更清楚Q也会减一些不必要的麻烦?/font>
q是来看一个示例:
comma:= ,
empty:=
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))
在这个示例中Q?(comma)的值是一个逗号?(space)使用?(empty)定义 了一个空|$(foo)的值是“a b c”Q?(bar)的定义用Q调用了函数“subst”Q这是一个替换函敎ͼq个函数有三个参敎ͼW一个参数是被替换字ԌW二个参数是替换字串Q第三个 参数是替换操作作用的字串。这个函C是?(foo)中的I格替换成逗号Q所?(bar)的值是“a,b,c”?/font>
二、字W串处理函数
$(subst <from>,<to>,<text>)
名称Q字W串替换函数——subst?br> 功能Q把字串<text>中的<from>字符串替换成<to>?br> q回Q函数返回被替换q后的字W串?/font>
CZQ?br>
$(subst ee,EE,feet on the street)Q?br>
?#8220;feet on the street”中的“ee”替换?#8220;EE”Q返回结果是“fEEt on the strEEt”?/font>
$(patsubst <pattern>,<replacement>,<text>)
名称Q模式字W串替换函数——patsubst?br> 功能Q查?lt;text>中的单词Q单词以“I格”?#8220;Tab”?#8220;回R”“换行”分隔Q是否符合模?lt;pattern>Q如果匹 配的话,则以<replacement>替换。这里,<pattern>可以包括通配W?#8220;%”Q表CZQ意长度的字串。如 ?lt;replacement>中也包含“%”Q那么,<replacement>中的q个“%”? ?lt;pattern>中的那个“%”所代表的字丌Ӏ(可以?#8220;\”来{义,?#8220;\%”来表C真实含义的“%”字符Q?br> q回Q函数返回被替换q后的字W串?/font>
CZQ?/font>
$(patsubst %.c,%.o,x.c.c bar.c)
把字?#8220;x.c.c bar.c”W合模式[%.c]的单词替换成[%.o]Q返回结果是“x.c.o bar.o”
备注Q?/font>
q和我们前面“变量章节”说过的相关知识有点相伹{如Q?br>
“$(var:<pattern>=<replacement>)”
相当?br> “$(patsubst <pattern>,<replacement>,$(var))”Q?br>
?#8220;$(var: <suffix>=<replacement>)”
则相当于
“$(patsubst %<suffix>,%<replacement>,$(var))”?br>
例如有:objects = foo.o bar.o baz.oQ?br> 那么Q?#8220;$(objects:.o=.c)”?#8220;$(patsubst %.o,%.c,$(objects))”是一L?/font>
$(strip <string>)
名称Q去I格函数——strip?br> 功能Q去?lt;string>字串中开头和l尾的空字符?br> q回Q返回被LI格的字W串倹{?br> CZQ?br>
$(strip a b c )
把字?#8220;a b c ”d开头和l尾的空|l果?#8220;a b c”?/font>
$(findstring <find>,<in>)
名称Q查扑֭W串函数——findstring?br> 功能Q在字串<in>中查?lt;find>字串?br> q回Q如果找刎ͼ那么q回<find>Q否则返回空字符丌Ӏ?br> CZQ?/font>
$(findstring a,a b c)
$(findstring a,b c)
W一个函数返?#8220;a”字符ԌW二个返?#8220;”字符ԌI字W串Q?/font>
$(filter <pattern...>,<text>)
名称Q过滤函数——filter?br> 功能Q以<pattern>模式qo<text>字符串中的单词,保留W合模式<pattern>的单词。可以有多个模式?br> q回Q返回符合模?lt;pattern>的字丌Ӏ?br> CZQ?/font>
sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
cc $(filter %.c %.s,$(sources)) -o foo
$(filter %.c %.s,$(sources))q回的值是“foo.c bar.c baz.s”?/font>
$(filter-out <pattern...>,<text>)
名称Q反qo函数——filter-out?br> 功能Q以<pattern>模式qo<text>字符串中的单词,去除W合模式<pattern>的单词。可以有多个模式?br> q回Q返回不W合模式<pattern>的字丌Ӏ?br> CZQ?/font>
objects=main1.o foo.o main2.o bar.o
mains=main1.o main2.o
$(filter-out $(mains),$(objects)) q回值是“foo.o bar.o”?br>
$(sort <list>)
名称Q排序函数——sort?br> 功能Q给字符?lt;list>中的单词排序Q升序)?br> q回Q返回排序后的字W串?br> CZQ?(sort foo bar lose)q回“bar foo lose” ?br> 备注Qsort函数会去?lt;list>中相同的单词?/font>
$(word <n>,<text>)
名称Q取单词函数——word?br> 功能Q取字符?lt;text>中第<n>个单词。(从一开始)
q回Q返回字W串<text>中第<n>个单词。如?lt;n>?lt;text>中的单词数要大,那么q回I字W串?br> CZQ?(word 2, foo bar baz)q回值是“bar”?/font>
$(wordlist <s>,<e>,<text>)
名称Q取单词串函数——wordlist?br> 功能Q从字符?lt;text>中取?lt;s>开始到<e>的单词串?lt;s>?lt;e>是一个数字?br> q回Q返回字W串<text>中从<s>?lt;e>的单词字丌Ӏ如?lt;s>?lt;text>? 的单词数要大Q那么返回空字符丌Ӏ如?lt;e>大于<text>的单词数Q那么返回从<s>开始, ?lt;text>l束的单词串?br> CZQ?$(wordlist 2, 3, foo bar baz)q回值是“bar baz”?/font>
$(words <text>)
名称Q单词个数统计函数——words?br> 功能Q统?lt;text>中字W串中的单词个数?br> q回Q返?lt;text>中的单词数?br> CZQ?(words, foo bar baz)q回值是“3”?br> 备注Q如果我们要?lt;text>中最后的一个单词,我们可以q样Q?(word $(words <text>),<text>)?/font>
$(firstword <text>)
名称Q首单词函数——firstword?br> 功能Q取字符?lt;text>中的W一个单词?br> q回Q返回字W串<text>的第一个单词?br> CZQ?(firstword foo bar)q回值是“foo”?br> 备注Q这个函数可以用word函数来实玎ͼ$(word 1,<text>)?/font>
以上Q是所有的字符串操作函敎ͼ如果搭配混合使用Q可以完成比较复杂的功能。这里,举一个现实中应用的例子。我们知道,make使用“VPATH”变量来指?#8220;依赖文g”的搜索\径。于是,我们可以利用q个搜烦路径来指定编译器对头文g的搜索\径参数CFLAGSQ如Q?/font>
override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH)))
如果我们?#8220;$(VPATH)”值是“src:../headers”Q那?#8220;$(patsubst %,-I%,$(subst :, ,$(VPATH)))”返?#8220;-Isrc -I../headers”Q这正是cc或gcc搜烦头文件\径的参数?/font>
三、文件名操作函数
下面我们要介l的函数主要是处理文件名的。每个函数的参数字符串都会被当做一个或是一pd的文件名来对待?/font>
$(dir <names...>)
名称Q取目录函数——dir?br> 功能Q从文g名序?lt;names>中取出目录部分。目录部分是指最后一个反斜杠Q?#8220;/”Q之前的部分。如果没有反斜杠Q那么返?#8220;./”?br> q回Q返回文件名序列<names>的目录部分?br> CZQ?$(dir src/foo.c hacks)q回值是“src/ ./”?/font>
$(notdir <names...>)
名称Q取文g函数——notdir?br> 功能Q从文g名序?lt;names>中取出非目录部分。非目录部分是指最后一个反斜杠Q?#8220;/”Q之后的部分?br> q回Q返回文件名序列<names>的非目录部分?br> CZQ?$(notdir src/foo.c hacks)q回值是“foo.c hacks”?br>
$(suffix <names...>)
名称Q取后缀函数——suffix?br> 功能Q从文g名序?lt;names>中取出各个文件名的后~?br> q回Q返回文件名序列<names>的后~序列Q如果文件没有后~Q则q回I字丌Ӏ?br> CZQ?(suffix src/foo.c src-1.0/bar.c hacks)q回值是“.c .c”?/font>
$(basename <names...>)
名称Q取前缀函数——basename?br> 功能Q从文g名序?lt;names>中取出各个文件名的前~部分?br> q回Q返回文件名序列<names>的前~序列Q如果文件没有前~Q则q回I字丌Ӏ?br> CZQ?(basename src/foo.c src-1.0/bar.c hacks)q回值是“src/foo src-1.0/bar hacks”?/font>
$(addsuffix <suffix>,<names...>)
名称Q加后缀函数——addsuffix?br> 功能Q把后缀<suffix>加到<names>中的每个单词后面?br> q回Q返回加q后~的文件名序列?br> CZQ?(addsuffix .c,foo bar)q回值是“foo.c bar.c”?/font>
$(addprefix <prefix>,<names...>)
名称Q加前缀函数——addprefix?br> 功能Q把前缀<prefix>加到<names>中的每个单词后面?br> q回Q返回加q前~的文件名序列?br> CZQ?(addprefix src/,foo bar)q回值是“src/foo src/bar”?/font>
$(join <list1>,<list2>)
名称Q连接函数——join?br> 功能Q把<list2>中的单词对应地加?lt;list1>的单词后面。如?lt;list1>的单词个数要 ?lt;list2>的多Q那么,<list1>中的多出来的单词保持原栗如?lt;list2>的单词个数要 ?lt;list1>多,那么Q?lt;list2>多出来的单词被复制?lt;list2>中?br> q回Q返回连接过后的字符丌Ӏ?br> CZQ?(join aaa bbb , 111 222 333)q回值是“aaa111 bbb222 333”?/font>
在Makefile中,规则的顺序是很重要的Q因为,Makefile中只应该有一个最l目 标,其它的目标都是被q个目标所q带出来的,所以一定要让make知道你的最l目标是什么。一般来_定义在Makefile中的目标可能会有很多Q但? W一条规则中的目标将被确立ؓ最l的目标。如果第一条规则中的目标有很多个,那么Q第一个目标会成ؓ最l的目标。make所完成的也是q个目标?/font>
好了Q还是让我们来看一看如何书写规则?/font>
一、规则D?/strong>
foo.o : foo.c defs.h # foo模块
cc -c -g foo.c
看到q个例子Q各位应该不是很陌生了,前面也已说过Qfoo.o是我们的目标Qfoo.c和defs.h是目标所依赖的源文gQ而只有一个命?#8220;cc -c -g foo.c”Q以Tab键开_。这个规则告诉我们两件事Q?/font>
1、文件的依赖关系Qfoo.o依赖于foo.c和defs.h的文Ӟ如果foo.c和defs.h的文件日期要比foo.o文g日期要新Q或是foo.o不存在,那么依赖关系发生?br> 2、如果生成(或更斎ͼfoo.o文g。也是那个cc命oQ其说明了,如何生成foo.oq个文g。(当然foo.c文ginclude了defs.h文gQ?/font>
二、规则的语法
targets : prerequisites
command
...
或是q样Q?
targets : prerequisites ; command
command
...
targets是文件名Q以I格分开Q可以用通配W。一般来_我们的目标基本上是一个文Ӟ但也有可能是多个文g?/font>
command是命令行Q如果其不与“target:prerequisites”在一行,那么Q必M[Tab键]开_如果和prerequisites在一行,那么可以用分号做为分隔。(见上Q?/font>
prerequisites也就是目标所依赖的文Ӟ或依赖目标)。如果其中的某个文g要比目标文g要新Q那么,目标p认ؓ?#8220;q时?#8221;Q被认ؓ是需要重生成的。这个在前面已经讲过了?/font>
如果命o太长Q你可以使用反斜框(‘\’Q作为换行符。make对一行上有多个字符没有限制。规则告诉make两g事,文g的依赖关pd如何成成目标文g?/font>
一般来_make会以UNIX的标准ShellQ也是/bin/sh来执行命令?/font>
三、在规则中用通配W?/strong>
如果我们惛_义一pd比较cM的文Ӟ我们很自然地想起用通配W。make支持三各通配W:“*”Q?#8220;?”?#8220;[...]”。这是和Unix的B-Shell是相同的?/font>
波浪P“~”Q字W在文g名中也有比较Ҏ的用途。如果是“~/test”Q这pC当前用 L$HOME目录下的test目录。?#8220;~hchen/test”则表C用户hchen的宿ȝ录下的test目录。(q些都是Unix下的知? 了,make也支持)而在Windows或是MS-DOS下,用户没有宿主目录Q那么L号所指的目录则根据环境变?#8220;HOME”而定?/font>
通配W代替了你一pd的文Ӟ?#8220;*.c”表示所以后~为c的文件。一个需要我们注意的是,如果我们的文件名中有通配W,如:“*”Q那么可以用转义字符“\”Q如“\*”来表C真实的“*”字符Q而不是Q意长度的字符丌Ӏ?/font>
好吧Q还是先来看几个例子吧:
clean:
rm -f *.o
上面q个例子我不不多说了Q这是操作系lShell所支持的通配W。这是在命o中的通配W?/font>
print: *.c
lpr -p $?
touch print
上面q个例子说明了通配W也可以在我们的规则中,目标print依赖于所有的[.c]文g。其中的“$?”是一个自动化变量Q我会在后面l你讲述?/font>
objects = *.o
上面q个例子Q表CZQ通符同样可以用在变量中。ƈ不是说[*.o]会展开Q不Qobjects的值就?#8220;*.o”。Makefile中的变量其实是 C/C++中的宏。如果你要让通配W在变量中展开Q也是让objects的值是所有[.o]的文件名的集合,那么Q你可以q样Q?/font>
objects := $(wildcard *.o)
q种用法由关键字“wildcard”指出Q关于Makefile的关键字Q我们将在后面讨论?/font>
四、文件搜?/strong>
在一些大的工E中Q有大量的源文gQ我们通常的做法是把这许多的源文g分类Qƈ存放在不同的目录中。所以,当make需要去扑֯文g的依赖关pLQ你可以在文件前加上路径Q但最好的Ҏ是把一个\径告诉makeQ让make在自动去找?/font>
Makefile文g中的Ҏ变量“VPATH”是完成q个功能的,如果没有指明q个变量Qmake只会在当前的目录中去扑֯依赖文g和目标文件。如果定义了q个变量Q那么,make׃在当当前目录找不到的情况下,到所指定的目录中LL件了?/font>
VPATH = src:../headers
上面的的定义指定两个目录Q?#8220;src”?#8220;../headers”Qmake会按照这个顺序进行搜索。目录由“冒号”分隔。(当然Q当前目录永q是最高优先搜索的地方Q?/font>
另一个设|文件搜索\径的Ҏ是用make?#8220;vpath”关键字(注意Q它是全写的)Q? q不是变量,q是一个make的关键字Q这和上面提到的那个VPATH变量很类|但是它更为灵zR它可以指定不同的文件在不同的搜索目录中。这是一个很 灉|的功能。它的用方法有三种Q?/font>
1、vpath <pattern> <directories>
为符合模?lt;pattern>的文件指定搜索目?lt;directories>?/font>
2、vpath <pattern>
清除W合模式<pattern>的文件的搜烦目录?/font>
3、vpath
清除所有已被设|好了的文g搜烦目录?/font>
vapth使用Ҏ中的<pattern>需要包?#8220;%”字符?#8220;%”的意思是? 配零或若q字W,例如Q?#8220;%.h”表示所有以“.h”l尾的文件?lt;pattern>指定了要搜烦的文仉Q? ?lt;directories>则指定了<pattern>的文仉的搜索的目录。例如:
vpath %.h ../headers
该语句表C,要求make?#8220;../headers”目录下搜索所有以“.h”l尾的文件。(如果某文件在当前目录没有扑ֈ的话Q?/font>
我们可以q箋C用vpath语句Q以指定不同搜烦{略。如果连l的vpath语句中出C相同?lt;pattern>Q或是被重复了的<pattern>Q那么,make会按照vpath语句的先后顺序来执行搜烦。如Q?/font>
vpath %.c foo
vpath % blish
vpath %.c bar
其表C?#8220;.c”l尾的文Ӟ先在“foo”目录Q然后是“blish”Q最后是“bar”目录?/font>
vpath %.c foo:bar
vpath % blish
而上面的语句则表C?#8220;.c”l尾的文Ӟ先在“foo”目录Q然后是“bar”目录Q最后才?#8220;blish”目录?/font>
五、伪目标
最早先的一个例子中Q我们提到过一?#8220;clean”的目标,q是一?#8220;伪目?#8221;Q?/font>
clean:
rm *.o temp
正像我们前面例子中的“clean”一P即然我们生成了许多文件编译文Ӟ我们也应该提供一个清除它们的“目标”以备完整地重~译而用?Q以“make clean”来用该目标Q?/font>
因ؓQ我们ƈ不生?#8220;clean”q个文g?#8220;伪目?#8221;q不是一个文Ӟ只是一个标{,׃“ 伪目?#8221;不是文gQ所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过昄地指明这?#8220;目标”才能让其生效。当Ӟ“伪目?#8221;的取名不? 和文件名重名Q不然其失M“伪目?#8221;的意义了?/font>
当然Qؓ了避免和文g重名的这U情况,我们可以使用一个特D的标记“.PHONY”来显C地指明一个目标是“伪目?#8221;Q向make说明Q不是否有q个文gQ这个目标就?#8220;伪目?#8221;?/font>
.PHONY : clean
只要有这个声明,不管是否?#8220;clean”文gQ要q行“clean”q个目标Q只?#8220;make clean”q样。于是整个过E可以这样写Q?/font>
.PHONY: clean
clean:
rm *.o temp
伪目标一般没有依赖的文g。但是,我们也可以ؓ伪目标指定所依赖的文件。伪目标同样可以作ؓ“ 默认目标”Q只要将其放在第一个。一个示例就是,如果你的Makefile需要一口气生成若干个可执行文gQ但你只想简单地敲一个make完事Qƈ且,所 有的目标文g都写在一个Makefile中,那么你可以?#8220;伪目?#8221;q个Ҏ:
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
我们知道QMakefile中的W一个目标会被作为其默认目标。我们声明了一?#8220;all”的伪 目标Q其依赖于其它三个目标。由于伪目标的特性是QL被执行的Q所以其依赖的那三个目标L不如“all”q个目标新。所以,其它三个目标的规则L 会被册。也pC我们一口气生成多个目标的目的?#8220;.PHONY : all”声明?#8220;all”q个目标?#8220;伪目?#8221;?/font>
随便提一句,从上面的例子我们可以看出Q目标也可以成ؓ依赖。所以,伪目标同样也可成Z赖。看下面的例子:
.PHONY: cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff
rm program
cleanobj :
rm *.o
cleandiff :
rm *.diff
“make clean”清除所有要被清除的文g?#8220;cleanobj”?#8220;cleandiff”q两个伪目标有点?#8220;子程?#8221;的意思。我们可以输?#8220;make cleanall”?#8220;make cleanobj”?#8220;make cleandiff”命o来达到清除不同种cL件的目的?/font>
六、多目标
Makefile的规则中的目标可以不止一个,其支持多目标Q有可能我们的多个目标同时依赖于 一个文Ӟq且其生成的命o大体cM。于是我们就能把其合qv来。当Ӟ多个目标的生成规则的执行命o是同一个,q可能会可我们带来麻烦,不过好在我们? 可以使用一个自动化变量“$@”Q关于自动化变量Q将在后面讲qͼQ这个变量表C着目前规则中所有的目标的集合,q样说可能很抽象Q还是看一个例子吧?/font>
bigoutput littleoutput : text.g
generate text.g -$(subst output,,$@) > $@
上述规则{h于:
bigoutput : text.g
generate text.g -big > bigoutput
littleoutput : text.g
generate text.g -little > littleoutput
其中Q?$(subst output,,$@)中的“$”表示执行一个Makefile的函敎ͼ函数名ؓsubstQ后面的为参数。关于函敎ͼ在后面讲述。这里的q个函数是截 取字W串的意思,“$@”表示目标的集合,像一个数l,“$@”依次取出目标Qƈ执于命o?/font>
七、静态模?/strong>
静态模式可以更加容易地定义多目标的规则Q可以让我们的规则变得更加的有弹性和灉|。我们还是先来看一下语法:
<targets ...>: <target-pattern>: <prereq-patterns ...>
<commands>
...
targets定义了一pd的目标文Ӟ可以有通配W。是目标的一个集合?/font>
target-parrtern是指明了targets的模式,也就是的目标集模式?/font>
prereq-parrterns是目标的依赖模式Q它对target-parrtern形成的模式再q行一ơ依赖目标的定义?/font>
q样描述q三个东西,可能q是没有说清楚,q是举个例子来说明一下吧。如果我? ?lt;target-parrtern>定义?#8220;%.o”Q意思是我们?lt;target>集合中都是以“.o”l尾的,而如果我? ?lt;prereq-parrterns>定义?#8220;%.c”Q意思是?lt;target-parrtern>所形成的目标集q行二次 定义Q其计算Ҏ是,?lt;target-parrtern>模式中的“%”Q也是L了[.o]q个l尾Q,qؓ其加上[.c]q个l尾Q? 形成的新集合?/font>
所以,我们?#8220;目标模式”或是“依赖模式”中都应该?#8220;%”q个字符Q如果你的文件名中有“%”那么你可以用反斜杠“\”q行转义Q来标明真实?#8220;%”字符?/font>
看一个例子:
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
上面的例子中Q指明了我们的目标从$object中获取,“%.o”表明要所有以
“.o”l尾的目标,也就?#8220;foo.o
bar.o”Q也是变量$object集合的模式,而依赖模?#8220;%.c”则取模式“%.o”?#8220;%”Q也是“foo
bar”Qƈ为其加下“.c”的后~Q于是,我们的依赖目标就?#8220;foo.c
bar.c”。而命令中?#8220;$<”?#8220;$@”则是自动化变量,“$<”表示所有的依赖目标集(也就?#8220;foo.c
bar.c”Q,“$@”表示目标集(也就?#8220;foo.o bar.o”Q。于是,上面的规则展开后等价于下面的规则:
foo.o : foo.c
$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
$(CC) -c $(CFLAGS) bar.c -o bar.o
试想Q如果我们的“%.o”有几百个Q那U我们只要用q种很简单的“静态模式规?#8221;可以写完一堆规则,实在是太有效率了?#8220;静态模式规?#8221;的用法很灉|Q如果用得好Q那会一个很强大的功能。再看一个例子:
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 $<
$(filter %.o,$(files))表示调用Makefile的filter函数Q过?#8220;$filter”集,只要其中模式?#8220;%.o”的内宏V其的它内容Q我׃用多说了吧。这个例字展CZMakefile中更大的Ҏ?/font>
八、自动生成依赖?/strong>
在Makefile中,我们的依赖关pd能会需要包含一pd的头文gQ比如,如果我们的main.c中有一?#8220;#include "defs.h"”Q那么我们的依赖关系应该是:
main.o : main.c defs.h
但是Q如果是一个比较大型的工程Q你必需清楚哪些C文g包含了哪些头文gQƈ且,你在加入或删 除头文gӞ也需要小心地修改MakefileQ这是一个很没有l护性的工作。ؓ了避免这U繁重而又Ҏ出错的事情,我们可以使用C/C++~译的一个功 能。大多数的C/C++~译器都支持一?#8220;-M”的选项Q即自动扑֯源文件中包含的头文gQƈ生成一个依赖关pR例如,如果我们执行下面的命令:
cc -M main.c
其输出是Q?/font>
main.o : main.c defs.h
于是q译器自动生成的依赖关p,q样一来,你就不必再手动书写若q文件的依赖关系Q而由~译器自动生成了。需要提醒一句的是,如果你用GNU的C/C++~译器,你得?#8220;-MM”参数Q不Ӟ“-M”参数会把一些标准库的头文g也包含进来?/font>
gcc -M main.c的输出是Q?/font>
main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h \
/usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \
/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h \
/usr/include/bits/types.h /usr/include/bits/pthreadtypes.h \
/usr/include/bits/sched.h /usr/include/libio.h \
/usr/include/_G_config.h /usr/include/wchar.h \
/usr/include/bits/wchar.h /usr/include/gconv.h \
/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h \
/usr/include/bits/stdio_lim.h
gcc -MM main.c的输出则是:
main.o: main.c defs.h
那么Q编译器的这个功能如何与我们的Makefile联系在一起呢。因样一来,我们? Makefile也要Ҏq些源文仉新生成,让Makefile自已依赖于源文gQ这个功能ƈ不现实,不过我们可以有其它手D|q回地实现这一功能? GNUl织把编译器为每一个源文g的自动生成的依赖关系攑ֈ一个文件中Qؓ每一?#8220;name.c”的文仉生成一?#8220;name.d”? Makefile文gQ[.d]文g中就存放对应[.c]文g的依赖关pR?/font>
于是Q我们可以写出[.c]文g和[.d]文g的依赖关p,q让make自动更新或自成[.d]文gQƈ把其包含在我们的主Makefile中,q样Q我们就可以自动化地生成每个文g的依赖关pM?/font>
q里Q我们给Z一个模式规则来产生[.d]文gQ?/font>
%.d: %.c
@set -e; rm -f $@; \
$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
q个规则的意思是Q所有的[.d]文g依赖于[.c]文gQ?#8220;rm -f
$@”的意思是删除所有的目标Q也是[.d]文gQ第二行的意思是Qؓ每个依赖文g“$<”Q也是[.c]文g生成依赖文gQ?#8220;$@”表示模式
“%.d”文gQ如果有一个C文g是name.cQ那?#8220;%”是“name”Q?#8220;$$$$”意ؓ一个随机编PW二行生成的文g有可能是
“name.d.12345”Q第三行使用sed命o做了一个替换,关于sed命o的用法请参看相关的用文档。第四行是删除临时文g?/font>
总而言之,q个模式要做的事是在编译器生成的依赖关pM加入[.d]文g的依赖,x依赖关系Q?/font>
main.o : main.c defs.h
转成Q?/font>
main.o main.d : main.c defs.h
于是Q我们的[.d]文g也会自动更新了,q会自动生成了,当然Q你q可以在q个[.d]文g 中加入的不只是依赖关p,包括生成的命令也可一q加入,让每个[.d]文g都包含一个完赖的规则。一旦我们完成这个工作,接下来,我们p把这些自动生? 的规则放q我们的主Makefile中。我们可以用Makefile?#8220;include”命oQ来引入别的Makefile文gQ前面讲q)Q例如:
sources = foo.c bar.c
include $(sources:.c=.d)
上述语句中的“$(sources:.c=.d)”中的“.c=.d”的意思是做一个替换,? 变量$(sources)所有[.c]的字串都替换成[.d]Q关于这?#8220;替换”的内容,在后面我会有更ؓ详细的讲q。当Ӟ你得注意ơ序Q因? include是按ơ来载入文gQ最先蝲入的[.d]文g中的目标会成为默认目标?/font>
每条规则中的命o和操作系lShell的命令行是一致的。make会一按顺序一条一条的执行? 令,每条命o的开头必M[Tab]键开_除非Q命令是紧跟在依赖规则后面的分号后的。在命o行之间中的空格或是空行会被忽略,但是如果该空格或I? 以Tab键开头的Q那么make会认为其是一个空命o?/font>
我们在UNIX下可能会使用不同的ShellQ但是make的命令默认是?#8220;/bin/sh”——UNIX的标准Shell解释执行的。除非你特别指定一个其它的Shell。Makefile中,“#”是注释符Q很像C/C++中的“//”Q其后的本行字符都被注释?/font>
一、显C命?/strong>
通常Qmake会把其要执行的命令行在命令执行前输出到屏q上。当我们?#8220;@”字符在命令行前,那么Q这个命令将不被make昄出来Q最具代表性的例子是,我们用这个功能来像屏q显CZ些信息。如Q?/font>
@echo 正在~译XXX模块......
当make执行Ӟ会输?#8220;正在~译XXX模块......”字串Q但不会输出命oQ如果没?#8220;@”Q那么,make输出:
echo 正在~译XXX模块......
正在~译XXX模块......
如果make执行Ӟ带入make参数“-n”?#8220;--just-print”Q那么其只是昄命oQ但不会执行命oQ这个功能很有利于我们调试我们的MakefileQ看看我们书写的命o是执行v来是什么样子的或是什么顺序的?/font>
而make参数“-s”?#8220;--slient”则是全面止命o的显C?/font>
二、命令执?/strong>
当依赖目标新于目标时Q也是当规则的目标需要被更新Ӟmake会一条一条的执行其后的命 令。需要注意的是,如果你要让上一条命令的l果应用在下一条命令时Q你应该使用分号分隔q两条命令。比如你的第一条命令是cd命oQ你希望W二条命令得? cd之后的基上运行,那么你就不能把这两条命o写在两行上,而应该把q两条命令写在一行上Q用分号分隔。如Q?/font>
CZ一Q?br> exec:
cd /home/hchen
pwd
CZ二:
exec:
cd /home/hchen; pwd
当我们执?#8220;make exec”ӞW一个例子中的cd没有作用Qpwd会打印出当前的Makefile目录Q而第二个例子中,cdpv作用了,pwd会打印出“/home/hchen”?/font>
make一般是使用环境变量SHELL中所定义的系lShell来执行命令,默认情况下? UNIX的标准Shell—?bin/sh来执行命令。但在MS-DOS下有点特D,因ؓMS-DOS下没有SHELL环境变量Q当然你也可以指定。如 果你指定了UNIX风格的目录Ş式,首先Qmake会在SHELL所指定的\径中扑֯命o解释器,如果找不刎ͼ其会在当前盘W中的当前目录中LQ如果再 找不刎ͼ其会在PATH环境变量中所定义的所有\径中L。MS-DOS中,如果你定义的命o解释器没有找刎ͼ其会l你的命令解释器加上诸如 “.exe”?#8220;.com”?#8220;.bat”?#8220;.sh”{后~?/font>
三、命令出?/strong>
每当命oq行完后Qmake会检每个命令的q回码,如果命oq回成功Q那么make会执行下 一条命令,当规则中所有的命o成功q回后,q个规则q是成功完成了。如果一个规则中的某个命令出错了Q命令退出码非零Q,那么make׃l止执行当前 规则Q这有可能l止所有规则的执行?/font>
有些时候,命o的出错ƈ不表C就是错误的。例如mkdir命oQ我们一定需要徏立一个目录,? 果目录不存在Q那么mkdir成功执行,万事大吉Q如果目录存在,那么出错了。我们之所以用mkdir的意思就是一定要有这L一个目录,于是我们 ׃希望mkdir出错而终止规则的q行?/font>
Z做到q一点,忽略命o的出错,我们可以在Makefile的命令行前加一个减?#8220;-”Q在Tab键之后)Q标Cؓ不管命oZ出错都认为是成功的。如Q?/font>
clean:
-rm -f *.o
q有一个全局的办法是Q给make加上“-i”或是“--ignore-errors”参数Q? 那么QMakefile中所有命令都会忽略错误。而如果一个规则是?#8220;.IGNORE”作ؓ目标的,那么q个规则中的所有命令将会忽略错误。这些是不同U? 别的防止命o出错的方法,你可以根据你的不同喜Ƣ设|?/font>
q有一个要提一下的make的参数的?#8220;-k”或是“--keep-going”Q这个参数的意思是Q如果某规则中的命o出错了,那么q目该规则的执行,但l执行其它规则?/font>
四、嵌套执行make
在一些大的工E中Q我们会把我们不同模块或是不同功能的源文件放在不同的目录中,我们可以在每 个目录中都书写一个该目录的MakefileQ这有利于让我们的Makefile变得更加地简z,而不至于把所有的东西全部写在一个Makefile中, q样会很隄护我们的MakefileQ这个技术对于我们模块编译和分段~译有着非常大的好处?/font>
例如Q我们有一个子目录叫subdirQ这个目录下有个Makefile文gQ来指明了这个目录下文g的编译规则。那么我们L的Makefile可以q样书写Q?/font>
subsystem:
cd subdir && $(MAKE)
其等价于Q?/font>
subsystem:
$(MAKE) -C subdir
定义$(MAKE)宏变量的意思是Q也许我们的make需要一些参敎ͼ所以定义成一个变量比较利于维护。这两个例子的意思都是先q入“subdir”目录Q然后执行make命o?/font>
我们把这个Makefile叫做“LMakefile”QLMakefile的变量可以传递到下的Makefile中(如果你显C的声明Q,但是不会覆盖下层的Makefile中所定义的变量,除非指定?#8220;-e”参数?/font>
如果你要传递变量到下Makefile中,那么你可以用这L声明Q?/font>
export <variable ...>
如果你不惌某些变量传递到下Makefile中,那么你可以这样声明:
unexport <variable ...>
如:
CZ一Q?/font>
export variable = value
其等价于Q?/font>
variable = value
export variable
其等价于Q?/font>
export variable := value
其等价于Q?/font>
variable := value
export variable
CZ二:
export variable += value
其等价于Q?/font>
variable += value
export variable
如果你要传递所有的变量Q那么,只要一个exportp了。后面什么也不用跟,表示传递所有的变量?/font>
需要注意的是,有两个变量,一个是SHELLQ一个是MAKEFLAGSQ这两个变量不管你是 否exportQ其L要传递到下层Makefile中,特别是MAKEFILES变量Q其中包含了make的参C息,如果我们执行“L Makefile”时有make参数或是在上层Makefile中定义了q个变量Q那么MAKEFILES变量会是这些参敎ͼq会传递到下层 Makefile中,q是一个系l的环境变量?/font>
但是make命o中的有几个参数ƈ不往下传递,它们?#8220;-C”,“-f”,“-h”“-o”?#8220;-W”Q有关Makefile参数的细节将在后面说明)Q如果你不想往下层传递参敎ͼ那么Q你可以q样来:
subsystem:
cd subdir && $(MAKE) MAKEFLAGS=
如果你定义了环境变量MAKEFLAGSQ那么你得确信其中的选项是大安会用到的Q如果其中有“-t”,“-n”,?#8220;-q”参数Q那么将会有让你意想不到的结果,或许会让你异常地恐慌?/font>
q有一个在“嵌套执行”中比较有用的参数Q?#8220;-w”或是“--print- directory”会在make的过E中输出一些信息,让你看到目前的工作目录。比如,如果我们的下Umake目录?#8220;/home/hchen/gnu /make”Q如果我们?#8220;make -w”来执行,那么当进入该目录Ӟ我们会看刎ͼ
make: Entering directory `/home/hchen/gnu/make'.
而在完成下层make后离开目录Ӟ我们会看刎ͼ
make: Leaving directory `/home/hchen/gnu/make'
当你使用“-C”参数来指定make下层MakefileӞ“-w”会被自动打开的。如果参C?#8220;-s”Q?#8220;--slient”Q或?#8220;--no-print-directory”Q那么,“-w”L失效的?/font>
五、定义命令包
如果Makefile中出C些相同命令序列,那么我们可以些相同的命o序列定义一个变量。定义这U命令序列的语法?#8220;define”开始,?#8220;endef”l束Q如Q?/font>
define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef
q里Q?#8220;run-yacc”是这个命令包的名字,其不要和Makefile中的变量重名。在 “define”?#8220;endef”中的两行是命o序列。这个命令包中的W一个命令是q行YaccE序Q因为YaccE序L生成“y.tab.c”的文 Ӟ所以第二行的命令就是把q个文gҎ名字。还是把q个命o包放C个示例中来看看吧?/font>
foo.c : foo.y
$(run-yacc)
我们可以看见Q要使用q个命o包,我们好像用变量一栗在q个命o包的使用中,命o? “run-yacc”中的“$^”是“foo.y”Q?#8220;$@”是“foo.c”Q有兌U以“$”开头的Ҏ变量Q我们会在后面介l)Qmake在执 行命令包Ӟ命o包中的每个命令会被依ơ独立执行?/font>
概述
—?/strong>
什么是makefileQ或许很多Winodws的程序员都不知道q个东西Q因为那? Windows的IDE都ؓ你做了这个工作,但我觉得要作一个好的和professional的程序员Qmakefileq是要懂。这好像现在有q么? 的HTML的编辑器Q但如果你想成ؓ一个专业h士,你还是要了解HTML的标识的含义。特别在Unix下的软g~译Q你׃能不自己写makefile 了,会不会写makefileQ从一个侧面说明了一个h是否具备完成大型工程的能力?/font>
因ؓQmakefile关系C整个工程的编译规则。一个工E中的源文g不计敎ͼ其按cd、功 能、模块分别放在若q个目录中,makefile定义了一pd的规则来指定Q哪些文仉要先~译Q哪些文仉要后~译Q哪些文仉要重新编译,甚至于进? 更复杂的功能操作Q因为makefile像一个Shell脚本一P其中也可以执行操作系l的命o?/font>
makefile带来的好处就是—?#8220;自动化编?#8221;Q一旦写好,只需要一个make命oQ整? 工程完全自动~译Q极大的提高了Y件开发的效率。make是一个命令工P是一个解释makefile中指令的命o工具Q一般来_大多数的IDE都有q? 个命令,比如QDelphi的makeQVisual C++的nmakeQLinux下GNU的make。可见,makefile都成Z一U在工程斚w的编译方法?/font>
现在讲述如何写makefile的文章比较少Q这是我惛_q篇文章的原因。当Ӟ不同产商? make各不相同Q也有不同的语法Q但其本质都是在“文g依赖?#8221;上做文章Q这里,我仅对GNU的makeq行讲述Q我的环境是RedHat Linux 8.0Qmake的版本是3.80。必竟,q个make是应用最为广泛的Q也是用得最多的。而且其还是最遵@于IEEE 1003.2-1992 标准的(POSIX.2Q?/font>
在这文档中Q将以C/C++的源码作为我们基Q所以必然涉及一些关于C/C++的编译的知识Q相关于q方面的内容Q还请各位查看相关的~译器的文档。这里所默认的编译器是UNIX下的GCC和CC?/font>
关于E序的编译和链接
—————————?/strong>
在此Q我惛_说关于程序编译的一些规范和ҎQ一般来_无论是C、C++、还是pasQ首? 要把源文件编译成中间代码文gQ在Windows下也是 .obj 文gQUNIX下是 .o 文gQ即 Object FileQ这个动作叫做编译(compileQ。然后再把大量的Object File合成执行文gQ这个动作叫作链接(linkQ?/font>
~译Ӟ~译器需要的是语法的正确Q函C变量的声明的正确。对于后者,通常是你需要告诉编? 器头文g的所在位|(头文件中应该只是声明Q而定义应该放在C/C++文g中)Q只要所有的语法正确Q编译器可以编译出中间目标文g。一般来_每个? 文g都应该对应于一个中间目标文ӞO文g或是OBJ文gQ?/font>
链接Ӟ主要是链接函数和全局变量Q所以,我们可以使用q些中间目标文gQO文g或是OBJ? Ӟ来链接我们的应用E序。链接器q不函数所在的源文Ӟ只管函数的中间目标文ӞObject FileQ,在大多数时候,׃源文件太多,~译生成的中间目标文件太多,而在链接旉要明昑֜指出中间目标文g名,q对于编译很不方便,所以,我们要给 中间目标文g打个包,在Windows下这U包?#8220;库文?#8221;QLibrary File)Q也是 .lib 文gQ在UNIX下,是Archive FileQ也是 .a 文g?/font>
ȝ一下,源文仉先会生成中间目标文gQ再׃间目标文件生成执行文件。在~译Ӟ~译器只 程序语法,和函数、变量是否被声明。如果函数未被声明,~译器会l出一个警告,但可以生成Object File。而在链接E序Ӟ链接器会在所有的Object File中找d数的实现Q如果找不到Q那到就会报链接错误码(Linker ErrorQ,在VC下,q种错误一般是QLink 2001错误Q意思说是说Q链接器未能扑ֈ函数的实现。你需要指定函数的Object File.
好,a归正传,GNU的make有许多的内容Q闲a叙Q还是让我们开始吧?/font>
Makefile 介绍
——————?/strong>
make命o执行Ӟ需要一?Makefile 文gQ以告诉make命o需要怎么Lȝ译和链接E序?/font>
首先Q我们用一个示例来说明Makefile的书写规则。以便给大家一个感兴认识。这个示例来源于GNU的make使用手册Q在q个CZ中,我们的工E有8个C文gQ和3个头文gQ我们要写一个Makefile来告诉make命o如何~译和链接这几个文g。我们的规则是:
1Q如果这个工E没有编译过Q那么我们的所有C文g都要~译q被链接?br> 2Q如果这个工E的某几个C文g被修改,那么我们只编译被修改的C文gQƈ链接目标E序?br> 3Q如果这个工E的头文件被改变了,那么我们需要编译引用了q几个头文g的C文gQƈ链接目标E序?/font>
只要我们的Makefile写得够好Q所有的q一切,我们只用一个make命o可以完成,make命o会自动智能地Ҏ当前的文件修改的情况来确定哪些文仉要重~译Q从而自q译所需要的文g和链接目标程序?/font>
一、Makefile的规?/strong>
在讲q这个Makefile之前Q还是让我们先来_略地看一看Makefile的规则?/font>
target ... : prerequisites ...
command
...
...
target也就是一个目标文Ӟ可以是Object FileQ也可以是执行文件。还可以是一个标{(LabelQ,对于标签q种Ҏ,在后l的“伪目?#8221;章节中会有叙q?/font>
prerequisites是Q要生成那个target所需要的文g或是目标?/font>
command也就是make需要执行的命o。(L的Shell命oQ?/font>
q是一个文件的依赖关系Q也是_targetq一个或多个的目标文件依赖于 prerequisites中的文gQ其生成规则定义在command中。说白一点就是说Qprerequisites中如果有一个以上的文g? target文g要新的话Qcommand所定义的命令就会被执行。这是Makefile的规则。也是Makefile中最核心的内宏V?/font>
说到底,Makefile的东西就是这样一点,好像我的q篇文档也该l束了。呵c还不尽Ӟq是Makefile的主U和核心Q但要写好一个Makefileq不够,我会以后面一点一点地l合我的工作l验l你慢慢到来。内容还多着呢。:Q?/font>
二、一个示?/strong>
正如前面所说的Q如果一个工E有3个头文gQ和8个C文gQ我们ؓ了完成前面所q的那三个规则,我们的Makefile应该是下面的q个样子的?/font>
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
反斜杠(\Q是换行W的意思。这h较便于Makefile的易诅R我们可以把q个内容保存? 文g?#8220;Makefile”?#8220;makefile”的文件中Q然后在该目录下直接输入命o“make”可以生成执行文件edit。如果要删除执行文g? 所有的中间目标文gQ那么,只要单地执行一?#8220;make clean”可以了?/font>
在这个makefile中,目标文gQtargetQ包含:执行文gedit和中间目标文? Q?.oQ,依赖文gQprerequisitesQ就是冒号后面的那些 .c 文g?.h文g。每一?.o 文g都有一l依赖文Ӟ而这?.o 文g又是执行文g edit 的依赖文件。依赖关pȝ实质上就是说明了目标文g是由哪些文g生成的,换言之,目标文g是哪些文件更新的?/font>
在定义好依赖关系后,后箋的那一行定义了如何生成目标文g的操作系l命令,一定要以一个Tab 键作为开头。记住,makeq不命令是怎么工作的,他只执行所定义的命令。make会比较targets文g和prerequisites文g的修? 日期Q如果prerequisites文g的日期要比targets文g的日期要斎ͼ或者target不存在的话,那么Qmake׃执行后箋定义的命 令?/font>
q里要说明一点的是,clean不是一个文Ӟ它只不过是一个动作名字,有点像C语言中的 lable一P其冒号后什么也没有Q那么,make׃会自动去找文件的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令,p在make 命o后明昑־指出q个lable的名字。这LҎ非常有用Q我们可以在一个makefile中定义不用的~译或是和编译无关的命oQ比如程序的打包Q程 序的备䆾Q等{?/font>
三、make是如何工作的
在默认的方式下,也就是我们只输入make命o。那么,
1、make会在当前目录下找名字?#8220;Makefile”?#8220;makefile”的文件?br> 2、如果找刎ͼ它会找文件中的第一个目标文ӞtargetQ,在上面的例子中,他会扑ֈ“edit”q个文gQƈ把这个文件作为最l的目标文g?br> 3、如果edit文g不存在,或是edit所依赖的后面的 .o 文g的文件修Ҏ间要比editq个文g斎ͼ那么Q他׃执行后面所定义的命令来生成editq个文g?br> 4、如果edit所依赖?o文g也存在,那么make会在当前文g中找目标?o文g的依赖性,如果扑ֈ则再Ҏ那一个规则生?o文g。(q有点像一个堆栈的q程Q?br> 5、当Ӟ你的C文g和H文g是存在的啦,于是make会生?.o 文gQ然后再?.o 文g生命make的终极Q务,也就是执行文件edit了?/font>
q就是整个make的依赖性,make会一层又一层地L文g的依赖关p,直到最l编译出W一 个目标文件。在扑֯的过E中Q如果出现错误,比如最后被依赖的文件找不到Q那么make׃直接退出,q报错,而对于所定义的命令的错误Q或是编译不? 功,makeҎ不理。make只管文g的依赖性,卻I如果在我找了依赖关系之后Q冒号后面的文gq是不在Q那么对不vQ我׃工作啦?/font>
通过上述分析Q我们知道,像cleanq种Q没有被W一个目标文件直接或间接兌Q那么它后面所定义的命令将不会被自动执行,不过Q我们可以显Cmake执行。即命o—?#8220;make clean”Q以此来清除所有的目标文gQ以侉K~译?/font>
于是在我们编E中Q如果这个工E已被编译过了,当我们修改了其中一个源文gQ比? file.cQ那么根据我们的依赖性,我们的目标file.o会被重编译(也就是在q个依性关pd面所定义的命令)Q于是file.o的文件也是最新的 啦,于是file.o的文件修Ҏ间要比edit要新Q所以edit也会被重新链接了Q详见edit目标文g后定义的命oQ?/font>
而如果我们改变了“command.h”Q那么,kdb.o、command.o和files.o都会被重~译Qƈ且,edit会被重链接?/font>
四、makefile中用变?/strong>
在上面的例子中,先让我们看看edit的规则:
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
我们可以看到[.o]文g的字W串被重复了两次Q如果我们的工程需要加入一个新的[.o]? Ӟ那么我们需要在两个地方加(应该是三个地方,q有一个地方在clean中)。当Ӟ我们的makefileq不复杂Q所以在两个地方加也不篏Q但如果 makefile变得复杂Q那么我们就有可能会忘掉一个需要加入的地方Q而导致编译失败。所以,Zmakefile的易l护Q在makefile中我? 可以使用变量。makefile的变量也是一个字W串Q理解成C语言中的宏可能会更好?/font>
比如Q我们声明一个变量,叫objects, OBJECTS, objs, OBJS, obj, 或是 OBJQ反正不什么啦Q只要能够表Cobj文gp了。我们在makefile一开始就q样定义Q?/font>
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
于是Q我们就可以很方便地在我们的makefile中以“$(objects)”的方式来使用q个变量了,于是我们的改良版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 : 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)
于是如果有新?.o 文g加入Q我们只需单地修改一?objects 变量可以了?/font>
关于变量更多的话题,我会在后l给你一一道来?/font>
五、让make自动推导
GNU的make很强大,它可以自动推导文件以及文件依赖关pd面的命oQ于是我们就没必要去在每一个[.o]文g后都写上cM的命令,因ؓQ我们的make会自动识别,q自己推导命令?/font>
只要make看到一个[.o]文gQ它׃自动的把[.c]文g加在依赖关系中,如果make 扑ֈ一个whatever.oQ那么whatever.cQ就会是whatever.o的依赖文件。ƈ?cc -c whatever.c 也会被推导出来,于是Q我们的makefile再也不用写得q么复杂。我们的是新的makefile又出炉了?/font>
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)
q种ҎQ也是make?#8220;隐晦规则”。上面文件内容中Q?#8220;.PHONY”表示Qclean是个伪目标文件?/font>
关于更ؓ详细?#8220;隐晦规则”?#8220;伪目标文?#8221;Q我会在后箋l你一一道来?/font>
六、另c风格的makefile
即然我们的make可以自动推导命oQ那么我看到那堆[.o]和[.h]的依赖就有点不爽Q那么多的重复的[.h]Q能不能把其收拢hQ好吧,没有问题Q这个对于make来说很容易,谁叫它提供了自动推导命o和文件的功能呢?来看看最新风格的makefile吧?/font>
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
$(objects) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h
.PHONY : clean
clean :
rm edit $(objects)
q种风格Q让我们的makefile变得很简单,但我们的文g依赖关系显得有点凌׃。鱼和熊掌不可兼得。还看你的喜好了。我是不喜欢q种风格的,一是文件的依赖关系看不清楚Q二是如果文件一多,要加入几个新?o文gQ那q不清楚了?/font>
七、清I目标文件的规则
每个Makefile中都应该写一个清I目标文Ӟ.o和执行文Ӟ的规则,q不仅便于重~译Q也很利于保持文件的清洁。这是一?#8220;修养”Q呵呵,q记得我的《编E修充R吗Q。一般的风格都是Q?/font>
clean:
rm edit $(objects)
更ؓE_的做法是Q?/font>
.PHONY : clean
clean :
-rm edit $(objects)
前面说过Q?PHONY意思表Cclean是一?#8220;伪目?#8221;Q。而在rm命o前面加了一个小? L意思就是,也许某些文g出现问题Q但不要,l箋做后面的事。当Ӟclean的规则不要放在文件的开_不然Q这׃变成make的默认目标,怿 谁也不愿意这栗不成文的规矩是—?#8220;clean从来都是攑֜文g的最?#8221;?/font>
上面是一个makefile的概貌,也是makefile的基Q下面还有很多makefile的相关细节,准备好了吗?准备好了来?/font>
一、Makefile里有什么?
Makefile里主要包含了五个东西Q显式规则、隐晦规则、变量定义、文件指C和注释?/font>
1、显式规则。显式规则说明了Q如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文gQ文件的依赖文gQ生成的命o?/font>
2、隐晦规则。由于我们的make有自动推导的功能Q所以隐晦的规则可以让我们比较粗p地略地书写MakefileQ这是由make所支持的?/font>
3、变量的定义。在Makefile中我们要定义一pd的变量,变量一般都是字W串Q这个有点你C语言中的宏,当Makefile被执行时Q其中的变量都会被扩展到相应的引用位|上?/font>
4、文件指C。其包括了三个部分,一个是在一个Makefile中引用另一? MakefileQ就像C语言中的include一P另一个是指根据某些情冉|定Makefile中的有效部分Q就像C语言中的预编?if一Pq有 是定义一个多行的命o。有兌一部分的内容,我会在后l的部分中讲q?/font>
5、注释。Makefile中只有行注释Q和UNIX的Shell脚本一P其注释是?#8220;#”字符Q这个就像C/C++中的“//”一栗如果你要在你的Makefile中?#8220;#”字符Q可以用反斜框进行{义,如:“\#”?/font>
最后,q值得一提的是,在Makefile中的命oQ必要以[Tab]键开始?/font>
二、Makefile的文件名
默认的情况下Qmake命o会在当前目录下按序扑֯文g名ؓ“GNUmakefile”? “makefile”?#8220;Makefile”的文Ӟ扑ֈ了解释这个文件。在q三个文件名中,最好?#8220;Makefile”q个文g名,因ؓQ这个文件名 W一个字Wؓ大写Q这h一U显目的感觉。最好不要用“GNUmakefile”Q这个文件是GNU的make识别的。有另外一些make只对全小写的 “makefile”文g名敏感,但是基本上来_大多数的make都支?#8220;makefile”?#8220;Makefile”q两U默认文件名?/font>
当然Q你可以使用别的文g名来书写MakefileQ比 如:“Make.Linux”Q?#8220;Make.Solaris”Q?#8220;Make.AIX”{,如果要指定特定的MakefileQ你可以使用make?#8220;- f”?#8220;--file”参数Q如Qmake -f Make.Linux或make --file Make.AIX?/font>
三、引用其它的Makefile
在Makefile使用include关键字可以把别的Makefile包含q来Q这很像C语言?includeQ被包含的文件会原模原样的放在当前文件的包含位置。include的语法是Q?/font>
include <filename>
filename可以是当前操作系lShell的文件模式(可以保含路径和通配W)
在include前面可以有一些空字符Q但是绝不能是[Tab]键开始。include ?lt;filename>可以用一个或多个I格隔开。D个例子,你有q样几个MakefileQa.mk、b.mk、c.mkQ还有一个文件叫 foo.makeQ以及一个变?(bar)Q其包含了e.mk和f.mkQ那么,下面的语句:
include foo.make *.mk $(bar)
{h于:
include foo.make a.mk b.mk c.mk e.mk f.mk
make命o开始时Q会把找寻include所指出的其它MakefileQƈ把其内容安置? 当前的位|。就好像C/C++?include指o一栗如果文仉没有指定l对路径或是相对路径的话Qmake会在当前目录下首先寻找,如果当前目录 下没有找刎ͼ那么Qmakeq会在下面的几个目录下找Q?/font>
1、如果make执行Ӟ?#8220;-I”?#8220;--include-dir”参数Q那么make׃在这个参数所指定的目录下d找?br> 2、如果目?lt;prefix>/includeQ一般是Q?usr/local/bin?usr/includeQ存在的话,make也会L?/font>
如果有文件没有找到的话,make会生成一条警告信息,但不会马上出现致命错误。它会l蝲? 其它的文Ӟ一旦完成makefile的读取,make会再重试q些没有扑ֈQ或是不能读取的文gQ如果还是不行,make才会出现一条致命信息。如果你 惌make不理那些无法d的文Ӟ而l执行,你可以在include前加一个减?#8220;-”。如Q?/font>
-include <filename>
其表C,无论includeq程中出C么错误,都不要报错l执行。和其它版本make兼容的相兛_令是sincludeQ其作用和这一个是一L?/font>
四、环境变?MAKEFILES
如果你的当前环境中定义了环境变量MAKEFILESQ那么,make会把q个变量中的值做一 个类ginclude的动作。这个变量中的值是其它的MakefileQ用I格分隔。只是,它和include不同的是Q从q个环境变中引入? Makefile?#8220;目标”不会起作用,如果环境变量中定义的文g发现错误Qmake也会不理?/font>
但是在这里我q是不要使用q个环境变量Q因为只要这个变量一被定义,那么当你使用make Ӟ所有的Makefile都会受到它的影响Q这l不是你想看到的。在q里提这个事Q只是ؓ了告诉大Ӟ也许有时候你的Makefile出现了怪事Q那? 你可以看看当前环境中有没有定义这个变量?/font>
五、make的工作方?/font>
GNU的make工作时的执行步骤入下Q(x其它的make也是cMQ?/font>
1、读入所有的Makefile?br> 2、读入被include的其它Makefile?br> 3、初始化文g中的变量?br> 4、推导隐晦规则,q分析所有规则?br> 5、ؓ所有的目标文g创徏依赖关系链?br> 6、根据依赖关p,军_哪些目标要重新生成?br> 7、执行生成命令?/font>
1-5步ؓW一个阶D,6-7为第二个阶段。第一个阶D中Q如果定义的变量被用了Q那 么,make会把其展开在用的位置。但makeq不会完全马上展开Qmake使用的是拖g战术Q如果变量出现在依赖关系的规则中Q那么仅当这条依赖被? 定要使用了,变量才会在其内部展开?/font>
当然Q这个工作方式你不一定要清楚Q但是知道这个方式你也会对make更ؓ熟悉。有了这个基Q后l部分也容易看懂了?/font>