ZyLiu's Blog

Makefile学习(二)

字数统计: 7.1k阅读时长: 28 min
2018/08/18 Share

5 使用变量

在Makefile中的定义的变量,就像是C/C++语言中的宏一样,他代表了一个文本字串,在Makefile中执行的时候其会自动原模原样地展开在所使用的地方。其与C/C++所不同的是,你可以在Makefile中改变其值。在Makefile中,变量可以使用在“目标”,“依赖目标”,“命令”或 是Makefile的其它部分中。

变量的命名字可以包含字符、数字,下划线(可以是数字开头),但不应该含有“:”、“#”、“=”或是空字符(空格、回车等)。变量是大小写敏感的,“foo”、“Foo”和“FOO”是三个不同的变量名。传统的Makefile的变量名是全大写的命名方式,但推荐使用大小写搭配的变量名,如:MakeFlags。这样可以避免和系统的变量冲突,而发生意外的事情。

有一些变量是很奇怪字串,如$<$@$^等,这些是自动化变量。

5.1 变量的基础

变量在声明时需要给予初值,而在使用时,需要给在变量名前加上$符号,但最好用小括号()或是大括号{}把变量给包括起来。如果要使用真实的$字符,那么需要用$$来表示。

变量可以使用在许多地方,如规则中的“目标”、“依赖”、“命令”以及新的变量中。
先看一个例子:

1
2
3
4
5
objects = program.o foo.o utils.o

program : $(objects)
cc -o program $(objects)
$(objects) : defs.h

变量会在使用它的地方精确地展开,就像C/C++中的宏一样,例如:

1
2
3
4
5
6
7
foo = c
prog.o : prog.$(foo)
$(foo)$(foo) -$(foo) prog.$(foo)

#展开后得到:
prog.o : prog.c
cc -c prog.c

当然,千万不要在你的Makefile中这样干,这里只是举个例子来表明Makefile中的变量在使用处展开的真实样子。可见其就是一个“替代”的原理。

5.2 变量中的变量

将变量赋值给变量共有两种方式,分别是

  • =
  • :=

第一种方式为全局搜索变量,第二种方式为只搜索上面的变量。
比如:

1
2
3
4
5
6
7
y = $(x) bar
x = foo
# $(y) 的值为 foo bar

y := $(x) bar
x := foo
# $(y) 的值为 bar

第一种方式的好处是可以在变量中使用后面定义的变量,坏处是可能造成递归,且如果在变量中使用函数,这种方式会让我们的make运行时非常慢。他会使用得两个make的函数wildcardshell发生不可预知的错误。因为你不会知道这两个函数会被调用多少次。

一个复杂的例子,其中包括了make的函数、条件表达式和一个系统变量“MAKELEVEL”的使用:

1
2
3
4
5
6
ifeq (0,${MAKELEVEL})
cur-dir := $(shell pwd)
whoami := $(shell whoami)
host-type := $(shell arch)
MAKE := ${MAKE} host-type=${host-type} whoami=${whoami}
endif

对于系统变量MAKELEVEL,其意思是,如果我们的make有一个嵌套执行的动作,那么,这个变量会记录了我们的当前Makefile的调用层数。

下面再介绍两个定义变量时我们需要知道的,请先看一个例子,如果我们要定义一个变量,其值是一个空格,那么我们可以这样来:

1
2
nullstring :=
space := $(nullstring) # end of the line

nullstring是一个Empty变量,其中什么也没有,而我们的space的值是一个空格。因为在操作符的右边是很难描述一个空格的,这里采用的技术很管用,先用一个Empty变量来标明变量的值开始了,而后面采用#注释符来表示变量定义的终止,这样我们可以定义出其值是一个空格的变量。注意这里关于#的使用,注释符#的这种特性值得我们注意,如果我们这样定义一个变量:

1
dir := /foo/bar    # directory to put the frobs in

dir这个变量的值是/foo/bar,后面还跟了4个空格,如果我们这样使用这样变量来指定别的目录——$(dir)/file那么就完蛋了,这点需要注意。

