GNU make 总结 (三)

一、makefile 变量

makefile中的变量名是大小写敏感的,例如”foo”和”Foo”是两个不同的变量。通常情况下,对于一般变量,我们可以使用小写形式,而对于参数变量,采用全大写形式。当我们定义好一个变量后,采用”$(VARIABLE_NAME)”或者”${VARIABLE_NAME}”形式使用这个变量。所有在命令或者文件名中使用"$"时需要使用两个”$$”来表示。变量的展开可以看作是C语言中的宏展开,是一个文本替换过程。

1 两种定义变量的方式

1.1  递归展开式变量

使用”=”或者”define”定义,这种变量在使用时视为文本替换过程。所谓递归展开就是,变量在定义时不会展开,而是在使用变量时,其所指的内容才会层层展开。其优点是在变量定义时,可以引用之前没有定义的变量(可能在后续部分定义或者是通过make命令行选项传递的变量);其缺点是可能出现递归调用而陷入死循环中。

1.2  直接展开式变量

使用”:=”定义。这种方式下,对其他变量的引用在定义时就会被展开,因此不能引用后续定义的变量。在比较复杂的makefile中,尽量使用这种方式定义变量,以减少出错机率。

注:使用直接展开式时,可以实现在一个变量中包含一个前导空格,如下所示:

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

space变量表示一个空格,注释和$(nullstring)之间有一个空格。

但是要注意make处理变量时,尾部的空格不会被忽略。

在变量定义时,如果该变量之前没有被定义过,才对这个变量赋值,否则保持该变量值不变,这个操作可以通过”?=”来实现。

2 变量的使用

“$(VAR:A=B)”表示将变量VAR中所有以A结尾的字替换为以B结尾的字(所谓“字”就是以空格隔开的不同字符串)。变量的其他部分保持不变。

比如:

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

替换后,变量bar的内容就是a.c b.c c.c

另一种变量替换使用”patsubst”函数。需要用到模式字符%,如下所示:

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

当使用一个没有定义的变量时,make默认它的值为空。

某些特殊的变量在make中有固定的值,但是我们也可以显式地重新给它赋值。

3 追加变量值

如果想要在已有的变量上追加一个值,可以使用”+=”操作实现,如下:

object = main.o foo.o bar.o utils.o
object += another.o

如此,object的值就变为:main.o foo.o bar.o utils.o another.o

4 override指示符

执行make时,命令行的优先级高于makefile文件中的定义,也就是说,如果通过命令行定义了一个变量,那么它将替代在makefile中出现的同名变量的定义。如果不希望makefile中的变量被命令行传递的值所取代,需要使用override来对这个变量进行声明,如下所示:

override VAR = value
或者:
override VAR := value
或者:
override VAR += more

注:如果之前定义变量时使用了override,那么对该变量追加时必须使用override,否则追加操作不会生效。使用override的目的并不是为了调整命令行参数和makefile变量的冲突,主要是为了对于某些需求i指定不变量。例如,编译时必须使用“-g”选项,那么我们就可以将CFLAGS定义如下:

override CFLAGS += -g

这样,无论命令行中传入哪些参数,我们都可以保证”-g”选项一直存在。

5 多行定义

define指示符定义了一个包含多行字符串的变量。格式如下:

define lines
echo foo
echo $(bar)
endef

以define开始,endef结束。lines为变量名,中间为变量值。

如果将变量lines作为命令包来执行,相当于:

“lines = echo foo; echo $(bar)”

注:变量值中可以包含:换行符、空格符、Tab等。如果以Tab开始,那么该行将被作为命令行来处理。

6 系统环境变量

makefile中可以使用任何已经定义了的系统环境变量。如果对一个已有的系统环境变量进行了重新定义,那么此次make执行过程将使用用户自定义的变量值,除非在make时使用“-e”选项,此时,用户的定义不会覆盖同名的系统环境变量。

默认情况下,所有的系统环境变量和命令行参数都会被传递给子make进程,如果需要将普通变量也传递到子make中,需要使用”export”指示符进行变量声明。

注:系统环境变量“SHELL”比较特殊,用于作为用户和系统的交互接口,因此make中对这个变量进行了重新设置,将其设为“/bin/sh”。

7 目标指定变量

一个目标指定变量的作用域是所有这些目标的上下文,包括和这个目标相关的所有执行过程,具有“局部性”。格式如下:

TARGET ... : VARIABLE-ASSIGNMENT
或者:
TARGET ... : override VARIABLE-ASSIGNMENT

