《GNU make》学习笔记(四)---规则的命令

4.1 命令回显

    通常,make在执行命令行之前会把要执行的命令行进行输出。我们称之为“回显”,就好像我们输入命令执行一样。

    如果要执行的命令行以字符“@”开始,则make在执行时这个命令就不会被回显。典型的用法是我们在使用“echo”命令输出一些信息时。   例如:   

@echo 正在编译XXX模块......

当make执行时,会输出“正在编译XXX模块......”字串,但不会输出命令,如果没有“@”,那么,make将输出:

echo 正在编译XXX模块......

正在编译XXX模块......

     如果使用make的命令行参数“-n”或“--just-print”,那么make执行时只显示所要执行的命令,但不会真正的去执行这些命令。这个选项对于我们调试Makefile非常有用。

      而make参数“-s”或“--slient”则是禁止所有执行命令的显示,就好像所有的命令行均使用“@”开始一样。在Makefile中使用没有依赖的特殊目标“.SILENT”也可以禁止命令的回显,但是它的缺点是不如“@”灵活。因此我们在书写Makefile时,推荐使用“@”来控制命令的回显。

      介绍一下.SILENT:.出现在目标“.SILENT”的依赖列表中的文件,make在创建这些文件时,不打印出重建此文件所执行的命令。同样,给目标“.SILENT”指定命令行是没有意义的。没有任何依赖文件的目标“.SILENT”告诉make在执行过程中不打印任何执行的命令。

4.2 命令的执行

规则中,当目标需要被重建时。此规则所定义的命令将会被执行,如果是多行命令,那么make就为每一行命令使用一个独立的子shell去执行。因此,多行命令之间的执行是相互独立的,相互之间不存在依赖。

而在Makefile中书写在同一行中的多个命令属于一个完整的shell命令行,书写在独立行的一条命令是一个独立的shell命令行。所以需要注意:在一个规则的命令中,命令行“cd”改变目录不会对其后的命令的执行产生影响。就是说其后的命令执行的工作目录不会是之前使用“cd”进入的那个目录。如果要实现这个目的,就不能把“cd”和其后的命令放在两行来书写。而应该把这两条命令写在一行上,用分号分隔。如果希望把一个完整的shell命令行书写在多行上,需要使用反斜杠(\)来对处于多行的命令进行连接,表示他们是一个完整的shell命令行。
这解释了我之前的一个疑问:为什么Makefile里有cd,但是Make执行完之后还在Makefile所在的目录。这就是因为每一行命令都是独立的。

4.3 并发执行命令

GUN make可以同时执行多条命令。通常情况下,一个时刻只有一个命令在执行,下一个命令在当前命令执行完成之后才能够被执行。不过可以通过make的命令行选项“-j”或者“--job”来告诉make在同一时刻可以允许多条命令同时被执行。

如果选项“-j”之后存在一个整数,其含义是告诉make在同一时刻可允许同时执行命令的数目。这个数字被称为“job slots”。当“-j”选项之后没有出现一个数字时,那么同一时刻执行的命令数目没有要求。
并行执行命令所带来的问题是显而易见:

  1. 同一时刻的多个被执行的命令同时输出,造成输出到终端的信息的交替,显得凌乱
  2. 在同一时刻可能会存在多个命令执行进程读取标准输入,但是对于标注输入来设备来说,在同一时刻只能有一个进程访问输入设备,因此当同一时刻存在多个执行命令的进程需要读取标准输入流时其它的将会出输入流无效导致致命错误(通常此进程会得到操作系统的管道破裂信号而被终止)。
  3. 会导致make的递归调用出现问题

4.4 命令执行的错误

