5.6 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进程)和下层make(subdir目录下运行的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)”