Makefile的重建与include指令
include指令
当make
看到include
指令时,会事先对通配符以及变量引用进行扩展,然后试着读引入文件(include file).
如果这个文件存在,则整个过程会继续下去;如果不存在,则make
会汇报此问题(如果你用的不是-include
)并且读取其余的Makefile
.
当所有的读取动作完成后,make
会从规则数据库中寻找任何可以用来更新引入文件的规则,如果找到了一个相符的规则,make
就会按照正常的步骤来更新目标。
如果任何一个引入文件被规则更新,那么make
会接着清除它的内部数据库并且重新读取整个Makefile
.
在完成了读取、更新、重新读取的过程后,如果仍有文件不存在而导致include
指令执行失败,那么make
就会报告错误并终止执行。
下面的一个例子可以帮助我们理解这个过程。这个例子修改自《GNU Make 项目管理》。
假设我们有一个Makefile
,里面的内容如下
1 $(warning Reading Makefile)
2
3 include foo.mk
4 $(warning Finished include)
5
6 foo.mk :bar.mk
7 m4 --define=FILENAME=[email protected] bar.mk>[email protected]
另外我们还有一个bar.mk
文件,它的内容很简单,只有如下一行
$(warning Reading FILENAME)
要理解这个Makefile
,先解释几个知识点。
warning函数
函数的格式是
$(warning text)
这里的text
只是举例,可以换成任何你想要显示的信息。
warning函数的输出中包含了当前Makefile的文件名,当前行的行号,以及要显示的信息内容。warning函数扩展之后会变成空字符串,所以它几乎可以用在任何地方。
关于m4
What is m4?
M4 can be called a “template language”, a “macro language” or a “preprocessor language”. The name “m4” also refers to the program which processes texts in this language: this “preprocessor” or “macro processor” takes as input an m4 template and sends this to the output, after acting on any embedded directives, called macros.
At its most basic, it can be used for simple embedded text replacement. If m4 receives the input
define(AUTHOR, William Shakespeare)
A Midsummer Night‘s Dream
by AUTHOR
then it outputs
A Midsummer Night‘s Dream
by William Shakespeare
简而言之,m4
将输入拷贝到输出,同时将宏展开。
下载地址:ftp://ftp.gnu.org/gnu/m4/
上面的第7行,作用就是将bar.mk
文件的内容拷贝到foo.mk
文件中,同时替换FILENAME
为[email protected]
,也就是foo.mk
。所以说
m4 --define=FILENAME=[email protected] bar.mk>[email protected]
这行命令生成了foo.mk
文件,其内容就是:
$(warning Reading foo.mk)
掌握了上面两个知识点后,我们继续看Makefile
文件。
执行make
命令后。输出如下
Makefile:1: Reading Makefile
Makefile:3: foo.mk: 没有那个文件或目录
Makefile:4: Finished include
m4 --define=FILENAME=foo.mk bar.mk>foo.mk
Makefile:1: Reading Makefile
foo.mk:1: Reading foo.mk
Makefile:4: Finished include
make: “foo.mk”是最新的。
根据输出结果,我们可以窥见make的处理流程:
1. 读Makefile
文件,从头开始处理;
2. 执行include foo.mk
,但是没有找到这个文件,于是输出foo.mk: 没有那个文件或目录
;
3. 继续读取文件,直到读取整个文件结束。然后,make
会从规则数据库中寻找任何可以用来更新引入文件的规则,找到了
foo.mk :bar.mk
m4 --define=FILENAME=[email protected] bar.mk>[email protected]
于是根据这个规则,生成了foo.mk
文件;
4. 因为foo.mk
文件被更新了,所以make
会清除它的内部数据库并且重新读取整个Makefile
,于是再次输出Reading Makefile
;
5. 当遇到include foo.mk
的时候,找到了foo.mk
这个文件,于是读入它,所以输出Reading foo.mk
;
6. 继续读完剩下的部分;在读取完成后也会去试图更新所有的已经读取的makefile文件,包括foo.mk
,但是foo.mk
不会再次被重建,因为它比bar.mak
新;
7. 解析已经读取的makefile文件并执行必要的动作。根据依赖树,因为foo.mk
比bar.mk
新,所以不需要执行命令,于是输出“foo.mk”是最新的。
无限循环的Makefile
对于上面的第6点,我们可以在命令中更新bar.mak
的时间戳,让foo.mk
比bar.mak
旧,于是foo.mk
会再次被重建,也就是说foo.mk
被更新了(比之前的它要新),这样就会再回到第4步,构成一个无限循环。
如何达到这个效果呢?Makefile的内容改成下面的(第8~9行是新增加的):
1 $(warning Reading Makefile)
2
3 include foo.mk
4 $(warning Finished include)
5
6 foo.mk :bar.mk
7 m4 --define=FILENAME=[email protected] bar.mk>[email protected]
8 sleep 1
9 touch bar.mk
第8行:表示延时1秒
第9行:更新bar.mak
之所以要延时,是因为要让bar.mak
比foo.mk
新得“明显”;否则尽管bar.mk
比foo.mk
新,但是只新那么一丁点(小于系统时间的最小分辨度),于是它们在时间戳上是一样的,这样就达不到我们的目的。
此时我们执行make
命令(执行前请删除foo.mk
文件),如愿以偿,看到一个死循环,输出结果如下图:
书中还提到:
现在是让你知道“make也可以把Makefile本身作为一个可能的工作目标”这件事的好时机。当make读进整个makefile之后,make将会试着寻找可以用来重新当前所执行的makefile的规则。如果找到了,make将会处理此规则,然后检查makefile是否已经更新。如果已经更新,make将清除它的内部状态,重新读进此makefile,重新完成整个分析动作。
作者还举了一个无限循环的例子,其内容如下:
.PHONY: dummy
makefile: dummy
touch [email protected]
执行结果如下图:
其实并没有无限循环。
我把这个例子修改如下:
1 $(warning read me...)
2 .PHONY:dummy
3 Makefile:dummy
4 sleep 1
5 touch [email protected]
执行结果如下图:
对执行过程的解释:
1. make读入Makefile,解析目标Makefile
与依赖dummy
的时间戳,因为dummy
是伪目标,伪目标总是尚未更新,所以Makefile
也需要更新,所以就执行touch命令,这样会更新Makefile
的时间戳,于是make会重进读入Makefile;
2. make读入Makefile,解析目标Makefile
与依赖dummy
的时间戳……(循环到第1步)
实验结果说明第4行是非常有必要的,如果不延时的话,因为上次touch更新了Makefile,所以再次读入,再执行touch,但是这两次touch之间的时间间隔太短了,短于系统时间的最小分辨度,这样就会导致第二次touch后,make对比Makefile的时间戳,发现时间戳一样,认为它没有被更新,所以不会再次读入它。这样就无法看到无限循环的效果。
总结
以下文字摘自《GNU make中文手册》的3.7节,作为本文的总结。
make在读入所有makefile文件之后,首先将所读取的每个makefile作为一个目标,寻找更新它们的规则。如果存在一个更新某一个 makefile文件明确规则或者隐含规则,就去更新对应的makefile文件。完成对所有的makefile文件的更新之后,如果之前所读取的任何一个makefile文件被更新,那么make就清除本次执行的状态重新读取一遍所有的makefile文件(此过程中,同样在读取完成以后也会去试图更新所有的已经读取的makefile文件,但是一般这些文件不会再次被重建,因为它们在时间戳上已经是最新的)。读取完成以后再开始解析已经读取的makefile文件并开始执行必要的动作。
参考资料
[0] 《GNU Make 项目管理》
[1] Notes on the M4 Macro Language
[2] 徐海兵:《GNU make中文手册》