规则中的命令在运行结束后,make会检测命令执行的返回状态,如果返回成功,那么就在另外一个子shell下执行下一条命令。如果一个规则中的某一个命令出错(返回状态非0),make就会放弃对当前规则的执行,也有可能会终止所有规则的执行。
在一些情况下,规则中的一个命令的执行失败并不代表规则执行的错误。例如我们使用“mkdir”命令来确保存在一个目录。当此目录不存在使我们就建立这个目录,当目录存在时那么“mkdir”就会执行失败。其实我们并不希望mkdir在执行失败后终止规则的执行。为了忽略一些无关命令执行失败的情况,我们可以在命令之前加一个减号“-”(在[Tab]字符之后),来告诉make忽略此命令的执行失败。命令中的“-”号会在在shell解析并执行此命令之前被去掉,shell所解释的只是纯粹的命令,“-”字符是由make来处理的。
在执行make时,如果使用命令行选项“-i”或者“—ignore-errors”, make将会忽略所有规则中命令执行执行的错误。
没有依赖的特殊目标“.IGNORE”在Makefile中有同样的效果。.IGNORE”的方式已经很少使用,因为它没有在命令行之前使用“-”字符方式灵活。
当使用make的“-i”选项或者使用“-”字符来忽略命令执行错误时,make始终会把命令的执行结果作为成功来对待。但会提示错误信息,同时提示这个错误被忽略。
当没有使用上述三种方式来通知make忽略命令的执行错误时,而在错误发生时,就意味着定义这个命令的规则的目标不能被正确重建,同样,和此目标相关的其它目标也不会被正确重建。因此,由于先决条件不能建立,后续的命令将不会执行。在发生这样情况时,一般make会立刻退出并返回一个非0状态,表示执行失败。像对待命令执行的错误一样,我们可以使用make的命令行选项“-k”或者“--keep-going”来通知make,当出现错误时不立即退出,而是继续后续命令的执行。直到无法继续执行命令时才异常退出。。例如:使用“-k”参数,在重建一个.o文件目标时出现错误,make不会立即退出。虽然make已经知道因为这个错误而无法完成终极目标的重建,但还是继续完成其它后续的依赖文件的重建。直到执行最后链接时才错误退出。
一般“-k”参数在实际中应用主要在:当同时修改了工程中的多个文件后,“-k”参数可以帮助我们确认对那些文件的修改是正确的(可以被编译),那些文件的修改是不不正确的(不能正确编译)。例如我们修改了工程中的20个源文件,修改完成之后使用“-k”参数来进行make,它可以一次性找出修改的20个文件中哪些是不能被编译的。
通常情况下,执行失败的命令一旦改变了它所在规则的目标文件,则这个改变了的目标可能不是一个被正确重建的文件。但是这个文件的时间戳已经被更新过了(这种情况也会发生在使用一个信号来强制中止命令执行的时候)。因此在下一次执行make时,由于时间戳更新它不会被再次重建。因此终极目标的重建很难保证是正确的。为了避免这种错误的出现,应该在一次make执行失败之后使用“make clean”来清除已经重建的所有目标,之后再执行make。
我们也可以让make自动完成这个动作,实现这个目的我们只需要Makefile中定义特殊目标“.DELETE_ON_ERROR”。
推荐的做法是:在make执行失败时,修改错误之后执行make之前,使用“make clean”明确的删除第一次错误重建的所有目标。

本节小结:虽然make提供了命令行选项来忽略命令执行的错误,建议对于此选项谨慎使用。对于一些需要忽略的错误,可以用其他方式代替,例如删除命令我们可以这样写: “$(RM)”或者“rm -f”,创建目录的命令可以这样写:“mkdir –p ”等等。

4.5 中断make的执行

make在执行命令时如果收到一个致命信号(结束make),make将会删除命令重建的规则目标文件。其依据是此目标文件的当前时间戳是否和make开始时的时间戳相同。
删除这个目标文件的目的是为了确保下一次make时目标文件能够被正确重建。假设正在编译文件是键入“Ctrl-c”,在这时编译器已经开始写文件“foo.o”,但是“Ctrl-c”产生的信号关闭了编译器。这种情况时文件“foo.o”可能是不完整的,但这个内容不完整的“foo.o”文件的时间戳比源程序‘foo.c’的时间戳新。如果在make收到终止信号后不删除文件“foo.o”而直接退出,那么下次执行make时make将认为该文件已是最新而不会去重建文件它。最后在链接程序生成终极目标时可能由于某一个.o文件的不完整,导致出现一些奇怪的令人难以理解的错误信息。

同时,我们可以在Makefile中将一个目标文件作为特殊目标“.PRECIOUS”的依赖,这样可以防止make在重建这个目标时异常终止时对这个目标文件的删除动作。详见《Make的特殊目标》中.PRECIOUS目标。

4.6 make的递归执行

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

