[转]详细易懂的Linux makefile教程(4)

8、目标变量

前面咱们所讲的在Makefile中定义的变量都是“全局变量”,在整个文件,咱们均可以访问这些变量。固然,“自动化变量”除外,如“$<”等这种类量的自动化变量就属于“规则型变量”,这种变量的值依赖于规则的目标和依赖目标的定义。express

固然,我样一样能够为某个目标设置局部变量,这种变量被称为“Target-specific Variable”,它能够和“全局变量”同名,由于它的做用范围只在这条规则以及连带规则中,因此其值也只在做用范围内有效。而不会影响规则链之外的全局变量的值。安全


其语法是:ide

<target ...> : <variable-assignment>函数

<target ...> : overide <variable-assignment>测试

<variable-assignment>能够是前面讲过的各类赋值表达式,如“=”、“:=”、“+=”或是“?=”。第二个语法是针对于make命令行带入的变量,或是系统环境变量。spa

这个特性很是的有用,当咱们设置了这样一个变量,这个变量会做用到由这个目标所引起的全部的规则中去。如:命令行

prog : CFLAGS = -g
prog : prog.o foo.o bar.o
$(CC) $(CFLAGS) prog.o foo.o bar.oorm

prog.o : prog.c
$(CC) $(CFLAGS) prog.c排序

foo.o : foo.c
$(CC) $(CFLAGS) foo.cip

bar.o : bar.c
$(CC) $(CFLAGS) bar.c

在这个示例中,无论全局的$(CFLAGS)的值是什么,在prog目标,以及其所引起的全部规则中(prog.o foo.o bar.o的规则),$(CFLAGS)的值都是“-g”


9、模式变量

在GNU的make中,还支持模式变量(Pattern-specific Variable),经过上面的目标变量中,咱们知道,变量能够定义在某个目标上。模式变量的好处就是,咱们能够给定一种“模式”,能够把变量定义在符合这种模式的全部目标上。

咱们知道,make的“模式”通常是至少含有一个“%”的,因此,咱们能够以以下方式给全部以[.o]结尾的目标定义目标变量:

%.o : CFLAGS = -O

一样,模式变量的语法和“目标变量”同样:

<pattern ...> : <variable-assignment>

<pattern ...> : override <variable-assignment>

override一样是针对于系统环境传入的变量,或是make命令行指定的变量。

 

使用条件判断
——————

使用条件判断,可让make根据运行时的不一样状况选择不一样的执行分支。条件表达式能够是比较变量的值,或是比较变量和常量的值。

1、示例

下面的例子,判断$(CC)变量是否“gcc”,若是是的话,则使用GNU函数编译目标。

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

可见,在上面示例的这个规则中,目标“foo”能够根据变量“$(CC)”值来选取不一样的函数库来编译程序。

咱们能够从上面的示例中看到三个关键字:ifeq、else和endif。ifeq的意思表示条件语句的开始,并指定一个条件表达式,表达式包含两 个参数,以逗号分隔,表达式以圆括号括起。else表示条件表达式为假的状况。endif表示一个条件语句的结束,任何一个条件表达式都应该以endif 结束。

当咱们的变量$(CC)值是“gcc”时,目标foo的规则是:

foo: $(objects)
$(CC) -o foo $(objects) $(libs_for_gcc)

而当咱们的变量$(CC)值不是“gcc”时(好比“cc”),目标foo的规则是:

foo: $(objects)
$(CC) -o foo $(objects) $(normal_libs)

固然,咱们还能够把上面的那个例子写得更简洁一些:

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)

 

2、语法

条件表达式的语法为:

<conditional-directive>
<text-if-true>
endif

以及:

<conditional-directive>
<text-if-true>
else
<text-if-false>
endif


其中<conditional-directive>表示条件关键字,如“ifeq”。这个关键字有四个。

第一个是咱们前面所见过的“ifeq”

ifeq (<arg1>, <arg2> )
ifeq '<arg1>' '<arg2>'
ifeq "<arg1>" "<arg2>"
ifeq "<arg1>" '<arg2>'
ifeq '<arg1>' "<arg2>"

比较参数“arg1”和“arg2”的值是否相同。固然,参数中咱们还可使用make的函数。如:

