2010年2月19日星期五

Makefile中变量和递归

Makefile中变量和递归

make的递归执行过程中,上层make可以明确指定将一些变量的定义通过环境变量的方式传递给子make过程。没有明确指定需要传递的变量,上层make不会将其所执行的Makefile中定义的变量传递给子make过程。使用环境变量传递上层所定义的变量时,上层所传递给子make过程的变量定义不会覆盖子make过程所执行makefile文件中的同名变量定义。

如果子make过程所执行Makefile中存在同名变量定义,则上层传递的变量定义不会覆盖子Makefile中定义的值。就是说如果上层make传递的变量和子make所执行的Makefile中存在重复的变量定义,则以子Makefile中的变量定义为准。除非使用make的“-e”选项。

我们在本届的第一段中提到,当上层make过程要将所执行的Makefile中的变量传递给子make过程时,需要明确地指出。在GNU make中,实现此功能的指示符是“export”。当一个变量使用“export”进行声明后,变量和变量的值将被加入到当前工作的环境变量中,以后make所执行的所有规则的命令都可以使用这个变量。而当没有使用指示符“export”对任何变量进行声明的情况下,上层make只将那些已经初始化的环境变量(在执行make之前已经存在的环境变量)和使用命令行指定的变量(如命令“make CFLAGS +=-g”或者“make –e CFLAGS +=-g”)传递给子make程序,通常这些变量由字符、数字和下划线组成。需要注意的是:有些shell不能处理那些名字中包含(除字母、数字、下划线以外)其他字符的变量。

两个特殊的变量“SHELL”和“MAKEFLAGS”除非使用指示符“unexport”对它们进行声明,否则在整个make的执行过程中它们会始终被自动的传递给子make。另外一个变量“MAKEFILES”,如果此变量有值(不为空)那么同样他会被自动的传递给子make。在没有使用关键字“export”声明的变量,make执行时不会被自动传递给子make过程,因此下层Makefile中可以定义和上层同名的变量,这样不会引起变量定义冲突。

上层Makefile中定义的某一个变量需要传递给子make时,应该在上层Makefile中使用指示符“export”对此变量进行声明。格式如下:

 

export VARIABLE ...

 

当不希望将一个变量传递给子make时,可以使用指示符“unexport”来声明这个变量。格式如下:

 

unexport VARIABLE ...

 

在以上的两种格式,指示符“export”或者“unexport”的参数(变量部分),如果它是对一个变量或者函数的引用,这些变量或者函数将会被立即展开。并赋值给export或者unexport的变量。例如:

 

Y = Z

export X=$(Y)

 

等价于“export X=Z”。在这里进行展开是为了保证传递给子make的此变量的值有效。

export”更方便的用法是在定义此变量时同时对它进行声明。如下的几个例子:

1.         

export VARIABLE = value

 

等效于:

 

VARIABLE = value

export VARIABLE

 

2.         

 

export VARIABLE := value

等效于:

 

VARIABLE := value

export VARIABLE

 

3.         

export VARIABLE += value

 

等效于:

 

VARIABLE += value

export VARIABLE

 

其实在Makefile中指示符“export”和“unexport”的功能和在shell下功能相同。

另外一个不带任何参数的指示符“export”指示符:

 

export

 

含义是将此Makefile中定义的所有变量传递给子make过程。如果不需要传递其中一个变量,可以使用指示符“unexport”来声明这个变量,这个被声明的变量就不会被传递给子make。使用“export”将所有定义的变量传递给子Makefile时,那些名字中包含其它字符(除字母、数字和下划线以外的字符)的变量可能不会被传递给子make,对这类特殊命名的变量传递需要明确的使用“export”指示符对它进行声明。

需要说明的是:单独使用“export”来导出所有变量的行为是老版本GNU make所默认的。但是在新版本的GNU make中取消了这一默认的行为。因此在编写和老版本GNU make兼容的Makefile时,需要使用特殊目标“.EXPORT_ALL_VARIABLES”来代替“export”,此特殊目标的功和不带参数的“export”相同。它会被老版本的make忽略,只有新版本的make能够识别这个特殊目标。

因为,如果在老版本的GNU make中使用指示符“export”,将会出现语法错误。例如为了和老版本兼容可以这样来声明一些变量:

 

.EXPORT_ALL_VARIABLES

VARIABLE1=var1

VARIABLE2=var2

 