subsystem:
    cd subdir && $(MAKE)

其等价于规则:

subsystem:
    $(MAKE) -C subdir

4.6.1 变量MAKE

在使用make的递归调用时,在Makefile中规则的命令行中应该使用变量“MAKE”来代替直接使用“make”。就像上面的例子一样。
变量“MAKE”的值是“make”程序的文件名。如果其值为“/bin/make”那么上边规则的命令就为“cd subdir && /bin/make”。这样做的好处是:当我们使用一个其它版本的make程序时,可以保证最上层使用的make程序和其子目录下执行的make保持一致。

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

4.6.2 变量和递归

在make的递归执行过程中,上层make可以明确指定将一些变量的定义通过环境变量的方式传递给子make过程。但是对于没有明确指定需要传递的变量,上层make不会将其传递给子make。
如果子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”中的变量CFLAGS)传递给子make程序。
通常变量由字符、数字和下划线组成。需要注意的是:有些shell不能处理那些名字中包含(除字母、数字、下划线以外)其他字符的变量。

两个特殊的变量“SHELL”和“MAKEFLAGS”,除非使用指示符“unexport”对它们进行声明,否则在整个make的执行过程中它们会始终被自动的传递给子make。
"export"或者“unexport”的使用格式:

export VARIABLE ...

另外,一个不带任何参数的指示符“export”指示符含义是将此Makefile中定义的所有变量传递给子make过程。使用“export”将所有定义的变量传递给子Makefile时,那些名字中包含其它字符(除字母、数字和下划线以外的字符)的变量可能不会被传递给子make,对这类特殊命名的变量传递需要明确的使用“export”指示符对它进行声明。
“unexport”指示符来禁止一个变量的向下传递。这一动作也是现行版本make所默认的,因此我们就没有必要在上层的Makefile中使用它。
在多级递归调用的make执行过程中。变量“MAKELEVEL”代表了调用的深度。在make一级级的执行过程中变量“MAKELEVEL”的值不断的发生变化,通过它的值我们可以了解当前make递归调用的深度。最上一级时“MAKELEVEL”的值为“0”、下一级时为“1”、再下一级为“2”……

4.6.3 命令行选项和递归

在make的递归执行过程中,最上层(可以称之为主控)make的命令行选项“-k”、“-s”等被自动的通过环境变量“MAKEFLAGS”传递给子make进程。同样,在执行make时命令行中给定了一个变量的定义(如“make CFLAGS+=-g”),此变量和它的值(CFLAGS+=-g)也会借助环境变量“MAKEFLAGS”传递给子make进程。
需要注意的是有几个特殊的命令行选项例外,分别是:“-C”、“-f”、“-o”和“-W”,这些命令行选项不会被赋值给变量“MAKEFLAGS”。
执行多级的make调用时,如果不希望“MAKEFLAGS”的值传递给子make,就需要在执行子make时对它重新进行赋值。例如:

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

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

在执行make的同时可以通过命令行来定义一个变量,像上例中的那样;。其实真正的命令行中的变量定义 是通过另外一个变量“MAKEOVRRIDES”来记录的,变量“MAKEFLAGS”引用此变量,因而命令行中的变量定义就可以被记录在环境变量“MAKEFLAGS”中被传递下去。当不希望将上层make在命令行中定义的变量传递给子make时,就可以在上层Makefile中把“MAKEOVERRIDES”赋空来实现(MAKEOVERRIDES=)。这种方式一般很少使用,建议非万不得已您还是最好不使用这种方式。
另外一些系统中对环境变量的长度存在一个上限,因此当“MAKEFLAGS”的值超过一定的数目时,执行过程出现了类似“Arg list too long”的错误提示。

4.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'.

通常,在主控Makefile中当如果使用“-C”参数来为make指定一个目录或者使用“cd”进入一个目录时,“-w”选项会被自动打开。例如,在主Makefile中有:

test:
    $(MAKE) -C test

执行时会提示:make[1]: Entering directory `/home/summer/code/make/test'
主控make可以使用选项“-s”(“--slient”)来禁止此选项的自动打开。另外,make的命令行选项“--no-print-directory”,将禁止所有关于目录信息的打印。

猜你喜欢

转载自blog.csdn.net/Colorful_lights/article/details/80850402
GNU