VARIABLE-ASSIGNMENT为赋值表达式,可以使用”=” / ”:=” / ”+=” / ”?=”

为某个变量指定具体值只影响与之相关的TARGET,其余目标不受影响。

目标指定变量与普通变量拥有相同的优先级,因此可能受到命令行参数的影响,此时需要使用”-e”选项或者override。

目标指定变量和同名的全局变量属于两个不同的变量,可以拥有不同的定义风格。

8 模式指定变量

使用目标指定变量定义时,此变量被定义在某个具体目标及其所引发的规则的目标上。而模式指定变量定义将一个变量值指定到所有符合此模式的目标上。设置一个模式指定变量如下:

PATTERN ... : VARIABLE-ASSIGNMENT
或者:
PATTERN ... : override VARIABLE-ASSIGNMENT

与目标指定变量相比,此处的模式使用模式字符%,如%.a,%.o等。如果单独使用%作为目标,则指定的变量对于所有类型的目标都是有效的。

二、makefile 命令

规则的命令是由一些shell命令组成的,它们被一条一条地执行。规则中除了第一条紧跟在依赖列表之后使用分号隔开的命令以外,其它的命令行都必须以Tab字符开始。

多个命令行之间可以有空行和注释行,但是注意:如果一个空行是以Tab键开始的,那就不是普通的空行,而是一个空命令行。

通常系统中可能存在多个不同的shell,如果没有明确指定,那么所有规则中的命令行使用“/bin/sh”来解析。

1 命令的回显

通常情况下,make在执行命令之前都会把命令本身显示到标准输出设备上。但是如果命令行是以”@”开始的,那么就会取消执行前的回显功能。例如:

@echo begin...

执行时显示:begin...

如果在执行make时,命令行中传入了“-n”或者“—just-print”,那么make将只是回显所要执行的命令,包括以”@”开始的命令,而不会去执行这些命令。使用这个选项,我们就会按照执行顺序打印出makefile中所有需要执行的命令。

相反地,make的“-s”或者“--silent”将禁止所有执行命令的显示功能,相当于所有命令都是以“@”开始的一样。

2 命令的执行

规则中,当目标需要被重建时,此规则所定义的命令将会被执行。如果是多行命令,那么每一行命令都使用一个独立的子shell进程执行(即多行命令的执行是相互独立的多个进程,不存在依赖关系)。

makefile中写在同一行中的多个命令属于一个完整的shell命令行,最终是作为一个独立的shell进程执行的。因此,如果在makefile中要使用cd先进入某个目录,然后在该目录下执行某个命令,必须将cd作为一个shell命令行书写,cd的执行对其后的命令是没有影响的,具体形式如下:

foo : bar/lose
cd bar; gobble lose > ../foo
也可以:
foo : bar/lose
cd bar; \
gobble lose > ../foo

3 并发执行命令

通常情况下,下一个命令只有在当前命令执行完成后才能开始执行。如果需要make在同一时刻可以执行多条命令,可以使用“-j”或者“--job”。“-j”后可以接一个整数,告诉编译器在同一时刻允许执行命令的数目,这个数目被称为“job slots”,其默认值为1,表示make将串行执行规则的命令。

注:在同一个时刻可能存在多个命令同时读取标准输入,但是标准输入设备在同一时刻只能存在一个进程访问它,此时操作系统发出管道破裂信号。

执行make时,如果某一条命令执行失败,且该命令产生的错误不可忽略,那么其它用于重建同一目标的命令执行也将会被终止。此时,如果make没有使用“-k”或者“—keep-going”选项,make将停止执行而退出。但是,如果make即将被终止时,而其子进程正在执行,那么make必须等到所有这些子进程结束之后才真正退出。

执行make时,如果系统处于超负荷状态下,那么需要控制make程序所占用的系统负载,使用“-l”或者“—max-load”选项,后面接一个浮点数,比如-l 2.5告诉make程序当系统平均负载超过2.5时,不再启动任何执行命令的子程序。不带浮点数的“-l”用于取消前面设置的“-l”。默认情况下无负载限制。

4 命令执行错误

通常,规则中的命令在运行结束后,make会检查命令执行的返回状态,如果返回成功,那么就启动另一个子shell进程来执行下一条命令。有时,命令执行失败并不表示规则执行错误。为了忽略命令执行失败的情况,可以在命令前加上-,例如:

clean :
-rm *.o