这样对于不同版本的make来说都是兼容的,其含义是将特殊目标“.EXPORT_ALL_VARIABLES”的依赖中的所有变量全部传递给子make

和指示符“export”相似,也可以使用单独的“unexport”指示符来禁止一个变量的向下传递。这一动作也是现行版本make所默认的,因此我们就没有必要在上层的Makefile中使用它。在多级的make递归调用中,我么可以在中间的Makefile中使用它来限制上层传递来的变量再向下传递。需要明确的是,不能使用“export”或者“unexport”来实现命令中使用的变量控制功能。就是说,不能做到用这两个指示符来限制某个(些)变量在执行特定命令时有效,而对于其它的命令则无效。在Makefile中,最后一个出现的指示符“export”或者“unexport”决定整个make运行过程中变量是否进行传递。

在多级递归调用的make执行过程中。变量“MAKELEVEL”代表了调用的深度。在make一级级的执行过程中变量“MAKELEVEL”的值不断的发生变化,通过它的值我们可以了解当前make递归调用的深度。最上一级时“MAKELEVEL”的值为“0”、下一级时为“1”、再下一级为“2.......例如:

Main目录下的Makefile清单如下:

#maindir Makefile

       ………

       ………

       .PHONY :test

       test:

              @echo “main makelevel = $(MAKELEVEL)”

              @$(MAKE) –C subdir dislevel

 

#subdir Makefile

       ………..

       ………..

       .PHONY : test

       test :

              @echo “subdir makelevel = $(MAKELEVEL)”

 

maindir 目录下执行“make test”。将显式如下信息:

main makelevel = 0

make[1]: Entering directory `/…../ subdir '

subdir makelevel = 1

make[1]: Leaving directory `/…../ subdir '

 

在主控的MakefileMAKELEVEL 为“0”,而在subdirMakefile中,MAKELEVEL为“1”。

这个变量主要用在条件测试指令中。例如:我们可以通过测试此变量的值来决定是否执行递归方式的make调用或者其他方式的make调用。我们希望一个子目录必须被上层make进行调用才能可以执行此目录下的Makefile,而不允许直接在其所在的目录下执行make。我们可以这样实现:

 

.......

$(ifeq $(MAKELEVEL),0)

all : msg

else

all : other   

endif

 

……

…...

 

msg:

@echo ”Can not make in this directory!”

……

……

 

当在包含次条件判断的Makefile所在的目录下执行make时,将会得到提示“Can not make in this directory!”。

5.6.3       命令行选项和递归

make的递归执行过程中。最上层(可以称之为主控)make的命令行选项“-k”、“-s”等被自动的通过环境变量“MAKEFLAGS”传递给子make进程。变量“MAKEFLAGS”的值会被主控make自动的设置为包含执行make时的命令行选项的字符串。在主控执行make时使用“-k”和“-s”选项,那么“MAKEFLAGS”的值就为“ks”。子make进程处理时,会把此环境变量的值作为执行的命令行选项,因此子make进程就使用“-k”和“-s”这两个命令行选项。

同样,在执行make时命令行中给定了一个变量的定义(如“make CFLAGS+=-g”),此变量和它的值(CFLAGS+=-g)也会借助环境变量“MAKEFLAGS”传递给子make进程。可以借助make的环境变量“MAKEFLAGS 传递我们在主控make所使用的命令行选项给子make进程。需要注意的是有几个特殊的命令行选项例外,分别是:“-C”、“-f”、“-o”和“-W”。这些命令行选项不会被赋值给变量“MAKEFLAGS”。

Make命令行选项中一个比较特殊的是“-j”选项。在支持这个选项的操作系统上,如果给它指定了一个数值“N”(多任务的系统unixLinux支持,MS-DOS不支持),那么主控make和子make进程会在执行过程使用通信机制来限制系统在同一时刻(包括所有的递归调用的make进程,否则,将会导致make任务的数目数目无法控制而使别的任务无法到的执行)的任务的执行数目不大于“N”。另外,当使用的操作系统不能支持make执行过程中的父子间通信,那么无论在执行主控make时指定的任务数目“N”是多少,变量“MAKEFLAGS”中选项“-j”的数目会都被设置为“1”,这样可以确保系统正常运转。

执行多级的make调用时,如果不希望“MAKEFLAGS”的值传递给子make,就需要在执行子make时对它重新进行赋值。例如:

 

subsystem:

      cd subdir && $(MAKE) MAKEFLAGS=

 

