Chinaunix首页 | 论坛 | 博客
  • 博客访问: 189901
  • 博文数量: 30
  • 博客积分: 2500
  • 博客等级: 少校
  • 技术积分: 526
  • 用 户 组: 普通用户
  • 注册时间: 2008-02-19 10:07
文章分类

全部博文(30)

文章存档

2009年(1)

2008年(29)

我的朋友

分类: LINUX

2008-03-17 15:21:59

      make的递归执行

make的递归过程指的是:在Makefile中使用“make”作为一个命令来执行本身或者其它makefile文件的过程。递归调用在一个存在有多级子目录的项目中非常有用。例如,当前目录下存在一个“subdir”子目录,在这个子目录中有描述此目录编译规则的makefile文件,在执行make时需要从上层目录(当前目录)开始并完成它所有子目录的编译。那么在当前目录下可以使用这样一个规则来实现对这个子目录的编译:

subsystem:

cd subdir && $(MAKE)

 

其等价于规则:

 

subsystem:

$(MAKE) -C subdir

 

对这两个规则的命令进行简单说明,规则中“$(MAKE)”是对变量“MAKE”(下一小节将详细讨论)的引用(关于变量可参考 第六章 Makefile中的变量 )。第一个规则命令的意思是:进入子目录,然后在子目录下执行make。第二个规则使用了make“-C”选项,同样是首先进入子目录而后再执行make

书写这样的规则对于我们来说应该不是什么大问题,但是其中有一些需要我们深入了解的东西。首先需要了解它如何工作、上层make(在当前目录下运行的make进程)和下层makesubdir目录下运行的make进程)之间存在的联系。也许会发现这两个规则的实现,使用伪目标更能提高效率。

make的递归调用中,需要了解一下变量“CURDIR”,此变量代表make的工作目录。当使用“-C”选项进入一个子目录后,此变量将被重新赋值。总之,如果在Makefile中没有对此变量进行显式的赋值操作,那么它代表make的工作目录。我们也可以在Makefile为这个变量赋一个新的值。此时这变量将不再代表make的工作目录。

5.6.1       变量MAKE

在使用make的递归调用时,在Makefile规则的命令行中应该使用变量“MAKE”来代替直接使用“make”上一小节的例子应该这样来书写:

 

subsystem:

cd subdir &&  $(MAKE)

 

变量“MAKE”的值是“make”。如果其值为“/bin/make”那么上边规则的命令就为“cd subdir && /bin/make”这样做的好处是:当我们使用一个其它版本的make程序时,可以保证最上层使用的make程序和其子目录下执行的make程序保持一致

另外使用此变量的另外一个特点是:当规则命令行中变量MAKE时,可以改变make“-t”“--touch”),“-n”“--just-print”)和“-q”“--question”)命令行选项的效果。它所实现的功能和在规则中命令行首使用字符“+”的效果相同。

在规则的命令行中使用“make”代替了“$(MAKE)”以后,上例子规则的命令行为:“cd subdir && make”。在我们执行“make -t”“-t”选项用来更新所有目标的时间戳,而不执行任何规则的命令),结果是仅仅创建一个名为“subsystem”的文件,而不会进入到目录“subdir”去更新此目录下文件的时间戳。我们使用“-t”命令行参数的初衷是对规则中的目标文件的时间戳进行更新。而如果使“cd subdir && $(MAKE)”作为规则的命令行,执行“make -t”就可以实现我们的初衷。

变量“MAKE”的这个特点是:在规则的命令行中如果使用变量“MAKE”,标志“-t”“-n”“-q”在这个命令的执行中不起作用。尽管这些选项是告诉make不执行规则的命令行,但包含变量“MAKE”的命令行除外,它们会被正常执行。同时,执行make的命令行选项参数被通过一个变量“MAKEFLAGS”传递给子目录下的make程序

例如,当使用make的命令行选项“-t”来更新目标的时间戳或者“-n”选项打印命令时,这些选项将会被赋值给变量“MAKEFLAGS”被传递到下一级的make程序中。在下一级子目录中执行的make,这些选项会被附加作为make的命令行参数来执行,和在此目录下使用“make -t”或者“make -n”有相同的效果。

5.6.2       变量和递归

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中可以定义和上层同名的变量,不会引起变量定义冲突。

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

 

export VARIABLE ...

 

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

 

unexport VARIABLE ...

 

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

 

Y = Z

export X=$(Y)

 

其实就是“export X=Z”export时对变量进行展开,是为了保证传递给子make的变量值有效(使用当前Makefile中定义的变量值)。

“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”来声明这个变量。使用“export”将所有定义的变量传递给子Makefile时,那些名字中包含其它字符(除字母、数字和下划线以外的字符)的变量可能不会被传递给子make,对这类特殊命名的变量传递需要明确的使用“export”指示符对它进行声明虽然不带任何参数的“export”指示符具有特殊的含义,但是一个不带任何参数的“unexport”指示符却是没有任何意义的,它不会对make的执行过程(变量的传递)产生任何影响。

需要说明的是:单独使用“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时可以通过命令行来定义一个变量,像上例中的那样;前边已经提到过,这种变量是借助环境“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”的值,更不要把它设置为系统的环境变量来使用。否则可能会产生一些奇怪甚至让你感到不解的现象。

5.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”,将禁止所有关于目录信息的打印。

阅读(1427) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~