ifeq ($(strip $(foo)),)
<text-if-empty>
endif

这个示例中使用了“strip”函数,若是这个函数的返回值是空(Empty),那么<text-if-empty>就生效。

第二个条件关键字是“ifneq”。语法是:

ifneq (<arg1>, <arg2> )
ifneq '<arg1>' '<arg2>'
ifneq "<arg1>" "<arg2>"
ifneq "<arg1>" '<arg2>'
ifneq '<arg1>' "<arg2>"

其比较参数“arg1”和“arg2”的值是否相同,若是不一样,则为真。和“ifeq”相似。

第三个条件关键字是“ifdef”。语法是:

ifdef <variable-name>

若是变量<variable-name>的值非空,那到表达式为真。不然,表达式为假。固然,<variable- name>一样能够是一个函数的返回值。注意,ifdef只是测试一个变量是否有值,其并不会把变量扩展到当前位置。仍是来看两个例子:

示例一:
bar =
foo = $(bar)
ifdef foo
frobozz = yes
else
frobozz = no
endif

示例二:
foo =
ifdef foo
frobozz = yes
else
frobozz = no
endif

第一个例子中,“$(frobozz)”值是“yes”,第二个则是“no”。

第四个条件关键字是“ifndef”。其语法是:

ifndef <variable-name>

这个我就很少说了,和“ifdef”是相反的意思。

在<conditional-directive>这一行上,多余的空格是被容许的,可是不能以[Tab]键作为开始(否则就被认为是命令)。而注释符“#”一样也是安全的。“else”和“endif”也同样,只要不是以[Tab]键开始就好了。

特别注意的是,make是在读取Makefile时就计算条件表达式的值,并根据条件表达式的值来选择语句,因此,你最好不要把自动化变量(如“$@”等)放入条件表达式中,由于自动化变量是在运行时才有的。

并且,为了不混乱,make不容许把整个条件语句分红两部分放在不一样的文件中。

 

使用函数
————

在Makefile中可使用函数来处理变量,从而让咱们的命令或是规则更为的灵活和具备智能。make所支持的函数也不算不少,不过已经足够咱们的操做了。函数调用后,函数的返回值能够当作变量来使用。


1、函数的调用语法

函数调用,很像变量的使用,也是以“$”来标识的,其语法以下:

$(<function> <arguments> )


或是

${<function> <arguments>}

这里,<function>就是函数名,make支持的函数很少。<arguments>是函数的参数,参数间以逗号 “,”分隔,而函数名和参数之间以“空格”分隔。函数调用以“$”开头,以圆括号或花括号把函数名和参数括起。感受很像一个变量,是否是?函数中的参数可 以使用变量,为了风格的统一,函数和变量的括号最好同样,如使用“$(subst a,b,$(x))”这样的形式,而不是“$(subst a,b,${x})”的形式。由于统一会更清楚,也会减小一些没必要要的麻烦。

仍是来看一个示例:

comma:= ,
empty:=
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))

在这个示例中,$(comma)的值是一个逗号。$(space)使用了$(empty)定义了一个空格,$(foo)的值是“a b c”,$(bar)的定义用,调用了函数“subst”,这是一个替换函数,这个函数有三个参数,第一个参数是被替换字串,第二个参数是替换字串,第三个 参数是替换操做做用的字串。这个函数也就是把$(foo)中的空格替换成逗号,因此$(bar)的值是“a,b,c”。


2、字符串处理函数

$(subst <from>,<to>,<text> )

名称:字符串替换函数——subst。
功能:把字串<text>中的<from>字符串替换成<to>。
返回:函数返回被替换事后的字符串。

示例:

$(subst ee,EE,feet on the street),

把“feet on the street”中的“ee”替换成“EE”,返回结果是“fEEt on the strEEt”。


$(patsubst <pattern>,<replacement>,<text> )

名称:模式字符串替换函数——patsubst。
功能:查找<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”

备注:

这和咱们前面“变量章节”说过的相关知识有点类似。如:

“$(var:<pattern>=<replacement> )”
至关于
“$(patsubst <pattern>,<replacement>,$(var))”,

而“$(var: <suffix>=<replacement> )”
则至关于
“$(patsubst %<suffix>,%<replacement>,$(var))”。

例若有:objects = foo.o bar.o baz.o,
那么,“$(objects:.o=.c)”和“$(patsubst %.o,%.c,$(objects))”是同样的。

$(strip <string> )

名称:去空格函数——strip。
功能:去掉<string>字串中开头和结尾的空字符。
返回:返回被去掉空格的字符串值。
示例:

$(strip a b c )

把字串“a b c ”去到开头和结尾的空格,结果是“a b c”。

$(findstring <find>,<in> )

名称:查找字符串函数——findstring。
功能:在字串<in>中查找<find>字串。
返回:若是找到,那么返回<find>,不然返回空字符串。
示例:

$(findstring a,a b c)
$(findstring a,b c)

第一个函数返回“a”字符串,第二个返回“”字符串(空字符串)

$(filter <pattern...>,<text> )

名称:过滤函数——filter。
功能:以<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”。

$(filter-out <pattern...>,<text> )

名称:反过滤函数——filter-out。
功能:以<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”。

$(sort <list> )

名称:排序函数——sort。
功能:给字符串<list>中的单词排序(升序)。
返回:返回排序后的字符串。
示例:$(sort foo bar lose)返回“bar foo lose” 。
备注:sort函数会去掉<list>中相同的单词。

$(word <n>,<text> )

名称:取单词函数——word。
功能:取字符串<text>中第<n>个单词。(从一开始)
返回:返回字符串<text>中第<n>个单词。若是<n>比<text>中的单词数要大,那么返回空字符串。
示例:$(word 2, foo bar baz)返回值是“bar”。

$(wordlist <s>,<e>,<text> )

名称:取单词串函数——wordlist。
功能:从字符串<text>中取从<s>开始到<e>的单词串。<s>和<e>是一个数字。
返 回:返回字符串<text>中从<s>到<e>的单词字串。若是<s>比<text>中的 单词数要大,那么返回空字符串。若是<e>大于<text>的单词数,那么返回从<s>开始, 到<text>结束的单词串。
示例: $(wordlist 2, 3, foo bar baz)返回值是“bar baz”。

$(words <text> )

名称:单词个数统计函数——words。
功能:统计<text>中字符串中的单词个数。
返回:返回<text>中的单词数。
示例:$(words, foo bar baz)返回值是“3”。
备注:若是咱们要取<text>中最后的一个单词,咱们能够这样:$(word $(words <text> ),<text> )。

$(firstword <text> )

名称:首单词函数——firstword。
功能:取字符串<text>中的第一个单词。
返回:返回字符串<text>的第一个单词。
示例:$(firstword foo bar)返回值是“foo”。
备注:这个函数能够用word函数来实现:$(word 1,<text> )。

以上,是全部的字符串操做函数,若是搭配混合使用,能够完成比较复杂的功能。这里,举一个现实中应用的例子。咱们知道,make使用“VPATH”变量来指定“依赖文件”的搜索路径。因而,咱们能够利用这个搜索路径来指定编译器对头文件的搜索路径参数CFLAGS,如:

override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH)))

若是咱们的“$(VPATH)”值是“src:../headers”,那么“$(patsubst %,-I%,$(subst :, ,$(VPATH)))”将返回“-Isrc -I../headers”,这正是cc或gcc搜索头文件路径的参数。

 

3、文件名操做函数

下面咱们要介绍的函数主要是处理文件名的。每一个函数的参数字符串都会被当作一个或是一系列的文件名来对待。

$(dir <names...> )

名称:取目录函数——dir。
功能:从文件名序列<names>中取出目录部分。目录部分是指最后一个反斜杠(“/”)以前的部分。若是没有反斜杠,那么返回“./”。
返回:返回文件名序列<names>的目录部分。



示例: $(dir src/foo.c hacks)返回值是“src/ ./”。

$(notdir <names...> )

名称:取文件函数——notdir。
功能:从文件名序列<names>中取出非目录部分。非目录部分是指最后一个反斜杠(“/”)以后的部分。
返回:返回文件名序列<names>的非目录部分。
示例: $(notdir src/foo.c hacks)返回值是“foo.c hacks”。

