makefile 变量
makefile的变量名是一个字符串,变量的值是另一个文本字符串。变量可以在任何地方使用(新变量的赋值、目标、依赖、命令、指示符、函数
参数)。在引用变量的地方,变量会被它的值所取代(和c语言的宏类似)。
1. 变量命名
makefile的变量名是不包括 :、#、=、前置空白、后置空白的任何字符串(make在处理变量名时会忽略前置和后置空白)。通常定义一个包含
字母、数字、下划线的变量(可以以数字开头)。建议不要定义除字母、数字、下划线以外的变量,因为其可能会在当前及以后的make版本中被赋予
特殊的含义。
makefile的变量名是大小写敏感的。通常对makefile文件定义的变量使用小写方式,而对命令行定义的变量采用大写形式。
示例 1
非法的变量名::name、#name、=name
合法的变量名:1name、_name
2. 变量引用
变量定义后,就可以在makefile的任何地方(新变量的赋值、目标、依赖、命令、指示符、函数参数)引用这个变量。
语法格式如下:
$(variable)
${variable}
$v
$(v)
${v}
2.1变量引用总结
1. 引用变量时需要使用特殊字符 $ 开头。使用 () 或者 {} 标记变量名(多字符变量必须使用此种格式)。
2. 引用简单变量时,也可以不使用 () 或 {} 来标记变量名,而直接使用 $v的格式来实现。但是此种方式仅限于单字符变量名的引用。
3. 采用单字符变量引用的方式来引用多字符变量,make将把多字符变量名第一个字符作为单字符变量名来引用。
如 $variable实际是 $(v)ariable。
4. 如果在makefile中需要使用 $ 字符时,则需要使用两个 $$ 字符来表示普通字符 $。
示例 2
Makefile 文件的内容如下
.PHONY : clean
# target files
targets = test
# object files
objects = 1.o test.o
# compiler : cc gcc
cxx = cc
$(targets) : $(objects)
$(cxx) -o test test.o 1.o
1.o : 1.c
$(cxx) -c 1.c
test.o : 1.h
$(cxx) -c test.c
clean :
-rm $(targets) $(objects)
在命令提示符下输入“make -n”,执行结果如下:
cc -c 1.c
cc -c test.c
cc -o test test.o 1.o
通过这个结果,我们可以看到变量的展开过程和c语言的宏展开的过程一样,是一个严格的文本替换过程。
变量引用 $(targets) 被替换成test;变量引用 $(objects) 被替换成1.o test.o;变量引用 $(cxx) 被替换成cc。
示例3
Makefile文件的内容如下
o = single_variable
objects = 1.o test.o
print :
echo $(objects)
echo $objects
在命令提示符下输入“make -s”,执行结果如下:
1.o test.o
single_variablebjects
为了避免多字符变量引用变成单字符变量引用,建议在变量引用时都显示使用 () 或者 {} 标记变量名。
3. 变量定义
makefile的变量定义有两种风格(two flavors),这两种定义风格的区别在于:变量的定义风格和变量值的展开时机。
3.1递归扩展的变量(recursively expanded variables)
递归扩展的变量是通过 = 或者指示符define定义的。这种变量的引用是严格的文本替换过程。定义时可以引用之前没有定义的变量,这些被引
用的变量会在它被展开的同时被展开,而不会在定义的时候被展开。
语法格式如下:
variable = value
示例4
Makefile文件的内容如下
.PHONY : clean
clean_files = $(targets) $(objects)
# target files
targets = test
# object files
objects = 1.o test.o
# compiler : cc gcc
cxx = cc
$(targets) : $(objects)
$(cxx) -o test test.o 1.o
1.o : 1.c
$(cxx) -c 1.c
test.o : 1.h
$(cxx) -c test.c
clean :
-rm $(clean_files)
在命令提示符下输入“make clean”,执行结果如下:
rm test 1.o test.o
在示例4中,变量clean_files在定义时引用了变量targets和objects,但它们是在clean_files之后定义的。
3.2递归扩展的变量的特点
1. 递归扩展的变量在定义时可以引用其它的之前没有定义的变量(可以是任意地方定义的变量)。
2. 递归扩展的变量在定义时可能会出现递归定义,从而导致make进入到无限的变量展开过程。make检测到变量的递归定义时会报错。
3. 递归扩展的变量在定义时如果引用了某一个函数,那么函数总会在其被引用的地方被执行。这可能会降低make的执行效率。
示例5
Makefile文件的内容如下
variable_a = $(variable_b)
variable_b = $(variable_a)
print :
echo variable_a=$(variable_a)
在命令提示符下输入“make -s”,执行结果产生错误:
Makefile:1: *** Recursive variable 'variable_a' references itself (eventually). Stop.
3.3直接扩展的变量(simply expanded variables)
makefile提供了另外一种定义变量的风格(直接扩展的变量),以弥补递归扩展的变量的不足。这一类型的变量是通过 := 定义的。定义时也可
以引用其它的变量或者函数,它们将在定义时被展开。所以直接扩展的变量在定义后其值就是一个文本字符串,不包含任何对其它的变量或者函数的
引用。
语法格式如下:
variable := value
直接扩展的变量在定义时就完成了对其引用的变量或者函数的展开,因此它引用后面定义的变量时,其值将为空。
示例6
Makefile 文件的内容如下
variable_a := $(variable_b)
variable_b := $(variable_a)
print :
echo variable_a=$(variable_a)
在命令提示符下输入“make -s”,执行结果如下:
variable_a=
示例7
Makefile文件的内容如下
str := hello
str := $(str) world
print :
echo $(str)
在命令提示符下输入“make -s”,执行结果如下:
hello world
3.4直接扩展的变量的特点
1. 直接扩展的变量对其它的变量或者函数的展开发生在其定义时。
2. 直接扩展的变量的使用方式和大多数编程语言中的变量使用方式基本相同。它可以使一个复杂的makefile在一定程度上具有可预测性。在复
杂的 makefile中,建议使用直接扩展的风格定义变量。
3. 直接扩展的变量可以利用之前所定义的值来重新定义它,此方式经常在 makefile 中使用。比如使用某一个函数来对它以前的值进行处理并
重新赋值。
3.5 ?=赋值运算符
?= 在makefile中称为条件变量赋值运算符(conditional variable assignment operator),其只有在变量没有定义的情况下才会对这个变量
进行定义并赋值。
语法格式如下:
variable ?= value
等同于
ifeq ($(origin variable), undefined)
variable = value
endif
等同于
ifeq ($(flavor variable), undefined)
variable = value
endif
只有变量variable在没有定义的情况下(variable = 属于已定义的状态,只是没有值),才会被赋值value。
示例8
Makefile文件的内容如下
#i2c_support =
ifndef i2c_support
ifeq ($(origin i2c_support), undefined)
output_str = i2c_support is undefine
else
output_str = i2c_support has no right value
endif
endif
i2c_support ?= yes
print :
echo i2c_support=$(i2c_support)
echo $(output_str)
在命令提示符下输入“make -s”,执行结果如下:
i2c_support=yes
i2c_support is undefine
示例9
Makefile文件的内容如下
i2c_support =
ifndef i2c_support
ifeq ($(origin i2c_support), undefined)
output_str = i2c_support is undefine
else
output_str = i2c_support has no right value
endif
endif
i2c_support ?= yes
print :
echo i2c_support=$(i2c_support)
echo $(output_str)
在命令提示符下输入“make -s”,执行结果如下:
i2c_support=
i2c_support has no right value
示例8和示例9的结果表明,?= 只有在变量没有定义的情况下才会定义变量并赋值;ifdef variable只有在变量的值不为空(只要赋值符号后面
有内容即可)的情况下,表达式才会为真。
3.6 += 赋值运算符
+= 赋值运算符可以实现对一个变量值的追加操作(appending more text to variable)。
语法格式如下:
variable += value
+= 赋值运算符把value追加到变量variable原有值的末尾,使用空格将其分开。
如果被追加值的变量没有定义,那么 += 会自动变成 =,此变量就被定义为一个递归扩展的变量。如果之前存在这个变量定义,那么 += 就继承
之前定义时的风格。
3.6.1递归展开式变量的追加过程
变量使用 = 定义,+= 操作不对此变量值中的任何引用进行替换展开,而是按照文本的方式进行替换,然后在末尾添加需要追加的值,并使用 =
给此变量重新赋值。
variable = value
variable += more
等同于
temp = value
variable = $(temp) more
3.6.2直接展开式变量的追加过程
变量使用 := 定义,+= 操作会首先展开此变量的值,然后在末尾添加需要追加的值,并使用 := 重新给此变量赋值。
variable := value
variable += more
等同于
variable := value
variable := $(variable) more
示例10
Makefile 文件的内容如下
i2c_master = cpu
i2c_slave = eeprom
ifndef i2c_master
i2c_master = cpu
else
i2c_master += gpu
endif
ifeq ($(origin i2c_slave), undefined)
i2c_slave = eeprom
else
i2c_slave += thermal-sensor
endif
print :
echo master : $(i2c_master)
echo slave : $(i2c_slave)
在命令提示符下输入“make -s”,执行结果如下:
master : cpu gpu
slave : eeprom thermal-sensor
3.7定义值为多行的变量
定义变量的另外一种方式是使用define指示符,它可以定义一个包含多行字符串的变量。
语法格式如下:
define multi_line_variable
value
…
endef
使用define定义的变量和使用 = 定义的变量一样,都属于递归扩展的变量,两者只是在语法上有不同,在使用上完全一样。因此define所定义
的变量值中,对其它的变量或者函数的引用不会在定义时替换展开,其替换展开是在define定义的变量被引用时。
示例11
Makefile 文件的内容如下
define output_version
echo "GNU Make 3.82"
echo "Built for i686-pc-linux-gnu"
echo "Copyright (C) 2010 Free Software Foundation, Inc."
endef
print :
$(output_version)
在命令提示符下输入“make -s”,执行结果如下:
GNU Make 3.82
Built for i686-pc-linux-gnu
Copyright (C) 2010 Free Software Foundation, Inc.
3.8变量替换展开规则
make提供了多种对变量进行定义和赋值的方式。
variable = value
variable := value
variable ?= value
variable += value
define variable
values
endef
其替换展开的规则如下,immediate表示展开时机是在定义时,deferred表示展开时机是在引用时。
immediate = deferred
immediate := immediate
immediate ?= deferred
immediate += deferred & immediate
define immediate
deferred
endef
3.9定义一个空格
变量值中的前导空格在变量引用的时候将被弃用。根据直接扩展的变量在定义时对其引用的变量和函数进行展开的特点,可以实现将一个前导空
格定义在变量值中。
示例12
Makefile 文件的内容如下
null :=
# 两个变量null的中间是一个空格字符
space := $(null) $(null)
print :
echo hello$(space)world
在命令提示符下输入“make -s”,执行结果如下:
hello world
示例13
Makefile文件的内容如下
null :=
# 变量 null 作为变量值的开始,# 注释符作为变量值的结束,中间是一个空格字符
space := $(null) # leading whitespace
print :
echo hello$(space)world
在命令提示符下输入“make -s”,执行结果如下:
hello world
示例12和示例13都能实现将一个前导空格定义到变量值中。
make对变量进行处理时变量值中后置空格是不被忽略的,对于不包含后置空格的变量值的赋值,就不能随便使用任意空格之后,在同行中放置它
的注释内容,这样后置空格会被作为变量值的一部分。
示例14
Makefile文件的内容如下
trail_space := trail # three trailing whitespace
print :
printf "$(trail_space) end\n"
在命令提示符下输入“make -s”,执行结果如下:
trail end
为了避免后置空格的问题,推荐使用内置函数strip将前后空格去掉。
示例15
Makefile文件的内容如下
trail_space := trail # three trailing whitespace
print :
printf "$(strip $(trail_space)) end\n"
在命令提示符下输入“make -s”,执行结果如下:
trail end
4. 变量引用的高级用法
4.1替换引用
对于一个已经定义的变量,可以使用替换引用(substitution reference)将其值中某些字符使用指定的字符(字符串)进行替换。
语法格式如下:
$(variable:string1=string2)
${variable:string1=string2}
替换引用是替换变量variable中所有以string1结尾的单词为string2结尾的单词(变量值的多个单词以空格分开)。结尾的含义是空格之前。
需要特别注意的是,语法格式中的 : 的前面没有空白,否则将实现不了替换引用的功能。
示例16
Makefile 文件的内容如下
file_names = 1 2 3
objects = $(file_names:=.o) 4.obj
sources = $(objects:o=c)
print :
echo $(objects)
echo $(sources)
在命令提示符下输入“make -s”,执行结果如下:
1.o 2.o 3.o 4.obj
1.c 2.c 3.c 4.obj
变量的替换引用其实是内置函数patsubst的一个简化实现。在makefile中同时提供了这两种方式来实现相同的目的。
另外一种替换引用的语法可以实现内置函数patsubst的所有功能。不过需要在替换字符串中包含模式字符%。
语法格式如下:
$(variable:%string1=%string2)
${variable:%string1=%string2}
示例17
Makefile文件的内容如下
file_names = 1 2 3
objects = $(file_names:%=%.o) 4.obj
sources = $(objects:%o=%c)
print :
echo $(objects)
echo $(sources)
在命令提示符下输入“make -s”,执行结果如下:
1.o 2.o 3.o 4.obj
1.c 2.c 3.c 4.obj
4.2嵌套引用
变量的嵌套引用又称为计算的变量名(computed variable names),它是一个比较复杂的概念,表示一个变量名之中可以包含对其它变量的引
用。
语法格式如下:
$($(variable))
variable也可以使用变量的替换引用、嵌套引用、函数调用来定义,这样就可以形成复杂的、多层的嵌套引用。
示例18
Makefile文件的内容如下
x = computed variable names
y = x
z = y
str = $($($(z)))
print :
echo $(str)
在命令提示符下输入“make -s”,执行结果如下:
computed variable names
在示例18中,变量替换引用过程首先是最里边的变量引用 $(z) 被替换为值y;然后是 $(y) 被替换成值x;所以最后str = $(x) = computed
variable names。
4.2.1变量的嵌套引用总结
1.变量的嵌套引用就是使用一个变量表示另外一个变量。
2.变量的嵌套引用可以用在任何地方(包括变量名)。
3.变量的嵌套引用替换展开的顺序是从最里层的开始,逐步向外层替换展开。
4.使用变量的嵌套引用的唯一限制是不能通过指定部分需要调用的函数名称(包括函数名和参数列表)来实现对函数的调用。这是因为make在对
嵌套引用展开之前已经完成了对函数名的识别。
5. 系统环境变量
make在运行时,系统的所有环境变量对它都是可见的。在makefile中,可以引用任何已定义的系统环境变量。当然,也可以在makefile中重置系
统环境变量的值。
示例19
Makefile文件的内容如下
print :
echo system SHELL=$(shell echo $$SHELL)
echo make SHELL=$(SHELL)
echo DESKTOP_SESSION=$(DESKTOP_SESSION)
在命令提示符下输入“make -s”,执行结果如下:
system SHELL=/bin/bash
make SHELL=/bin/sh
DESKTOP_SESSION=ubuntu-2d
5.1系统环境变量总结
1. 在makefile中定义的变量或者make命令行变量,都将覆盖同名的环境变量(它并不能改变系统环境变量的值,被修改的系统环境变量只在
make执行过程有效)。
2. make在执行时使用-e命令行选项,makefile定义的变量不会覆盖同名的系统环境变量。
3. make在递归调用时,所有的系统环境变量会传递给下一级make进程。默认情况下,只有系统环境变量和命令行变量才会传递给下一级make进
程。
4. 系统环境变量SHELL在makefile中的值为/bin/sh,其在make开始执行时就已改变。而不是默认的值。
5. 在makefile中,非必须情况下不建议重置系统环境变量的值。
示例20
test.mak文件的内容如下
print :
echo DESKTOP_SESSION2=$(DESKTOP_SESSION)
echo test_variable2=$(test_variable)
Makefile文件的内容如下
DESKTOP_SESSION = ubuntu-3d
test_variable = test
print :
make -f test.mak
echo DESKTOP_SESSION1=$(DESKTOP_SESSION)
echo test_variable1=$(test_variable)
在命令提示符下输入“make -s”,执行结果如下:
DESKTOP_SESSION2=ubuntu-3d
test_variable2=
DESKTOP_SESSION1=ubuntu-3d
test_variable1=test
在命令提示符下输入“make -s -e”,执行结果如下:
DESKTOP_SESSION2=ubuntu-2d
test_variable2=
DESKTOP_SESSION1=ubuntu-2d
test_variable1=test
示例20的结果表明,系统环境变量可以传递给下一级make;makefile中定义的变量默认不能传递给下一级make;make在执行时使用-e参数,
makefile中定义的同名变量不能覆盖系统环境变量的值。
6. 目标指定变量
makefile提供了一种目标指定变量(target-specific variables),其允许同一个变量根据目标的不同而指定不同的值。
目标指定变量的值只在指定它的目标的上下文(所有和目标有依赖关系的目标)中有效,对于其它的目标不产生影响。因此可以说目标指定变量
的作用域具有局部性。
语法格式如下:
target… : variable-assignment
6.1目标指定变量总结
1. variable-assignment 可以使用任何一个有效的赋值方式(=、:=、?=、+=)。
2. 目标指定变量和同名的全局变量属于两个不同的变量。
3. 目标指定变量会作用到由这个目标所引发的所有的规则。
4. 目标指定变量的值不会影响同名的那个全局变量的值。如果在makefile中已经定义了一个和目标指定变量同名的变量(非目标指定变量),
那么目标指定变量值的改变只对指定的这些目标可见。
5. 目标指定变量和普通变量具有相同的优先级。
6. 目标指定变量常用于针对不同的目标文件执行不同的编译选项。
示例21
Makefile文件的内容如下
test : targets := test
1.o : targets += 1.o
test.o : targets += test.o
test : 1.o test.o
echo test targets = $(targets)
1.o : 1.c
echo 1.o targets = $(targets)
test.o : test.c 1.h
echo test.o targets = $(targets)
在命令提示符下输入“make -s”,执行结果如下:
1.o targets = test 1.o
test.o targets = test test.o
test targets = test
7. 模式指定变量
makefile提供了一种模式指定变量(pattern-specific variables),它将一个变量值指定到所有符合特定模式的目标。而目标指定变量是将变
量指定到某个具体目标和由它所引发的规则的目标。
语法格式如下:
pattern… : variable-assignment
模式指定变量的目标是一个或多个符合模式的目标,模式字符串中通常都会包含模式字符 %。在模式指定变量定义时,目标文件除了模式字符 %
以外还需要包含某种文件名的特征字符。当单独使用模式字符 % 作为目标时,指定的变量会对任何类型的目标文件都有效。
示例22
Makefile文件的内容如下
test : targets += test
1.o : targets += 1.o
test.o : targets += test.o
% : targets += all
%.o : targets += object
test : 1.o test.o
echo test targets = $(targets)
1.o : 1.c
echo 1.o targets = $(targets)
test.o : test.c 1.h
echo test.o targets = $(targets)
在命令提示符下输入“make -s”,执行结果如下:
1.o targets = all test all object 1.o
test.o targets = all test all object test.o
test targets = all test
示例22的结果表明,同一个变量如果使用追加方式,目标的局部变量值的顺序是: 所有规则定义的全局值(模式指定)+引发它所在规则被执行
的目标指定值(目标指定)+它所符合的模式指定值(模式指定)+此目标指定值(目标指定)。
发表评论