分类: LINUX
2008-03-17 15:22:46
书写Makefile时,可能有多个规则会使用相同的一组命令。就像c语言程序中需要经常使用到函数“printf”。这时我们就会想能不能将这样一组命令进行类似c语言函数一样的封装,以后在我们需要用到的地方可以通过它的名字(c语言中的函数名)来对这一组命令进行引用。这样就可减少重复工作,提高了效率。在GNU make中,可以使用指示符“define”来完成这个功能(关于指示符“define”)。通过“define”来定义这样一组命令,同时用一个变量(作为一个变量,不能和Makefile中其它常规的变量命名出现冲突)来代表这一组命令。通常我们把使用“define”定义的一组命令称为一个命令包。定义一个命令包的语法以“define”开始,以“endef”结束,例如:
define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef
这里,“run-yacc”是这个命令包的名字。在“define”和“endef”之间的命令就是命令包的主体。需要说明的是:使用“define”定义的命令包中,命令体中变量和函数的引用不会展开。命令体中所有的内容包括“$”、“(”、“)”等都是变量“run-yacc”的定义。它和c语言中宏的使用方式一样。关于变量可参考第六章 Makefile中的变量
例子中,命令包中第一个命令是对引用它所在规则中的第一个依赖文件运行yacc程序。yacc程序总是生成一个命名为“y.tab.c”的文件。第二行的命令就是把这个文件名改为规则目标的名字。
定义了这样一个命令包后,后续应该如何使用它?前面已经提到,命令包是使用一个变量来表示的。因此我们就可以按使用变量的方式来使用它。当在规则的命令行中使用这个变量时,命令包所定义的命令体就会对它进行替代。由于使用“define”定义的变量属于递归展开式变量,因此,命令包中所有命令中对其它变量的引用,在规则被执行时会被完全展开。例如这样一个规则:
foo.c : foo.y
$(run-yacc)
此规则在执行时,我们来看一下命令包中的变量的替换过程:
1. 命令包中的“$^”会被“foo.y”替换;
2. “$@”被“foo.c”替换。大家应该对“$<”和“$@”不陌生吧。
当在一个规则中引用一个已定义的命令包时,命令包中的命令体会被原封不动的展开在引用它的地方(和 c语言中的宏一样)。这些命令就成为规则的命令。因此我们也可在定义命令包时使用前缀来控制单独的一个命令行(例如“@”,“-”和“+”)。例如:
define frobnicate
@echo "frobnicating target $@"
frob-step-1 $< -o $@-step-1
frob-step-2 $@-step-1 -o $@
endef
此命令包的第一行命令执行前不会被回显,其它的命令在执行前都被回显。
另一方面,如果一个规则在引用此命令包之前使用了控制命令的前缀字符。那么这个前缀字符将会被添加到命令包定义的每一个命令行之中。例如:
frob.out: frob.in
@$(frobnicate)
这个规则执行时不会回显任何要执行的命令。
有时可能存在这样的一个需求,需要定义一个什么也不做的规则(不需要任何执行的命令)。前面已经有过这样的用法。这样的规则,只有目标文件(可以存在依赖文件)而没有命令行。像这样定义:
target: ;
这就是一个空命令的规则,为目标“target”定义了一个空命令。也可以使用独立的命令行格式来定义,需要注意的是独立的命令行必须以[Tab]字符开始。一般在定义空命令时,建议不使用命令行的方式,因为看起来空命令行和空行在感觉上没有区别。
大家会奇怪为什么要定义一个没有命令的规则。其唯一的原因是,空命令行可以防止make在执行时试图为重建这个目标去查找隐含命令(包括了使用隐含规则中的命令和“.DEFAULT”指定的命令。关于隐含规则可参考 第十章 使用隐含规则)。这一点它和伪目标有相同之处。使用空命令的目标时,需要注意:如果需要实现一个不是实际文件的目标,我们只是需要通过使用这个目标来完成对它所依赖的文件的重建动作。首先应该想到伪目标而不是空命令目标。因为一个实际不存在的目标文件的依赖文件,可能不会被正确重建。
因此,对于空命令规则,最好不要给它指定依赖文件。避免特殊情况下产生错误的情况。定义一个空命令规则,建议使用上例的格式。