$(suffix <names...> )

名称:取后缀函数——suffix。
功能:从文件名序列<names>中取出各个文件名的后缀。
返回:返回文件名序列<names>的后缀序列,若是文件没有后缀,则返回空字串。
示例:$(suffix src/foo.c src-1.0/bar.c hacks)返回值是“.c .c”。

$(basename <names...> )

名称:取前缀函数——basename。
功能:从文件名序列<names>中取出各个文件名的前缀部分。
返回:返回文件名序列<names>的前缀序列,若是文件没有前缀,则返回空字串。
示例:$(basename src/foo.c src-1.0/bar.c hacks)返回值是“src/foo src-1.0/bar hacks”。

$(addsuffix <suffix>,<names...> )

名称:加后缀函数——addsuffix。
功能:把后缀<suffix>加到<names>中的每一个单词后面。
返回:返回加事后缀的文件名序列。
示例:$(addsuffix .c,foo bar)返回值是“foo.c bar.c”。

$(addprefix <prefix>,<names...> )

名称:加前缀函数——addprefix。
功能:把前缀<prefix>加到<names>中的每一个单词后面。
返回:返回加过前缀的文件名序列。
示例:$(addprefix src/,foo bar)返回值是“src/foo src/bar”。

$(join <list1>,<list2> )

名称:链接函数——join。
功能:把<list2>中的单词对应地加到<list1>的单词后面。如 果<list1>的单词个数要比<list2>的多,那么,<list1>中的多出来的单词将保持原样。如 果<list2>的单词个数要比<list1>多,那么,<list2>多出来的单词将被复制 到<list2>中。
返回:返回链接事后的字符串。
示例:$(join aaa bbb , 111 222 333)返回值是“aaa111 bbb222 333”。

 

4、foreach 函数


foreach函数和别的函数很是的不同。由于这个函数是用来作循环用的,Makefile中的foreach函数几乎是仿照于Unix 标准Shell(/bin/sh)中的for语句,或是C-Shell(/bin/csh)中的foreach语句而构建的。它的语法是: 

$(foreach <var>,<list>,<text> ) 


这个函数的意思是,把参数<list>中的单词逐一取出放到参数<var>所指定的变量中,而后再执 行<text>所包含的表达式。每一次<text>会返回一个字符串,循环过程当中,<text>的所返回的每一个字符 串会以空格分隔,最后当整个循环结束时,<text>所返回的每一个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回 值。 

因此,<var>最好是一个变量名,<list>能够是一个表达式,而<text>中通常会使用<var>这个参数来依次枚举<list>中的单词。举个例子: 

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函数当中。 

5、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>只会有一个被计算。

 

6、call函数


call函数是惟一一个能够用来建立新的参数化的函数。你能够写一个很是复杂的表达式,这个表达式中,你能够定义许多参数,而后你能够用call函数来向这个表达式传递参数。其语法是: 

$(call <expression>,<parm1>,<parm2>,<parm3>...) 


当make执行这个函数时,<expression>参数中的变量,如$(1),$(2),$(3)等,会被参 数<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”。 

7、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>是一个命令运行中的自动化变量。关于自动化变量将在后面讲述。 

这些信息对于咱们编写Makefile是很是有用的,例如,假设咱们有一个Makefile其包了一个定义文件Make.def,在 Make.def中定义了一个变量“bletch”,而咱们的环境中也有一个环境变量“bletch”,此时,咱们想判断一下,若是变量来源于环境,那么 咱们就把之重定义了,若是来源于Make.def或是命令行等非环境的,那么咱们就不从新定义它。因而,在咱们的Makefile中,咱们能够这样写: 

ifdef bletch

ifeq "$(origin bletch)" "environment"

bletch = barf, gag, etc.

endif

endif 

固然,你也许会说,使用override关键字不就能够从新定义环境中的变量了吗?为何须要使用这样的步骤?是的,咱们用override是能够 达到这样的效果,但是override过于粗暴,它同时会把从命令行定义的变量也覆盖了,而咱们只想从新定义环境传来的,而不想从新定义命令行传来的。

相关文章
相关标签/搜索