Makefile的重建与include指令

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.mkbar.mk新,所以不需要执行命令,于是输出“foo.mk”是最新的。

无限循环的Makefile

对于上面的第6点,我们可以在命令中更新bar.mak的时间戳,让foo.mkbar.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.makfoo.mk新得“明显”;否则尽管bar.mkfoo.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中文手册》

时间: 2024-10-16 15:33:28

Makefile的重建与include指令的相关文章

使用-MM生成include指令和依赖生成(make include directive and dependency generation with -MM)

I want a build rule to be triggered by an include directive if the target of the include is out of date or doesn't exist. Currently the makefile looks like this: program_NAME := wget++ program_H_SRCS := $(wildcard *.h) program_CXX_SRCS := $(wildcard

include指令和include动作的区别

include指令和include动作的区别 1.include指令 include可以在JSP页面转换成Servlet之前,将JSP代码插入其中.它的主要优点是功能强大,所包含的代码可以含有总体上影响主页面的JSP构造,比如属性.方法的定义和文档类型的设定.它的缺点是难于维护只要被包含的页面发生更改,就得更改主页面,这是因为主页面不会自动地查看被包含的页面是否发生更改. include指令的语法格式如下: <%@ include file="Relative Url"%>

include指令和动作的区别

include指令:用于包含一个文本或代码的文件.称为文件加载指令,可以将其他的文件插入JSP网页.功能:该指令标签作用是在该标签的位置处,静态插入一个文件. include动作:动态包含一个文件,即将当前JSP页面,被包含的文件各自独立编译为字节码文件.当执行到该动作标签处,才加载执行被包含文件的字节码. 总而言之,他两的区别就是:是两种不同的包含,程序的执行性质是完全不同的,一个是静态包含,一个是动态包含,静态包含不能传递参数,但动态包含可以在两文件之间传递参数.

JSP中include指令和include动作的区别

include指令是编译阶段的指令,即include所包含的文件的内容是编译的时候插入到JSP文件中,JSP引擎在判断JSP页面未被修改,否则视为已被修改.由于被包含的文件是在编译时才插入的,因此如果只修改了include文件内容,而没有对JSP修改,得到的结构将不会改变,所以直接执行已经存在的字节码文件,而没有重新编译.因此对不经常变化的内容,用include指令是合适的,如果需要的内容是经常变化的,则需要动作元素<jsp:include>.下面将详细区分他们之间的不同 1.include指

include 指令与include 动作的区别

include指令称为文件加载指令,可以将其他文件插入jsp网页,被插入的文件必须保证插入后形成的新文件符合jsp页面的语法规则.其功能:该指令标签作用是在该标签的位置处,静态插入一个文件. include动作 起作用是在页面得到请求时动态包含一个文件 功能:当前jsp页面动态包含一个文件,即将当前jsp页面.被包含的文件各自独立编译为字节码文件.当执行到该动态标签处,才加载执行被包含文件的字节码.

include指令和&lt;jsp:include&gt;标准动作

利用JSP的包含机制,可以有效的避免重复,把可重用的部分独立出去,使用include把它们包含到当前文件.JSP有两种包含机制:include指令和<jsp:include>标准动作. 1.include指令 2.<jsp:include>标准动作 3.内部原理 include指令和<jsp:include>标准动作看上去一样,而且通常有相同的效果,但是它们生成的servlet代码并不相同. include指令在转换时发生,它就像把被包含的文件复制到当前文件一样:<

黑马程序员---C基础6【#include指令】【模块化编程】【计算机的进制】【原码、反码、补码】【位运算符】

------Java培训.Android培训.iOS培训..Net培训.期待与您交流! ------- [#include指令] 1.文件包含命令的格式: 1)#include “”双引号是包含用户自己书写定义的文件(可以是头文件,也可以是普通的文件)#include是预处理指令,不是一个语句,不需要加封号 2)#include<>   包含一个系统(编译器自带)的头文件 2.文件包含的实质: 把指定文件内容插入该命令行位置取代该命令行, include不一定非要写在第一行: 3.includ

&lt;12&gt;【掌握】#include指令+【掌握】include文件搜索顺序+

[掌握]#include指令 #include 是一个预处理指令 作用是:把要包含的文件的内容拷贝到当前书写 include的地方 1 #include <stdio.h> // <>引用的说系统文件,“”引用的是自己的文件 2 3 int main(int argc, const char * argv[]) { 4 5 //把当前目录中a.txt文件中的内容替换到当前写include的地方 6 //当前目录:和main.c同一个文件夹下得目录 7 #include "

Apache虚拟主机Include指令用法

#Apahce的Include指令 语法:Include 文件路径|目录路径 Apache中的Include是一个非常有用的指令,这个指令的意思就是将conf和conf.d目录下所有以.conf结尾的配置文件 都引到指定的位置,假如一台服务器上有很多个虚拟主机而且虚拟主机属于不同用户的,我们希望用户可以修改各自的 虚拟主机配置文件,而又不影响其他人的配置.这时我们就可以在一个目录里创建所有用户的虚拟机配置文件,用户修 改也只是修改各自的配置文件,这样就可以各自配置,而又互补影响. #安装http