潜心学习
全部博文(25)
分类: LINUX
2013-11-21 10:26:17
原文地址:Makefile学习笔记四 第五章 第六章 作者:好喜儿
规则的命令由一些shell命令行组成,它们被一条一条的执行。
规则中除了第一条紧跟在依赖列表之后使用分号隔开的命令以外,其它的每一行命令行必须以[Tab]字符开始。
多个命令行之间可以有空行和注释行(所谓空行,就是不包含任何字符的一行。如果以[Tab]键开始而其后没有命令的行,此行不是空行。是空命令行),在执行规则时空行被忽略。
make在执行命令行之前会把要执行的命令行输出到标准输出设备
典型的用法是在使用“echo”命令输出一些信息时。如:
@echo 开始编译XXX 模块......
执行时,将会得到“开始编译XXX模块...... ”这条输出信息。如果在命令行之前没有字符“@”,那么,
echo编译XXX 模块......
make的输出将是:
编译XXX 模块......
简单来说 makefile里有这么一行
gcc -o hello hello.c test_hello.o
那么在make的时候这一行就会打出来
但是如果是
@gcc -o hello hello.c test_hello.o
这样make的时候就不会显示有这样一行了
make参数“-s ”或“--slient ”则是禁止所有执行命令的显示,就好像所有的命令行均使用“@”开始一样。在Makefile中使用没有依赖的特殊目标“.SILENT ”也可以禁止命令的回显(可参考 4.9 Makefile的特殊目标 一节),但是它不如使用“@”来的灵活。因此在书写Makefile时,我们推荐使用“@”来控制命令的回显
每一行命令将在一个独立的子shell进程中被执行(就是说,每一行命令的执行是在一个独立的shell进城中完成)。
因此,多行命令之间的执行是相互独立的,相互之间不存在依赖(多条命令行的执行为多个相互独立的进程)。
在Makefile 中书写在同一行中的多个命令属于一个完整的shell命令行(用分号隔开),书写在独立行的一条命令是一个独立的shell命令行。
在一个规则的命令中,命令行“cd”改变目录不会对其后的命令的执行产生影响。就是说其后的命令执行的工作目录不会是之前使用“cd”进入的那个目录。如果要实现这个目的,就不能把“cd”和其后的命令放在两行来书写。而应该把这两条命令写在一行上,用分号分隔。这样它们才是一个完整的shell命令行。如
foo : bar/lose
cd bar; gobble lose > ../foo
如果希望把一个完整的shell命令行书写在多行上,需要使用反斜杠(\ )来对处于多行的命令进行连接,表示他们是一个完整的shell命令行。例如上例我们以也可以这样书写:
foo : bar/lose
cd bar; \
gobble lose > ../foo
make对所有规则命令的解析使用环境变量“SHELL”所指定的那个程序,在GNU make中,默认的程序是“/bin/sh ”。
GNU make 支持同时执行多条命令。通常情况下,同一时刻只有一个命令在执行,
下一个命令只有在当前命令执行完成之后才能够开始执行
如果选项“-j ”之后存在一个整数,其含义是告诉 make在同一时刻可允许同时执行命令的数目(想想编android时候经常 make -j4)。这个数字被称为“job slots ”。当“-j ”选项之后没有出现一个数字时,那么同一时刻执行的命令数目没有要求。使用默认的“job slots ”,值为 1。
如果一个规则中的某一个命令出错(返回非 0 状态),make就会放弃对当前规则后续命令的执行,也有可能会终止所有规则的执行
命令中的“- ”号会在 shell解析并执行此命令之前被去掉,shell 所解释的只是纯粹的命令,“- ”字符是由make来处理的。例如对于“clean”目标我们就可以这么写:
clean:
-rm *.o
其含义是:即使执行“rm”删除文件失败,make也继续执行
在make执行失败时,修改错误之后执行make之前,使用“make clean”明确的删除第一次错误重建的所有目标。 (好习惯)
make在执行命令时如果收到一个致命信号(终止 make),那么 make将会删除此过程中已经重建的那些规则的目标文件。
递归调用在一个存在有多级子目录的项目中非常有用。(比如android)
例如,当前目录下存在一个“subdir ”子目录,在这个子目录中有描述此目录编译规则的makefile 文件,在执行make时需要从上层目录(当前目录)开始并完成它所有子目录的编译。那么在当前目录下可以使用这样一个规则来实现对这个子目录的编译:
subsystem:
cd subdir && $(MAKE)
其等价于规则:
subsystem:
$(MAKE) -C subdir
对这两个规则的命令进行简单说明,规则中“$(MAKE)”是对变量“MAKE ”(下一小节将详细讨论)的引用(关于变量可参考 第六章 Makefile中的变量 )。
第一个规则命令的意思是:进入子目录,然后在子目录下执行make。
第二个规则使用了make的“-C”选项,同样是首先进入子目录而后再执行make。
在使用make的递归调用时,在Makefile 规则的命令行中应该使用变量“MAKE”来代替直接使用“make”。上一小节的例子应该这样来书写:
subsystem:
cd subdir && $(MAKE)
变量“MAKE ”的值是“make”。如果其值为“/bin/make ”那么上边规则的命令就为“cd subdir && /bin/make”。这样做的好处是:当我们使用一个其它版本的 make程序时,可以保证最上层使用的make程序和其子目录下执行的make程序保持一致。(代码的通用性)
上层make可以明确指定将一些变量的定义通过环境变量的方式传递给子make过程。
当一个变量使用“export ”进行声明后,变量和它的值将被加入到当前工作的环境变量中,以后在make 执行的所有规则的命令都可以使用这个变量。而当没有使用指示符“export ”对任何变量进行声明的情况下,上层 make只将那些已经初始化的环境变量(在执行make之前已经存在的环境变量)和使用命令行指定的变量传递给子 make程序,通常这些变量由字符、数字和下划线组成.
存在两个特殊的变量“SHELL”和“MAKEFLAGS”,对于这两个变量除非使用指示符“unexport ”对它们进行声明,它们在整个 make的执行过程中始终被自动的传递给所有的子make。(比如我们可以在Makefile打印一下@echo $(SHELL))
export VARIABLE ...
当不希望将一个变量传递给子make时,可以使用指示符“unexport ”来声明这个变量。
格式如下:
unexport VARIABLE ...
传递过程中变量“MAKEFLAGS”的值会被主控 make自动的设置为包含执行make时的命令行选项的字符串。
需要注意的是有几个特殊的命令行选项例外,他们是:
“-C”、“-f ”、“-o ”和“-W”。 这些命令行选项是不会被赋值给变量“MAKEFLAGS”
的
将这样一组命令进行类似c 语言函数一样的封装,以后在我们需要用到的地方可以通过它的名字(c 语言中的函数名)来对这一组命令进行引用。这样就可减少重复工作,提高了效率。
定义一个命令包的语法以“define ”开始,以“endef”结束,例如:
define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef
第一个命令是对引用它所在规则中的第一个依赖文件(函数“firstword ”,可参考 8.2 文本处理函数 一节,就是找出第一个单词)运行yacc程序。yacc程序总是生成一个命名为“y.tab.c ”的文件。第二行的命令就是把这个文件名改为规则目标的名字
命令包是使用一个变量来表示的。因此我们就可以按使用变量的方式来使用它。
foo.c : foo.y
$(run-yacc)
命令包中的变量的替换过程:1. 命令包中的“$^”会被“foo.y”替换;2. “$@ ”被“foo.c”替换。
有时可能存在这样的一个需求,需要定义一个什么也不做的规则(不需要任何执行的命令)。前面已经有过这样的用法(参考 3.8 重载另外一个Makefile)。这样的规则,只有目标文件(可以存在依赖文件)而没有命令行。像这样定义:
target: ;
大家会奇怪为什么要定义一个没有命令的规则。其唯一的原因是,空命令行可以防止make在执行时试图为重建这个目标去查找隐含命令(包括了使用隐含规则中的命令和“.DEFAULT ”指定的命令。
Makefile 中,变量是一个名字(像是 C 语言中的宏),代表一个文本字符串(变量的值)
1.在Makefile 中,变量是一个名字(像是 C 语言中的宏),代表一个文本字符串(变
量的值)
2. 变量可以用来代表一个文件名列表、编译选项列表、程序运行的选项参数列表、
搜索源文件的目录列表、编译输出的目录列表和所有我们能够想到的事物。
3.变量名是不包括“: ”、“# ”、“= ”、前置空白和尾空白的任何字符串。需要注意
的是,尽管在GNU make中没有对变量的命名有其它的限制,但定义一个包含
除字母、数字和下划线以外的变量的做法也是不可取的
4. 变量名是大小写敏感的。
5.另外有一些变量名只包含了一个或者很少的几个特殊的字符(符号)。称它们为
自动化变量。像“$<”、“$@ ”、“$?”、“$* ”等
第一种风格的变量是递归方式扩展的变量。这一类型变量的定义是通过“= ”(参考 6.5 如何设置变量 一节)或者使用指示符“define ”(参考 6.8 多行定义 一节)定义的
foo = $(bar)
bar = $(ugh)
ugh = Huh?
all:;echo $(foo)
其优点是:
这种类型变量在定义时,可以引用其它的之前没有定义的变量(可能在后续部分定义,或者是通过make的命令行选项传递的变量)
CFLAGS = $(include_dirs) -O
include_dirs = -Ifoo -Ibar
缺点是:1.使用此风格的变量定义,可能会由于出现变量的递归定义而导致make 陷入到
无限的变量展开过程中,最终使make 执行失败。例如,接上边的例子,我们给这个变量追加值:
CFLAGS = $(CFLAGS) –O (相当于自死锁了)
2.这种风格的变量定义中如果使用了函数,那么包含在变量值中的函数总会在变量被引用的地方执行(变量被展开时)。
为了避免“递归展开式”变量存在的问题和不方便。GNU make支持另外一种风格的变量,称为“直接展开”式。这种风格的变量使用“:= ”定义。在使用“:= ”定义变量时,变量值中对其他量或者函数的引用在定义变量时被展开(对变量进行替换)。所以变量被定义后就是一个实际需要的文本串,其中不再包含任何变量的引用
(就是说只弄一次,下文再改也不修改了)
CFLAGS := $(include_dirs) -O
include_dirs := -Ifoo -Ibar
由于变量“include_dirs ”的定义出现在“CFLAGS ”定义之后。因此在“CFLAGS ”的定义中,“include_dirs ”的值为空。“CFLAGS”的值为“-O”而不是“-Ifoo -Ibar -O ”。
下边我们来看一个复杂一点的例子。分析一下直接展开式变量定义(:= )的用法,
这里也用到了make的shell函数和变量“MAKELEVEL ”(此变量在make的递归调用时
代表make的调用深度,参考 5.6.2 变量和递归 一小节,
函数“shell”所实现的功能和shell中的引用(`` )相同。实现对命令的扩展。一个shell 命令作为此函数的参数,函数的返回结果是此命令在shell中的执行结果。)。
其中包括了对函数、条件表达式和系统变量“MAKELEVEL ”的使用:
ifeq (0,${MAKELEVEL})
cur-dir := $(shell pwd)
whoami := $(shell whoami)
host-type := $(shell arch)
MAKE := ${MAKE} host-type=${host-type} whoami=${whoami}
endif
第一行是一个条件判断,如果是顶层Makefile,就定义下列变量。否则不定义任何变量。
第二、三、四、五行分别定义了一个变量,在进行变量定义时对引用到的其它变量和函
数展开。
我们可以实现在一个变量中包含前导空格并在引用此变量时对空格加以保护。像这样:
nullstring :=
space := $(nullstring) # end of the line
变量“space ”就表示一个空格。在“space ”定义行中的注释使得我们的目的更清晰(明确地描述一个空格字符比较困难)
一般变量值中的前导空格字符在变量引用和函数调用时被丢弃;make对变量进行处理时变量值中尾空格是不被忽略的
只有此变量在之前没有赋值的情况下才会对这个变量进行赋值。
例如:
FOO ?= bar
其等价于:
ifeq ($(origin FOO), undefined)
FOO = bar
endif
格式为“$(VAR:A=B)”(或者“${VAR:A=B}”),意思是,替换变量“VAR ”中所有“A”字符结尾的字为“B”结尾的字(简单来说把=前面的结尾为A的替换成B)。“结尾”的含义是空格之前(变量值多个字之间使用空格分开)。而对于变量其它部分的“A ”字符不进行替
换。例如:
foo := a.o b.o c.o
bar := $(foo:.o=.c)
在这个定义中,变量“bar”的值就为“a.c b.c c.c”。使用变量的替换引用将变量“foo ”
以空格分开的值中的所有的字的尾字符“o”替换为“c”,其他部分不变。
另外一种引用替换的技术使用功能更强大的“patsubst ”函数。它的格式和上面“$(VAR:A=B)”的格式相类似,不过需要在“A”和“B”中需要包含模式字符“% ”。
这时它和“$(patsubst A,B $(VAR))”
一个变量名(文本串)之中可以包含对其它变量的引用。这种情况我们称之为“变量的套嵌引用”或者“计算的变量名”。先看一个例子:
x = y
y = z
a := $($(x))
x = variable1
variable2 := Hello
y = $(subst 1,2,$(x))
z = y
a := $($($(z)))
$(subst FROM,TO,TEXT)
函数名称:字符串替换函数—subst。
函数功能:把字串“TEXT”中的“FROM ”字符替换为“TO”。
返回值:替换后的新字符串。
变量的套嵌引用(计算的变量名)在我们的Makefile 中应该尽量避免使用。
作为一个优秀的程序员,在面对一个复杂问题时,应该是寻求一种尽可能简单、直接并且高效的处理方式来解决,而不是将一个简单问题在实现上复杂化。如果想在简单问题上突出自己使用某种语言的熟练程度,是一种非常愚蠢、且不成熟的行为
在运行make时通过命令行选项来取代一个已定义的变量值。参考 6.7 override 指示符 一节
— 在makefile文件中通过赋值的方式(参考 6.5 如何设置变量 一节)或者使用“define ”来为一个变量赋值(参考 6.8 多行定义 一节)。
— 将变量设置为系统环境变量。所有系统环境变量都可以被make使用。参考 6.9 系统环境变量 一节
— 自动化变量,在不同的规则中自动化变量会被赋予不同的值。它们每一个都有单一的习惯性用法。参考 10.5.3 自动化变量 一小节
— 一些变量具有固定的值。参考 10.3 隐含变量 一节
Makefile 中变量的设置(也可以称之为定义)是通过“= ”(递归方式)或者“:= ”(静态方式)来实现的。“= ”和“:= ”左边的是变量名,右边是变量的值。
1. 变量名之中可以包含函数或者其它变量的引用,make在读入此行时根据已定义情况进行替换展开而产生实际的变量名。参考 6.3.2 变量的套嵌引用 一小节
2. 变量的定义值在长度上没有限制。变量定义
较长时,一个好的做法就是将比较长的行分多个行来书写,除最后一行外行与行之间使用反斜杠(\ )连接,表示一个完整的行。这样的书写方式对make的处理不会造成任何影响,便于后期修改维护而且使得你的Makefile 更清晰。例如上边的例子就可以这样写:
ojects = main.o foo.o \
bar.o utils.o
3. 当引用一个没有定义的变量时,make默认它的值为空。
4. 一些特殊的变量在make中有内嵌固定的值(可参考 10.3 隐含变量 一节),不
过这些变量允许我们在Makefile中显式得重新给它赋值。
5. 还存在一些由两个符号组成的特殊变量,称之为自动环变量。它们的值不能在
Makefile 中进行显式的修改。这些变量使用在规则中时,不同的规则中它们会
被赋予不同的值。
6. 如果你希望实现这样一个操作,仅对一个之前没有定义过的变量进行赋值。那么可以使用速记符“?=”(条件方式)来代替“ = ”或者“:= ”来实现(可参考 6.2.4 “?=”操作符 一小节)。
我们可以在定义时(也可以不定义而直接追加)给它赋一个基本值,后续根据需要可随时对它的值进行追加(增加它的值)。在Makefile 中使用“+=”(追加方式)来实现对一个变量值的追加操作。像下边那样:
例一
objects += another.o
例二直接展开式变量的追加过程
variable := value
variable += more
相当于
variable := value
variable := $(variable) more
例三递归展开式变量的追加过程
variable = value
variable += more
相当于:
temp = value
variable = $(temp) more
通常在执行make时,如果通过命令行定义了一个变量,那么它将替代在Makefile中出现的同名变量的定义。
就是说,对于一个在 Makefile 中使用常规方式(使用“= ”、“:= ”或者“define ”)定义的变量,我们可以在执行 make时通过命令行方式重新指定这个变量的值,命令行指定的值将替代出现在Makefile 中此变量的值。
如果不希望命令行指定的变量值替代在Makefile 中的变量定义,那么我们需要在Makefile 中使用指示符“override ”来对这个变量进行声明,像下边那样:
override VARIABLE = VALUE
或者:
override VARIABLE := VALUE
也可以对变量使用追加方式:
override VARIABLE += MORE TEXT
对于追加方式需要说明的是:变量在定义时使用了“override ”,则后续对它值进行追
加时,也需要使用带有“override ”指示符的追加方式。否则对此变量值的追加不会生
效
define two-lines
echo foo
echo $(bar)
endef
如果将变量“two-lines ”作为命令包执行时,其相当于:
two-lines = echo foo; echo $(bar)
就相当于第五章说的定义命令包
make在运行时,系统中的所有环境变量对它都是可见的。
在 Makefile 中,可以引用任何已定义的系统环境变量。(这里我们区分系统环境变量和make的环境变量,系统环境变量是这个系统所有用户所拥有的,而make的环境变量只是对于make的一次执行过程有效,以下正文中出现没有限制的“环境变量”时默认指的是“系统环境变量”,在特殊的场合我们会区分两者)
正因为如此,我们就可以设置一个命名为“CFLAGS”的环境变量,用它来指定一个默认的编译选项
1.在Makefile 中对一个变量的定义或者以make命令行形式对一个变量的定义,都将覆盖同名的环境变量(注意:它并不改变系统环境变量定义,被修改的环境变量只在make执行过程有效)。而make使用“-e ”参数时,Makefile 和命令行定义的变量不会覆盖同名的环境变量,make将使用系统环境变量中这些变量的定义值。
(简单来说,不加e就是用Makefile自己的环境变量,就是make覆盖系统的,但是不是修改; 加e就是用系统的环境变量)
我们来看一个例子,结束本节。假如我们的机器名为“server-cc”;我们的Makefile
内容如下:
# test makefile
HOSTNAME = server-http
…………
.PHONY : debug
debug :
@echo “hostname is : $( HOSTNAME)”
@echo “shell is $(SHELL)”
1. 执行“make debug”将显示:
hostname is : server-http
shell is /bin/sh
2. 执行“make –e debug ”;将显示:
hostname is : server-cc
shell is /bin/sh
在Makefile中定义一个变量,那么这个变量对此Makefile的所有规则都是有效的。它就像是一个“全局的”变量(仅限于定义它的那个Makefile中的所有规则,类似于c语言中的全局静态变量,使用static声明的全局变量;如果需要对其它的Makefile中的规则有效,就需要使用“export ”对它进行声明。)。
设置一个目标指定变量的语法为:
TARGET ... : VARIABLE-ASSIGNMENT
或者:
TARGET ... : override VARIABLE-ASSIGNMENT
1. “VARIABLE-ASSIGNMENT”可以使用任何一个有效的赋值方式,“= ”(递归)、“:= ”(静态)、“+=”(追加)或者“?= ”(条件)。
2. 使用目标指定变量值时,目标指定的变量值不会影响同名的那个全局变量的值。就是说目标指定一个变量值时,如果在Makefile 中之前已经存在此变量的定义(非目标指定的),那么对于其它目标全局变量的值没有变化。变量值的改变只对指定的这些目标可见。
3. 目标指定变量和普通变量具有相同的优先级。就是说,当我们使用make 命令行的方式定义变量时,命令行中的定义将替代目标指定的同名变量定义(和普通的变量一样会被覆盖)。另外当使用make的“-e ”选项时,同名的环境变量也将覆盖目标指定的变量定义。因此为了防止目标指定的变量定义被覆盖,可以使用第二种格式,使用指示符“override ”对目标指定的变量进行声明。
4. 目标指定的变量和同名的全局变量属于两个不同的变量,它们在定义的风格(递归展开式和直接展开式)上可以不同。
5. 目标指定的变量变量会作用到由这个目标所引发的所有的规则中去(1是就相当于作用域,2是说它参加的它都做主)。例如:
prog : CFLAGS = -g
prog : prog.o foo.o bar.o
这个例子中,无论 Makefile 中的全局变量“CFLAGS ”的定义是什么。对于目标“prog ”以及其所引发的所有(包含目标为“prog.o ”、“foo.o”和“bar.o”的所有则)规则,变量“CFLAGS ”值都是“-g ”。
GNU make 除了支持上一节所讨论的模式指定变量之外,还支持另外一种方式:模式指定变量(Pattern-specific Variable )
使用目标定变量定义时,此变量被定义在某个具体目标和由它所引发的规则的目标上。
而模式指定变量定义是将一个变量值指定到所有符合此模式的目标上。
设置一个模式指定变量的语法和设置目标变量的语法相似:
PATTERN ... : VARIABLE-ASSIGNMENT
或者:
PATTERN ... : override VARIABLE-ASSIGNMENT
例如我们可以为所有的.o 文件指定变量“CFLAGS ”的值:
%.o : CFLAGS += -O