还有一个比较有用的操作符是?=,先看示例:

1
FOO ?= bar

其含义是,如果FOO没有被定义过,那么变量FOO的值就是bar,如果FOO先前被定义过,那么这条语将什么也不做,其等价于:

1
2
3
ifeq ($(origin FOO), undefined)
FOO = bar
endif

5.3 变量高级用法

变量值的替换

我们可以替换变量中的共有的部分,其格式是$(var:a=b)或是${var:a=b},其意思是,把变量var中所有以a字串结尾的a替换成b字串。这里的“结尾”意思是“空格”或是“结束符”。

举例说明:

1
2
foo := a.o b.o c.o
bar := $(foo:.o=.c)

这个示例中,我们先定义了一个“$(foo)”变量,而第二行的意思是把“$(foo)”中所有以“.o”字串“结尾”全部替换成“.c”,所以我们的“$(bar)”的值就是“a.c b.c c.c”。

把变量的值再当成变量

例子:

1
2
3
x = y
y = z
a := $($(x))

在这个例子中,$(x)的值是y,所以$($(x))就是$(y),于是$(a)的值就是z
注意,是x=y,而不是x=$(y)。如果是x=$(y),那么x的值就是z而不是y了。

更复杂的例子:

1
2
3
4
5
x = variable1
variable2 := Hello
y = $(subst 1,2,$(x))
z = y
a := $($($(z)))

这个例子中,$($($(z)))扩展为$($(y)),而其再次被扩展为$($(subst 1,2,$(x)))$(x)的值是variable1subst函数把variable1中的所有“1”字串替换成“2”字串,于是, variable1变成variable2,再取其值,所以,最终,$(a)的值就是$(variable2)的值——Hello

使用多个变量的名字组成一个变量的名字:

1
2
3
4
first_second = Hello
a = first
b = second
all = $($a_$b)

这里$a_$b$展开为first_second,最后$first_second的值为Hello

结合变量替换:

1
2
3
a_objects := a.o b.o c.o
1_objects := 1.o 2.o 3.o
sources := $($(a1)_objects:.o=.c)

如果$a1的值为1,最终的结果是1.c 2.c 3.c;如果$a1的值是a,最终的结果是a.c b.c c.c

“把变量的值再当成变量”这种技术,同样可以用在操作符的左边:

