make命令实质上就是一个单行shell脚本。实际上,make会获取每行命令并将它传递给subshell去执行。事实上,make会进行优化,make会通过在每个命令行中扫描shell特殊字符来进行此项检查。如果不存在任何shell特殊字符,make就会直接执行此命令,而不会将此命令传递给subshell去执行。
make默认使用/bin/sh这个shell。如果需要变更,可以通过在makefile中明确设定SHELL这个变量。
1. 解析命令
在工作目标之后,凡是第一个字符为tab符的文本一律会被视为命令(除非前一行的结尾是一个反斜线符号)。
当在非工作目标中,第一个字符使用tab字符,make将会报错。如下
makefile:20: *** commands commence before first target. Stop.
解析器所看到的命令位于合法的语境时,它会切换到“命令解析”模式,以一次一行方式建立脚本。当解析器所遇到的文本行不可能成为命令脚本的一部分时,它就会停止追加到脚本的动作。此处就带变脚本的结尾。
内置的函数将会终止“命令解析”模式,除非它前置了一个跳格符。这意味着它们必须被扩展成有效的shell命令,否则就会变成空值。例如函数warning和eval就会被扩展成空值。
1.1 持续很长的命令
因为每个命令会在它自己的shell中执行(至少看起来是这样),所以若要让一系列shell命令一起执行,则必须经过特别的处理。
例如下面的例子,假如我们需要产生一个文件,以便用来保存文件列表。Java编译器可以读取此类文件以一次编译多个源文件。
.INTERMEDIATE: file_list
file_list:
for d in logic ui \
do \
echo $d/*.java\
done > $@
上面有连个error。首先,对于循环控制变量的引用d必须加以规避;其次,因为for循环将会以单行的形式传递给subshell,所以我们必须于文件列表及for循环语句之后加上“分号”分隔符:
.INTERMEDIATE: file_list
file_list:
for d in logic ui; \
do \
echo $d/*.java; \
done > $@
请注意,在某些情况下,省略分号并不会让make或shell产生错误信息:
disk-free = echo "Checking free disk space ..." \
df . | awk `{ print $$4 }`
这个例子会先输出一段简单的信息,接着输出当前磁盘的可用块的数目。它会这么做吗?我们不经意地省略了echo命令之后的分号,因此df程序根本不会运行,只会将如下的内容送往awk:
Checking free disk space ... df .
于是awk便会忠实地输出第四个字段space...。
当你用define指令建立多行形式的命令序列时,也可能会遇到问题。这个问题跟前面的问题不太一样。当一个多行形式的宏被扩展时,宏主体中的每一行会被插入脚本并具有前导的跳格符,make会认为这样的每一行都是各自独立的命令,于是它们并不会在单一的subshell中执行。所以你也应该注意宏中命令行的延续问题。
1.2 命令修饰符
一个命令可以通过若干前缀加以修饰。我们已经多次看到“安静模式”前缀(@)被使用在前面的范例中,接下来我们会列出所有可用的前缀修饰符并加以说明
@
不要输出命令。
隐藏命令会让make的输出更容易阅读,不过这么做可能使得调试比较困难。
如果你发现自己经常需要移除@和恢复修饰符,你可以创建一个内容@修饰符的变量,例如QUIET,并将它使用在命令上:
QUIET = @
hairy_script:
$(QUIET) complex script ...
往后,如果你在make运行复杂脚本的时候需要看到脚本本身,只需要通过命令重新设定QUIET变量就行了:
$ make QUIET=hairy_script
complex script ...
-
破折号前缀(dash prefix)用来指示make应该忽略命令中的错误。
默认情况下,当make执行一个命令的时候,它会检查程序或管道的结束状态,如果返回的是非零(失败)的结束状态,make将会终止命令脚本接下的执行动作,并且结束执行。
+
加号修饰符用来要求make执行命令,就算用户是以--just-print(或-n)命令行选项来执行make的。
当要编写递归形式的makefile时,就会用到这个功能。
这些修饰符都可以使用在单行命令行上。显然,在命令行执行之前,这些修饰符都会被去除。
1.3 错误与中断
make每执行一个命令就会返回一个状态码。值为零的状态码表示命令执行成功,值为非零的状态码代表发生了某种错误。某些程序会以状态码来指示更具意义的内容,而不仅仅是代表错误。
因为每个命令会在它自己的shell中被执行,所以你经常会看到多行命令中的每个命令组件被分号隔开,好让它们在同一个shell中被执行。这样即使其中有命令组件发生错误,也不会让脚本终止运行。
评论