make(3/3):命令与调试技巧

命令

make命令实质上是一个单行shell脚本。实际的执行过程是:make先检查命令中是否有特殊字符(可能影响当前程序的行为),如果没有,就直接执行;如果有,就将其传递给subshell执行(使用fork/exec)。

make命令默认会交给/bin/sh执行,这由SHELL变量指定,出于可移植性的考虑,一般不建议修改。(不过改成bash还是可以接受的)

命令的解析

在工作目标之后,凡是第一个字符是tab的文本一律被视为命令(触发上一行结尾是反斜线)。因此,以tab开头的注释(# …)为shell注释,也会出现在console中。而以空格(无论多少个)开头的注释则为make注释,会被直接忽略。

make可以识别命令末尾的反斜线,已将其视为同一个命令,如:

1
2
3
4
5
6
.INTERMEDIATE: file_list
file_list:
for d in logic ui; \
do \
echo $$d/*.java; \
done > $@

这里补充一点,上一篇中提到过.INTERMEDIATE生成的文件最后会被删除,但是如果运行make file_list,这个规则就不是中间过程,生成的文件也不会被删除。

再举一个cd的例子:

1
2
3
TAGS:
cd src && \
ctags -recurse

命令修饰符

修饰符 解释
@ 不要输出命令
- 忽略命令中的错误
+ 要求make执行命令,就算用户是以–just-print命令行选项来执行make的

错误与中断

使用--keep-going-k选项可以让make编译完所有文件,以尽可能多的输出错误信息。在调试程序时,我们倾向于将复杂命令拆分成独立命令,而不是使用&&,因为这样可读性强,make还能在错误发生时提供更多的信息。

在使用-k选项运行程序时,如果命令出错导致一个文件没有被成功建立,而其时间戳已经被更新,那么随后的动作就会引用错误的数据,这时我们就可以使用.DELETE_ON_ERROR来标记,表示一旦出错就删除该文件。参考第一篇

当make的执行被信号中断时,如果文件被更改,当前工作目标文件就会被删除,这时可以使用.PRECIOUS来保护该文件不被删除。

命令行大小有长度限制(32k~128k,因系统而异)。这里说的长度不是命令本身的长度,而是运行后的长度,比如在一个有几千个文件的文件夹里面执行ls。

递归式make

当使用make来编译一个大型项目时,可以先为每个目录编写一个简单的、各自独立的makefile,然后使用make来分别执行它们。

调试技术

warning函数

warning函数会被扩展成空字符串,所以它可以被放在任何地方输出变量。

命令行选项

–just-print, -n

使得make读进makefile并且输出将要执行的命令而不去执行它们

–print-data-base, -p

运行makefile,显示GNU版权信息以及make所运行的命令,然后输出它的内部数据库。这会打印出大量debug信息,比如当前变量、使用的模式等

–warn-undefined-variables

使得make在未定义的变量被扩展时显示警告信息

–debug选项

使用–debug=option选项,可以获得最详细的一个方法。有五个调试选项可选:(实际使用时可以简写为首字母)

调试选项 解释
basic 最不详细的基本调试功能,时–debug的默认选项
verbose 除了basic,还提供了“哪些文件被分析、哪些必要添加不需要重建”等信息
implicit 除了verbose,还提供了关于“为每个工作目标搜索隐含规则”的额外信息
jobs 输出被make调用的子进程的细节,不会启用basic选项的功能
all 启用前面所有的选项,使用-d时,启用的就是这个功能

此外还有一个修饰符:makefile,即使用--debug=makefile。这样做不会启用调试信息,直到makefile被更新。


系列导航