一、命令回显

通常,make 在执行命令行之前会把要执行的命令行输出到标准输出设备。我们称之为“回显”,就好像我们在 shell 环境下输入命令执行时一样。

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

@echo 开始编译 XXX 模块 ......

执行时,将会得到“开始编译 XXX 模块......”这条输出信息。如果在命令行之前没有字符“@”,那么,make 的输出将是:
echo 编译 XXX 模块 ......
编译 XXX 模块 ......

另外,如果使用make的命令行参数“-n”或“--just-print”,那么make执行时只显示所要执行的命令,但不会真正的去执行这些命令。只有在这种情况下make才会打印出所有make需要执行的命令,其中也包括了使用“@”字符开始的命令。这个选项对于我们调试Makefile非常有用,使用这个选项我
们可以按执行顺序打印出Makefile中所有需要执行的所有命令。



二、命令的执行

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

在 Makefile 中书写在同一行中的多个命令属于一个完整的 shell 命令行,书写在独立行的一条命令是一个独立的 shell 命令行。因此:在一个规则的命令中,命令行“cd”改变目录不会对其后的命令的执行产生影响。就是说其后的命令执行的工作目录不会是之前使用“cd”进入的那个目录。如果要实现这个目的,就不能把“cd”和其后的命令放在两行来书写。而应该把这两条命令写在一行上,用分号分隔。这样它们才是一个完整的 shell 命令行。如:

foo : bar/lose
cd bar; gobble lose > ../foo

如果希望把一个完整的 shell 命令行书写在多行上,需要使用反斜杠(\)来对处于
多行的命令进行连接,表示他们是一个完整的 shell 命令行。例如上例我们以也可以这
样书写:
foo : bar/lose
cd bar; \
gobble lose > ../foo

make 对所有规则命令的解析使用环境变量“SHELL”所指定的那个程序,在 GNUmake 中,默认的程序是“/bin/sh”。

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

如果选项“-j”之后存在一个整数,其含义是告诉 make 在同一时刻可允许同时执行命令的数目。这个数字被称为“job slots”。当“-j”选项之后没有出现一个数字时,那么同一时刻执行的命令数目没有要求。使用默认的“job slots”,值为 1。表示 make将串行的执行规则的命令(同一时刻只能有一条命令被执行)。

并行执行命令所带来的问题是显而易见地:
1. 多个同时执的命令的输出信息将同时被输出到终端。当出现错误时很难根据一大堆凌乱的信息来区分是哪条命令执行错误。

2. 在同一时刻可能会存在多个命令执行进程同时读取标准输入,但是对于标准输入设备来说,在同一时刻只能存在一个进程访问它。

3. 会导致make的递归调用出现问题。



三、中断make的执行

make 在执行命令时如果收到一个致命信号(终止 make),那么 make 将会删除此过程中已经重建的那些规则的目标文件。其依据是此目标文件的当前时间戳和 make 开始执行时此文件的时间戳是否相同。

假设正在编译时键入“Ctrl-c”,此时编译器已经开始写文件“foo.o”,但是“Ctrl-c”产生的信号关闭了编译器。这种情况下文件“foo.o”可能是不完整的,但这个内容不完整的“foo.o”文件的时间戳比源程序‘foo.c’的时间戳新。如果在 make 收到终止信号后不删除文件“foo.o”而直接退出,那么下次执行make 时此文件被认为已是最新的而不会去重建它。最后在链接生成终极目标时由于某一个.o 文件的不完整,可能出现一堆令人难以理解的错误信息,或者产生了一个不正确的终极目标。

推荐的做法是:在 make 执行失败时,修改错误之后执行 make 之前,使用“make clean”明确的删除第一次错误重建的所有目标。



四、make的递归执行

make 的递归过程指的是:在 Makefile 中使用“make”作为一个命令来执行本身或者其它 makefile 文件的过程。

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

subsystem:
cd subdir && $(MAKE)

其等价于规则:
subsystem:
$(MAKE) -C subdir

对这两个规则的命令进行简单说明,规则中“$(MAKE)”是对变量“MAKE”的引用。一个规则命令的意思是:进入子目录,然后在子目录下执行make。第二个规则使用了make的“-C”选项,同样是首先进入子目录而后再执行make。

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

变量make

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

subsystem:
cd subdir && $(MAKE)

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

变量和递归

在 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”和“MAKEFLAGS”,对于这两个变量除非使用指示符“unexport”对它们进行声明,它们在整个 make 的执行过程中始终被自动的传递给所有的子 make。

需要将一个在上层定义的变量传递给子 make,应该在上层 Makefile 中使用指示符“export”对此变量进行声明。格式如下:
export VARIABLE ...
当不希望将一个变量传递给子 make 时,可以使用指示符“unexport”来声明这个变量。格式如下:
unexport VARIABLE ...

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


“export”更方便的用法是在定义变量的同时对它进行声明。看下边的几个例子:
1.
export VARIABLE = value
等效于:
VARIABLE = value
export VARIABLE

命令行选项和递归

在 make 的递归执行过程中。最上层(可以称之为主控) make 的命令行选项“-k”、“-s”等会被自动的通过环境变量“MAKEFLAGS”传递给子 make 进程。

如果在执行 make 时通过命令行指定了“-k”和“-s”选项,那么“MAKEFLAGS”的值会被自动设置为“ks”。子 make 进程在处理时,会把此环境变量的值作为执行的命令行参数,因此子 make 过程同样也会有“-k”和“-s”这两个命令行选项。

同样,执行 make 时命令行中给定的一个变量定义(如“make CFLAGS+=-g”),此变量和它的值(CFLAGS+=-g)也会借助环境变量“MAKEFLAGS”传递给子 make进程。可以借助 make 的环境变量“MAKEFLAGS” 传递我们在主控 make 所使用的命令行选项给子 make 进程。

执行多级的 make 调用时,当不希望传递“MAKEFLAGS”的给子 make 时,需要在调用子 make 是对这个变量进行赋空。例如:

subsystem:
cd subdir && $(MAKE) MAKEFLAGS=
此规则取消了子 make 执行时对父 make 命令行选项的继承(将变量“MAKEFLAGS”的值赋为空)。