表示即使rm删除文件失败,make也将继续执行。

在执行make时,使用命令行选项“-i”或者“—ignore-errors”同样可以忽略所有命令执行的错误。

5 make的中断

make在执行命令时如果收到一个致命信号,那么make将会删除该过程中已经重建的那些规则的目标文件,确保下一次make时目标文件能够被正确重建。假设正在编译foo.c时,接收到一个“Ctrl+C”信号,但是此时可能已经开始创建foo.o了,只是没有完成。如果不删除foo.o,下一次执行make时,由于foo.o的时间戳比foo.c新,因此不会重建foo.o,因此得到了不正确的目标文件。

当然如果目标文件的存在只是为了记录命令执行时间,那么此时该目标文件是不应该被删除的,可以将这个目标文件作为.PRECIOUS的依赖,即使make异常终止,该文件也不会被删除。

6 make的递归执行

make执行命令本身或者执行其他makefile文件的过程称为make的递归执行。递归调用在一个多级目录中非常有用,例如,当前目录下存在一个“subdir”子目录,该子目录中包含一个makefile文件,在执行make时需要从当前目录开始并完成对所有子目录的编译:

subsystem :
cd subdir && $(MAKE)

表示先进入subdir目录,然后在该目录下执行make工作。

注:变量MAKE的值为“make”。

7 变量和递归

在make的递归执行过程中,上层的make可以明确指定将一些变量的定义通过环境变量的方式传递给子make进程,但是其并不会覆盖子make进程所执行的makefile文件中所定义的同名变量,以子make中的变量定义为准,除非使用make的“-e”选项。

如果需要将上层make过程中的makefile变量传递到子make进程,需要将变量声明为export,这样就会把变量加入到环境变量中去,如下所示:

export VARIABLE ...

如果不希望将一个变量传递给子make,那么可以使用unexport声明,如下:

unexport VARIABLE ...

注:对于SHELL和MAKEFLAGS这两个变量,除非使用unexport对其进行声明,否则它们在整个make执行过程中始终被自动传递到子make进程中。

使用export和unexport声明的变量或者函数,其在声明处会被立即展开。

如果export不带任何变量,表示将此makefile中定义的所有变量传递给子make进程;但是一个不带任何参数变量的unexport没有任何意义。

在多级递归调用的make程序中,变量MAKELEVEL代表了调用的深度。在make的执行过程中,MAKELEVEL的值不断发生变化,最上一级时为0,下一级为1,...,该变量主要用于条件测试命令中。

8 命令行选项和递归

上层make的命令行选项“-k”“-s”等会被自动通过环境变量MAKEFLAGS传递给子make进程中。同样地,命令行中的变量定义如make CFLAGS += -g也会借助环境变量MAKEFLAGS传递给子make进程。

注:如下命令行选项不会赋值到MAKEFLAGS中去:“-C”“-f”“-o”“-W”。

当执行多级的make调用时,如果不希望传递MAKEFLAGS给子make进程,需要在调用子make时将其置空,如下:

subsystem :
cd subdir && $(MAKE) MAKEFLAGS =

如此,也就取消了子make执行时对父make命令行选项的继承关系。

9 –w选项

在执行多级的make调用中,选项“-w”或者“—print-directory”可以使得在开始编译一个目录之前和完成目录编译之后给出相关的提示信息,以方便开发人员跟踪make的执行过程。当然很多选项执行时也会自动加上“-w”参数。如果想要禁止打印目录提示,可以使用“-s”选项或者命令行选项“—no-print-directory”。

10 定义命令包

如果多个不同的规则都使用一组相同的命令,那么将这组命令定义为一个命令包比较方便,类似于函数或者方法。使用define来完成这个功能,格式如下:

define run-yacc
  yacc $(firstword $^)
  mv y.tab.c $@
endef

其中,run-yacc为命令包名,中间为命令体。命令体中的变量和函数不会被展开,等到其被使用时才展开。

如下使用:

foo.c : foo.y
$(run-yacc)

11 空命令

在执行make程序中,有时需要阻止make为重建目标而去查找隐含命令,此时可以使用空命令方式。对于空命令,最好不要存在任何依赖文件。

可以使用如下格式:

target : ;

或者是一个独立的空命令行,但是命令行必须以Tab字符开始。一般不建议这种做法,因为空命令行和空行看上去没什么区别。

上一篇:android 从 phonegap 到 js webview 交互


下一篇:Mysql监控工具小集合