此规则取消了子make执行式的命令行选项(将变量的值赋为空)。

在执行make的同时可以通过命令行来定义一个变量,像上例中的那样;前边已经提到过,这种变量是借助环境“MAKEFLAGS”来传递给多级调用的子make进程的。其实真正的命令行中的 变量定义 是通过另外一个变量“MAKEOVRRIDES”来记录的,变量“MAKEFLAGS”引用此变量,因而命令行中的变量定义就可以被记录在环境变量“MAKEFLAGS”中被传递下去。当不希望将上层make在命令行中定义的变量传递给子make时,就可以在上层Makefile中把“MAKEOVERRIDES”赋空来实现(MAKEOVERRIDES=)。这种方式一般很少使用,建议非万不得已您还是最好不使用这种方式(为了和POSIX2.0兼容,当Makefile中出现“.POSIX”这个特殊的目标时,在上层Makefile中修改变量“MAKEOVERRIDES”对子make不会产生任何影响)。另外一些系统中对环境变量的长度存在一个上限,因此当“MAKEFLAGS”的值超过一定的数目时,执行过程出现了类似“Arg list too long”的错误提示。

历史原因,在make中存在另外一个和“MAKEFLAGS”相类似的变量“MFLAGS”。现行版本中此变量保留的原因是和老版本的兼容需要。和“MAKEFLAGS”不同点是:1. 此变量在make的递归调用时不包含命令行选项中的变量定义部分(就是说此变量的定义没有包含对“MAKEOVERRIDES”的引用);2. 此变量的值(除为空的情况)是以“-”开始的,而“MAKEFLAGS”的值只有在长命令选项格式(如:“--warn-undefined-variables”)时才以“-”开头。传统得此变量一般被明确的使用在make递归调用命令中。像下边那样:

 

subsystem:

      cd subdir && $(MAKE) $(MFLAGS)

 

在现行的make版本中,变量“MFLAGS”已经成为一个多余部分。在书写和老版本make兼容的Makefile时可能需要这个变量。当然它在目前的版本上也能够正常的工作。

在某些特殊的场合,可能需要为所有的make进程指定一个统一的命令行选项。比如说需要给所有的运行的make指定“-k”选项。实现这个目的,我们可以在执行make之前设置一个系统环境变量(存在于当前系统的环境中)“MAKEFLAGS=k”,或者在主控Makefile中将它的值赋为“k”。需要注意的是:不能通过变量“MFLAGS”来实现。

make在执行时,首先将会对变量“MAKEFLAGS”的值(系统环境中或者在Makefile中设置的)进行分析。当变量的值不是以连字符(“-”)开始时,将变量的值按字分开,字之间使用空格分开。将这些字作为命令行的选项对待(除了选项“-C”、“-f”、“-h”、“-o”和“-W”以及他们的长格式,如果其中包含无效的选项也不会提示错误)。

最后需要强调的是:当把“MAKEFLAGS”设置到你的系统环境变量中时,要小心谨慎!将一些调试选项或者特殊选项设置为此变量值的一部分,在执行make时,会对make的正常执行产生影响,甚至是破坏性的影响。例如变量“MAKEFLAGS”中包含选项“t”、“n”、“q”这三个的任何一个,你在执行make时产生的结果可能并不是你所要达到的目的。建议大家最好不要随便更改“MAKEFLAGS”的值,更不要把它设置为系统的环境变量来使用。否则可能会产生一些奇怪甚至让你感到不解的现象。

1.6.4       -w选项

在多级make递归调用过程中,选项“-w”或者“--print-directory”可以让make在开始编译一个目录之前和完成此目录的编译之后给出相应的提示信息,方便开发人员能够跟踪make的执行过程。例如,在目录“/u/gnu/make”目录下执行“make -w”,将会看到如下的一些信息:

在开始执行之前我们将看到:

 

make: Entering directory `/u/gnu/make'.

 

而在完成之后我们同样将会看到:

 

make: Leaving directory `/u/gnu/make'.

 

通常,选项“-w”会被自动打开。在主控Makefile中当如果使用“-C”参数来为make指定一个目录或者使用“cd”进入一个目录时,“-w”选项会被自动打开。主控make可以使用选项“-s”(“--slient”)来禁止此选项的自动打开。另外,make的命令行选项“--no-print-directory”,将禁止所有关于目录信息的打印。

没有评论: