如何编写Makefile(4)-变量使用

目录

一、基础

二、变量中的变量

三、变量的高级用法

四、追加变量值

五、override指示符

六、多行变量

七、环境变量

八、目标变量

九、模式变量


        我们继续Makefile编写的内容学习~第三节可参考:↓传送门↓

如何编写Makefile(3)-CSDN博客icon-default.png?t=O83Ahttps://blog.csdn.net/L_peanut/article/details/144242622?spm=1001.2014.3001.5501        本节内容主要介绍Makefile的变量使用。

        Makefile中定义的变量类似于C/C++中的宏,其代表一个文本字符串,在Makefile执行的时候会自动在使用的位置进行展开。与C/C++不相同的是,我们可以在Makefile中改变其值。Makefile中,变量可以使用在“目标”,“依赖目标”,“命令”或者是Makefile的其它部分中。变量的命令可以包含字符、数字、下划线(可以是数字开头),但不可以包含 #=或者是空字符(空格、回车等)。变量是大小写敏感的,“test”、“Test”和“TEST”是三个不同的变量名。传统Makefile变量名是全大写的方式,平常使用时可以使用类似于C++变量驼峰式命名的方式,如“MakeFlags”,这样可以避免与系统变量冲突。

一、基础

        变量在声明时需要赋初值,在使用时需要在变量名前面加上$符号,但最好用小括号()或大括号{}把变量包含起来。如果需要使用真实的$字符,可以使用$$来表示。变量可以在许多地方使用,如规则中的“目标”、“依赖”、“命令”以及新的变量中。

objects=a.o b.o c.o

program:$(objects)
    cc -o program $(objects)

$(objects):header.h

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

value=c
a.o:a.$(value)
    $(value)$(value) -$(value) a.$(value)

        展开后可以得到:

a.o:a.c
    cc -c a.c

        当然,如果你在Makefile中这样写是非常不科学的。这里只是举例来说明Makefile中的变量在使用处展开的真实模样,原理是“替代”。

二、变量中的变量

        在定义变量的值时,可以使用其它变量来构造变量的值,在Makefile中有两种方式来使用变量定义变量的值。

        第一种方式。使用“=”号,=的左侧是变量,右侧是变量的值,右侧变量的值可以定义在文件的任何一处,也就是说,右侧中的变量不一定是已经定义好的值,也可以是使用后面定义的值。如:

value=$(a)
a=$(b)
b=c


all:
    echo $(value)

        如果我们执行“make all”,将会打印出变量$(value)的值是c,($(value))的值是$(a),$(a)的值是$(b),$(b)的值是c。这个功能有益有弊,好的地方是我们可以把变量的真实值推导后面来定义,如:

CFLAGS=$(include_dirs) -O
include_dirs=-Iuser1 -Iuser2

        当CFLAGS在命令中被展开时,会是-Iuser1 -Iuser2 -O,但是这种形式也有不好的地方,那就是递归定义。譬如说:

CFLAGS=$(CFLAGS) -O

或者

A=$(B)
B=$(A)

        这样会让make陷入无限的变量展开中,不过make是有能力检测这样的定义的,并且会报错。同时,如果在变量中使用函数,那么这种方式将会让我们运行make时非常慢,更糟糕的是使用的两个参数“wildcard”和“shell”可能会发生不可预知的错误。因为不知道这两个函数会调用多少次。

        为了避免上面的情况,可以使用make中的另一种变量来定义变量的方法,这种方法使用:=符号。如:

x:=a
y:=$(x) b
x:=c

等价于

y:=a b
x:=c

        这种方法前面的变量不能使用后面的变量,只能使用前面已经定义好了的变量,如果是这样:

y:=$(x) b
x:=a

        那么,y的值是“b”,而不是“a b”。

        下面这个例子相对复杂,包括了make的函数、条件表达式和系统变量:

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调用层数。下面这个例子,如果我们要定义一个变量,其值是一个空格,那么我们可以这样来:

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

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

dir:=/home/user    #directory to put the frobs in

        dir这个变量的值是“/home/user”,后面还跟了4个空格,如果我们这样使用变量来指定其它目录即“$(dir)/file”就不对了。

        还有一个比较有用的操作符是?=,如:

A?=b

        含义是如果A没有被定义过,那么变量A的值就是“b”,如果A先前被定义过,那么这条语句什么也不做,等价于:

ifeq ($(origin A), undefined)
    A=b
endif

三、变量的高级用法

        我们介绍两种变量的高级用法,第一种是变量值的替换。

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

value1:=a.o b.o c.o
value2:=$(value1:.o=.c)

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

        另一种变量替换的方法是以“静态模式”定义的,如:

value1:=a.o b.o c.o
value2:=$(value1:%.o=%.c)

        这依赖于被替换字串中有相同的模式,模式必须包含一个%字符,其结果也是$(value2)的值为“a.c b.c c.c”。

        第二种高级用法是“把变量的值再当成变量”,如:

x=y
y=z
a:=$($x))

        例子中$(x)的值是“y”,所以$($(x))就是$(y),于是$(a)的值就是“z”。我们还可以使用更多的层次:

x=y
y=z
z=u
a:=$($($(x)))

        这里a的值是“u”。

        我们再看一个复杂一点的例子:

x=$(y)
y=z
z=Hello
a:=$($(x))

        这里的$($x))被替换成$($(y)),因为y的值是“z”,所以最终结果是a:=$(z),也就是Hello。

        如果再复杂一点,加上函数:

x=value1
value2:=Hello
y=$(subst 1,2,$(x))
z=y
a:=$($($(z)))

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

        这种方式中可以使用多个变量来组成一个变量的名字,然后再取其值:

first_second=Hello
a=first
b=second
all=$($a_$b)

        这里的$a_$b组成了一个“first_second”,于是$(all)的值就是“Hello”。再来看看结合第一种技术的例子:

a_objects:=a.o b.o c.o
1_objects:=1.o 2.o 3.o

sources:=$($(a1)_objects:.o=.c)

        这个例子中如果$(a1)的值是“a”的话,$(sources)的值就是“a.c b.c c.c”;如果$(a1)的值是“1”,那么$(sources)的值是“1.c 2.c 3.c”。

        再看这种技术和“函数”与“条件语句”一起使用的例子:

ifdef do_sort
    func:=sort
else
    func:=strip
endif

bar:=a d b g q c
foo:=$($(func) $(bar))

        示例中如果定义了“do_sort”,foo:=$(sort a d b g q c),于是$(foo)的值就是“a b c d g q”,如果没有定义“do_sort”,foo:=$(strip a d b g q c),调用的就是strip函数。“把变量的值再当成变量”这种技术同样可以用在操作符的左边:

dir=foo
$(dir)_sources:=$(wildcard $(dir)/*.c)
define $(dir)_print
lpr $($(dir)_sources)
endef

        这个例子中定义了三个变量:“dir”、“foo_sources”和“foo_print”。

四、追加变量值

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

objects=main.o a.o b.o c.o
objects+=other.o

        其中的$(objects)就变成“main.o a.o b.o c.o other.o”,使用+=操作符,可以模拟为下面的例子:

objects=main.o a.o b.o c.o
objects:=$(objects) other.o

        不同的是,使用+=更加简洁。如果变量之前没有定义过,+=会自动变为=,如果有变量定义,那么+=会继承前次操作的赋值符。如果前一次是:=,那么+=会以:=作为其赋值符。如:

value:=a
value+=more

等价于

value:=a
value:=$(value) more

         但如果是这种情况:

value=a
value+=more

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

五、override指示符

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

override <variable>;=<value>;
override <variable>;:=<value>;

        也可以追加:

override <variable>; +=<more text>;

        对于多行的变量定义,我们使用define指示符,在define之前,也同样可以使用override指示符,如:

override define a
bar
endef

六、多行变量

        还有一种设置变量值的方法是使用“define”关键字,使用define关键字设置变量的值可以有换行,这有利于一些列的命令。

        define指示符后面跟的是变量的名字,另起一行定义变量的值。定义是以endef关键字结束,其工作方式和“=”操作符一样。变量的值可以包括函数、命令、文字或者是其它变量。因为命令需要以Tab键开头,所以如果用define定义的命令变量中没有以Tab键开头,那么make就不会把其认为是命令。

        下面展示define的用法:

define two-lines
echo foo
echo $(bar)
endef

七、环境变量

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

        如果我们在环境变量中设置了CFLAGS环境变量,那么我们就可以在所有的Makefile中使用这个变量了。如果Makefile中定义了CFLAGS,那么则会使用Makefile中的这个变量,如果没有定义则使用系统环境变量的值,类似于“全局变量”和“局部变量”的特性。

        当make嵌套调用时,上层Makefile中定义的变量会以系统环境变量的方式传递给下层的Makefile。默认情况下,只有通过命令行设置的变量会被传递。定义在文件中的变量,如果要向下层Makefile传递,则需要使用export关键字来声明。

八、目标变量

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

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

        语法为:

<target ...>:<variable-assignment>;

<target ...>:override <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目标以及其所引发的所有规则中,$(CFLAGS)的值都是-g。

九、模式变量

        在GNU的make中,支持模式变量(Pattern-specific Variable),通过上面的目标变量中,我们指定变量可以定义在某个目标上。模式变量的好处是我们可以给定一种“模式”,可以把变量定义在符合这种模式的所有目标上。

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

%.o:CFLAGS = -O

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

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猿核试Bug愁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值