1
2
3
4
5
dir = foo
$(dir)_sources := $(wildcard $(dir)/*.c)
define $(dir)_print
lpr $($(dir)_sources)
endef

这个例子中定义了三个变量:dirfoo_sourcesfoo_print

5.4 追加变量值

我们可以使用“+=”操作符给变量追加值,如:

1
2
objects = main.o foo.o bar.o utils.o
objects += another.o

于是,我们的$(objects)值变成:main.o foo.o bar.o utils.o another.o

如果变量之前没有定义过,那么,“+=”会自动变成“=”,如果前面有变量定义,那么“+=”会继承于前次操作的赋值符。如果前一次的是“:=”,那么“+=”会以“:=”作为其赋值符,如:

1
2
3
4
5
variable := value
variable += more
#等价于:
variable := value
variable := $(variable) more

但如果是这种情况:

1
2
variable = value
variable += more

由于前次的赋值符是=,所以+=也会以=来做为赋值,那么岂不会发生变量的递补归定义,这是很不好的,所以make会自动为我们解决这个问题,我们不必担心这个问题。

5.5 override指示符

如果有变量是通常make的命令行参数设置的,那么Makefile中对这个变量的赋值会被忽略。如果你想在Makefile中设置这类参数的值,那么,你可以使用override指示符。其语法是:

1
2
3
4
5
6
7
8
override <variable> = <value>
override <variable> := <value>
#追加:
override <variable> += <more text>
#多行变量
override define foo
bar
endef

5.6 多行变量

多行变量就是前面所述的define...endef。使用define关键字设置变量的值可以有换行,这有利于定义一系列的命令。
define指示符后面跟的是变量的名字,而重起一行定义变量的值,定义是以endef关键字结束。其工作方式和=操作符一样。变量的值可以包含函 数、命令、文字,或是其它变量。因为命令需要以Tab键开头,所以如果你用define定义的命令变量中没有以Tab键开头,那么make就不会把其认为是命令。注意这里是在使用时以Tab开头。

1
2
3
4
5
6
7
define two-lines
echo foo
echo $(bar)
endef

使用时:
$(two-lines)

5.7 环境变量

make运行时的系统环境变量可以在make开始运行时被载入到Makefile文件中,但是如果Makefile中已定义了这个变量,或是这个变量由make命令行带入,那么系统的环境变量的值将被覆盖。(如果make指定了-e参数,那么,系统环境变量将覆盖Makefile中定义的变量)

因此,如果我们在环境变量中设置了CFLAGS环境变量,那么我们就可以在所有的Makefile中使用这个变量了。这对于我们使用统一的编译参数有比较大的好处。如果Makefile中定义了CFLAGS,那么则会使用Makefile中的这个变量,如果没有定义则使用系统环境变量的值,一个共性和个性的统一,很像“全局变量”和“局部变量”的特性。

当make嵌套调用时(参见前面的“嵌套调用”章节),上层Makefile中定义的变量会以系统环境变量的方式传递到下层的Makefile中。当然,默认情况下,只有通过命令行设置的变量会被传递。而定义在文件中的变量,如果要向下层Makefile传递,则需要使用exprot关键字来声明。

5.8 目标变量

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

同样可以为某个目标设置局部变量,这种变量被称为“Target-specific Variable”,它可以和“全局变量”同名,因为它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效。而不会影响规则链以外的全局变量的值。

其语法是:

1
2
<target ...> : <variable-assignment>
<target ...> : overide <variable-assignment>

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

当我们设置了这样一个变量,这个变量会作用到由这个目标所引发的所有的规则中去。举例:

1
2
3
4
5
6
7
8
9
10
11
12
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

5.9 模式变量

上面介绍的目标变量,是指在一个指定目标上该变量生效,而模式变量是在一种给定“模式”上,把变量定义在符合这种模式的所有目标上。

我们知道,make的“模式”一般是至少含有一个%的,所以,我们可以以如下方式给所有以.o结尾的目标定义目标变量:

1
%.o : CFLAGS = -O

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

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

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

6 使用条件判断

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

6.1 示例

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

1
2
3
4
5
6
7
8
9
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

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

当然,我们还可以把上面的那个例子写得更简洁一些:

1
2
3
4
5
6
7
8
9
10
11
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)

6.2 语法

条件表达式的语法为:

1
2
3
<conditional-directive>
<text-if-true>
endif

以及:

1
2
3
4
5
<conditional-directive>
<text-if-true>
else
<text-if-false>
endif

<conditional-directive>可以是ifeq/ifneq/ifdef/ifndef

ifeqifneq共有五种表达,其中后四个都类似:

1
2
3
4
5
ifeq (<arg1>, <arg2>)
ifeq '<arg1>' '<arg2>'
ifeq "<arg1>" "<arg2>"
ifeq "<arg1>" '<arg2>'
ifeq '<arg1>' "<arg2>"

ifdefifndef的表达比较简单:

1
2
ifdef <variable-name>
ifndef <variable-name>

<conditional-directive>这一行上,多余的空格是被允许的,但是不能以[Tab]键做为开始(不然就被认为是命令)。而注释符#同样也是安全的。elseendif也一样,只要不是以[Tab]键开始就行了。

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


7 使用函数

7.1 函数的调用语法

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

1
2
3
$(<function> <arguments>)
#或是
${<function> <arguments>}

这里,<function>就是函数名,make支持的函数不多。<arguments>是函数的参数,参数间以逗号“,” 分隔,而函数名和参数之间以“空格”分隔。

函数调用以$开头,以圆括号或花括号把函数名和参数括起。

示例:

1
2
3
4
5
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

7.2 字符串处理函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#字符串替换函数
$(subst <from>,<to>,<text>)

#模式字符串替换函数
$(patsubst <pattern>,<replacement>,<text>)

#去空格函数
$(strip <string>)

#查找字符串函数
$(findstring <find>,<in>)

#过滤函数
$(filter <pattern...>,<text>)

#反向过滤函数
$(filter-out <pattern...>,<text>)

#排序函数
$(sort <list>)

#取单词函数
$(word <n>,<text>)

#取单词串函数
$(wordlist <s>,<e>,<text>)

#单词个数函数
$(words <text>)

#首单词函数
$(firstword <text>)
  • $(subst <from>,<to>,<text>)
    名称:字符串替换函数——subst。
    功能:把字串<text>中的<from>字符串替换成<to>
    返回:函数返回被替换过后的字符串。
    示例:

    1
    $(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>中的那个%所代表的字串。(可以用/来转义,以/%来表示真实含义的%字符)
    返回:函数返回被替换过后的字符串。
    示例:

    1
    $(patsubst %.c,%.o,x.c.c bar.c)

    把字串x.c.c bar.c符合模式[%.c]的单词替换成[%.o],返回结果是x.c.o bar.o
    备注:这与5.3节中的变量替换很相似,上述示例如果写为:$(x.c.c bar.c: .c = .o),则返回有些许不同,会返回x.o.o bar.o

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

    1
    $(strip a b c )

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

  • $(findstring <find>,<in>)
    名称:查找字符串函数——findstring。
    功能:在字串<in>中查找<find>字串。
    返回:如果找到,那么返回<find>,否则返回空字符串。
    示例:

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

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

  • $(filter <pattern...>, <text>)
    名称:过滤函数——filter。
    功能:以<pattern>模式过滤<text>字符串中的单词,保留符合模式<pattern>的单词。可以有多个模式。
    返回:返回符合模式<pattern>的字串。
    示例:

    1
    2
    3
    4
    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>的字串。
    示例:变量定义与上相同,$(filter-out %.c %.s,$(sources)),返回值是ugh.h

  • $(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,如:

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

如果我们的$(VPATH)值是src:../headers,那么$(patsubst %,-I%,$(subst :, ,$(VPATH)))将返回-Isrc -I../headers(所有单词的前面加-I),这正是cc或gcc搜索头文件路径的参数。

7.3 文件名操作函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#取目录函数
$(dir <names...>)
#从文件名序列<names>中取出目录部分。目录部分是指最后一个反斜杠(“/”)之前的部分。如果没有反斜杠,那么返回“./”。

#取文件函数
$(notdir <names...>)
#从文件名序列<names>中取出非目录部分。非目录部分是指最后一个反斜杠(“/”)之后的部分。

#取后缀函数
$(suffix <names...>)
#从文件名序列<names>中取出各个文件名的后缀。返回一个后缀序列。(没有.的项就无视)

#取前缀函数
$(basename <names...>)
#从文件名序列<names>中取出各个文件名的前缀部分。返回一个前缀序列。

#加后缀函数
$(addsuffix <suffix>,<names...>)
#把后缀<suffix>加到<names>中的每个单词后面。

#加前缀函数
$(addprefix <prefix>,<names...>)
#把前缀<prefix>加到<names>中的每个单词后面。

#连接函数
$(join <list1>,<list2>)
#把<list2>中的单词对应地加到<list1>的单词后面。如果<list1>的单词个数要比<list2>的多,那么,<list1>中的多出来的单词将保持原样。如果<list2>的单词个数要比<list1>多,那么,<list2>多出来的单词将被复制到<list2>中。

7.4 foreach函数

foreach函数和别的函数非常的不一样。因为这个函数是用来做循环用的,Makefile中的foreach函数几乎是仿照于Unix标准Shell(/bin/sh)中的for语句,或是C-Shell(/bin/csh)中的foreach语句而构建的。其语法为:

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

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

例子:

1
2
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

7.5 if函数

if函数的语法为:

1
2
3
$(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>没有被定义,那么,整个函数返回空字串。

7.6 call函数

call函数是可以创建新的参数化的函数,也是唯一一个。
你可以写一个非常复杂的表达式,这个表达式中,你可以定义许多参数,然后你可以用call函数来向这个表达式传递参数。其语法是:

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

当make执行这个函数时,<expression>参数中的变量,如$(1)$(2)$(3)等,会被参数<parm1><parm2><parm3>依次取代。而<expression>的返回值就是call函数的返回值。例如:

1
2
reverse =  $(1) $(2)
foo = $(call reverse,a,b)

那么,foo的值就是a b。当然,参数的次序是可以自定义的,不一定是顺序的,如:

1
2
reverse =  $(2) $(1)
foo = $(call reverse,a,b)

此时的foo的值就是b a

  • 疑问:一定是$(1)$(2)这种形式吗?

7.7 origin函数

origin函数不像其它的函数,他并不操作变量的值,他只是告诉你你的这个变量是哪里来的?其语法是:

1
$(origin <variable>)

注意,<variable>是变量的名字,不应该是引用。所以你最好不要在<variable>中使用$字符。Origin函数会以其返回值来告诉你这个变量的“出生情况”,下面,是origin函数的返回值:

  • undefined <variable>从来没有定义过。
  • 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中,我们可以这样写:

1
2
3
4
5
ifdef bletch
ifeq "$(origin bletch)" "environment"
bletch = barf, gag, etc.
endif
endif

override对比,override会重新定义所有种类的变量,这种方式只会重新定义环境变量。

7.8 shell函数

shell函数也不像其它的函数。顾名思义,它的参数应该就是操作系统Shell的命令。它和反引号`是相同的功能。这就是说,shell函数把执行操作系统命令后的输出作为函数返回。于是,我们可以用操作系统命令以及字符串处理命令awksed等等命令来生成一个变量,如:

1
2
contents := $(shell cat foo)
files := $(shell echo *.c)

注意,这个函数会新生成一个Shell程序来执行命令,所以你要注意其运行性能,如果你的Makefile中有一些比较复杂的规则,并大量使用了这个函数,那么对于你的系统性能是有害的。特别是Makefile的隐晦的规则可能会让你的shell函数执行的次数比你想像的多得多。

7.9 控制make的函数

make提供了一些函数来控制make的运行。通常,你需要检测一些运行Makefile时的运行时信息,并且根据这些信息来决定,你是让make继续执行,还是停止。

  • $(error <text ...>)

    产生一个致命的错误,<text ...>是错误信息。注意,error函数不会在一被使用就会产生错误信息,所以如果你把其定义在某个变量中,并在后续的脚本中使用这个变量,那么也是可以的。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #示例一:
    ifdef ERROR_001
    $(error error is $(ERROR_001))
    endif

    #示例二:
    ERR = $(error found an error!)
    .PHONY: err
    err: ; $(ERR)

    示例一会在变量ERROR_001定义了后执行时产生error调用,而示例二则在目录err被执行时才发生error调用。

  • (warning <text ...>)
    这个函数很像error函数,只是它并不会让make退出,只是输出一段警告信息,而make继续执行。

CATALOG
  1. 1. 5 使用变量
    1. 1.1. 5.1 变量的基础
    2. 1.2. 5.2 变量中的变量
    3. 1.3. 5.3 变量高级用法
      1. 1.3.1. 变量值的替换
      2. 1.3.2. 把变量的值再当成变量
    4. 1.4. 5.4 追加变量值
    5. 1.5. 5.5 override指示符
    6. 1.6. 5.6 多行变量
    7. 1.7. 5.7 环境变量
    8. 1.8. 5.8 目标变量
    9. 1.9. 5.9 模式变量
  2. 2. 6 使用条件判断
    1. 2.1. 6.1 示例
    2. 2.2. 6.2 语法
  3. 3. 7 使用函数
    1. 3.1. 7.1 函数的调用语法
    2. 3.2. 7.2 字符串处理函数
    3. 3.3. 7.3 文件名操作函数
    4. 3.4. 7.4 foreach函数
    5. 3.5. 7.5 if函数
    6. 3.6. 7.6 call函数
    7. 3.7. 7.7 origin函数
    8. 3.8. 7.8 shell函数
    9. 3.9. 7.9 控制make的函数