Makefile中的include命令详解
关于Makefile中的include命令,网上有很多介绍,比较普遍的说法是:Makefile中的include命令与C语言中的include命令类似,命令include file.dep,即把file.dep文件在当前Makefile文件中展开,亦即把file.dep文件的内容包含进当前Makefile文件;如果Makefile中有以file.dep为目标的规则,make会先使用规则对file.dep文件进行更新,然后将更新后的file.dep文件包含进当前Makefile文件。[网上描述]
这种关于include命令功能的描述只是大体正确,但还不够清楚和准确,下面将我认为的对include命令的功能更清楚和准确的描述(以include file.dep为例)表述如下,不妥之处请读者指正。
首先给出几个定义:由Makefile文件中的所有规则组成的集合称为U1;由file.dep文件中的所有规则组成的集合称为U2;集合U1和集合U2的并集称为集合U。
Makefile中的include命令先将文件file.dep包含进当前Makefile文件(第一次包含),这样Makefile文件中就有了file.dep文件的内容;[断点A] 然后在集合U(特别注意,这里是集合U)中检查是否有以file.dep为目标的规则。如果U中没有以file.dep为目标的规则,或者虽然有以file.dep为目标的规则,但根据依赖关系(即便在规则中的命令执行后也)不能使file.dep文件发生更新,则Makefile文件最终包含的就是file.dep文件的当前内容,include命令执行结束;[断点B] 如果集合U中有以file.dep为目标的规则,并且该规则使得file.dep文件发生了更新,则include命令会将更新后的file.dep文件再次包含进当前Makefile文件(再次包含),跳转到断点A往下继续执行。 |
这里需要澄清的有几点:
第一,多数情况下,U中没有规则能使file.dep文件发生更新,这就给我们这样一个假象:Makefile的include命令和C语言的include命令一样,只是简单的把file.dep文件的内容包含进当前Makefile文件,即include命令执行到断点A就执行完毕了,甚至都没有执行到断点B。
第二,很多情况下,file.dep文件中并不包含以file.dep为目标的规则,这就给我们另外一个假象:include file.dep后,检查是否有规则对file.dep文件进行更新时,检查的范围仅仅是集合U1,而不是集合U。
第三,由于多数情况下include命令会在第一次执行到断点B后结束,这就给我们第三个假象:include命令只会把file.dep文件包含一次,不会将其再次包含甚至多次包含。
以上三点就是[网上描述]不清楚,不准确,含糊之处。
为了验证以上表述,我们来设计几个小的Makefile实验。
实验一 首先,在一个文件夹中用touch命令新建如下几个文件;
1 $touch a.h
2 $touch b.h
3 $touch c.h
4 $touch Makefile
修改Makefile文件为以下内容:
1 all:test
2
3 test:a.h
4 test:b.h
5 test:c.h
6 @echo "hello world";
7 touch test;
执行make命令;
由于test文件不存在,所以,命令行输出以下内容:
1 $ make
2 hello world
3 touch test;
4 $ ls
5 a.h b.h c.h Makefile test
从ls命令的结果可以看到,的确产生了test文件。
再次执行make命令,命令行输出以下内容:
1 $ make
2 make: Nothing to be done for `all'.
这是因为当前test文件是最新生成的,比a.h,b.h和c.h都要新,所以根据规则不会再更新test文件。
下面我们分别在更新a.h,b.h和c.h文件后执行make命令,命令行输出以下内容:
1 $ touch a.h
2 $ make
3 hello world
4 touch test;
5
6 $ touch b.h
7 $ make
8 hello world
9 touch test;
10
11 $ touch c.h
12 $ make
13 hello world
14 touch test;
从命令行输出可知,每次都输出了“hello world”并且更新了test文件。
以上实验说明:在同一个Makefile文件中,如果两个或两个以上规则具有相同的目标,则在这些规则中,任一规则中的依赖文件的更新都会导致规则中的命令被执行。
实验二 下面我们对Makefile文件做一些改动,改动后的Makefile文件内容如下;
1 all:test
2
3 test:a.h
4 @echo "this is a.h";
5 test:b.h
6 @echo "this is b.h";
7 test:c.h
8 @echo "this is c.h";
9 touch test;
下面我们分别在更新a.h,b.h和c.h文件后执行make命令,命令行输出以下内容:
1 $ touch a.h(b.h,c.h,由于输出结果一样,这里为节省篇幅写在一行)
2 $ make
3 Makefile:6: warning: overriding commands for target `test'
4 Makefile:4: warning: ignoring old commands for target `test'
5 Makefile:8: warning: overriding commands for target `test'
6 Makefile:6: warning: ignoring old commands for target `test'
7 this is c.h
8 touch test;
从执行结果可以看出,只有最后一条规则的命令得到了执行。
以上实验说明:在同一Makefile文件中,如果有两个或两个以上规则具有相同的目标,则在这些规则中,任一规则中的依赖文件的更新都会且仅会导致最后一个规则中的命令被执行,前面规则的命令被忽略。
实验三 下面我们再对Makefile文件做一些改动,并且新建file.dep文件。把Makefile文件中以test为目标的第一条规则抠出来放在文件file.dep中,并在原位置使用include命令把file.dep文件包含进来,修改后的Makefile文件和file.dep文件的内容分别如下:
<file.dep>
1 test:a.h
2 @echo "this is a.h";
<Makefile>
1 all:test
2
3 include file.dep
4 test:b.h
5 @echo "this is b.h";
6 test:c.h
7 @echo "this is c.h";
8 touch test;
下面我们分别在更新a.h,b.h和c.h文件后执行make命令,命令行输出以下内容:
1 $ touch a.h(b.h,c.h,由于输出结果一样,这里为节省篇幅写在一行)
2 $ make
3 Makefile:5: warning: overriding commands for target `test'
4 file.dep:2: warning: ignoring old commands for target `test'
5 Makefile:7: warning: overriding commands for target `test'
6 Makefile:5: warning: ignoring old commands for target `test'
7 this is c.h
8 touch test;
从命令行输出可知,执行结果与实验二的非常相似,不同的只是第四行的警告信息发生在了file.dep文件中。
以上实验说明:include命令的确把file.dep文件包含进了Makefile文件,并且include命令至少执行到了断点A。
实验四 下面我们继续对Makefile文件和file.dep文件进行修改,并新建d.h文件;
1 $touch d.h
修改后的file.dep文件和Makefile文件的内容分别如下:
<file.dep>
1 file.dep:a.h
<Makefile>
1 all:test
2 include file.dep
3 file.dep:b.h
4 @echo "test:d.h" > file.dep;
5 test:c.h
6 touch test;
我们依次输入以下命令:
1 $touch file.dep
2 $touch test
3 $make
命令行结果如下:
1 make: Nothing to be done for `all'.
分析一下该结果的产生过程:首先include命令先把file.dep 文件包含进Makefile文件,包含进Makefile文件的内容为file.dep:a.h。然后,在集合U中检查是否有能使得file.dep文件发生更新的规则,此时,规则
file.dep:a.h
和规则
file.dep:b.h
@echo "test:d.h" >
file.dep;
都不能使file.dep文件发生更新,include命令在包含一次file.dep文件后执行结束。Makefile接着去执行all目标的规则,由于all依赖于test并且test文件比c.h文件新,所以会出现命令行所示的结果。
下面接着依次输入以下命令:
1 $touch b.h
2 $touch d.h
3 $make
命令行输出:
1 touch test;
然后我们查看file.dep文件的内容,发现其内容已变为:
1 test:d.h
依据命令行的输出结果和file.dep文件的内容,我们同样来分析一下该结果的产生过程:
首先include命令先把file.dep文件包含进Makefile文件,包含进Makefile文件的内容为file.dep:a.h。然后,在集合U中检查是否有能使得file.dep文件发生更新的规则,此时,规则
file.dep:a.h
不能使file.dep文件发生更新,但是规则
file.dep:b.h
@echo "test:d.h" >
file.dep;
却可以使file.dep文件发生更新,所以include命令会将更新后的file.dep文件再次包含进Makefile文件,而更新后的file.dep文件的内容也变为test:d.h。然后继续在U中检查是否有规则能使file.dep文件发生更新,此时U中以file.dep为目标的规则只有
file.dep:b.h
@echo "test:d.h" >
file.dep;
并且此时的file.dep比b.h新,所以该规则中的命令不会被执行且该规则也不能使file.dep文件发生更新,include命令到此执行结束,最终包含在Makefile文件的内容为test:d.h。
接下来就是去执行all目标的规则了,all依赖于test,此时Makefile文件中有两条以test为目标的规则:
test:d.h和
test:c.h
touch test;
此时test比c.h新,而d.h比test新,根据文件的新旧关系以及实验一可知,最后会输出“touch test;”,并且该命令的执行是由依赖关系test:d.h触发的。
以上实验说明:如果在集合U(这里是U1)中存在以file.dep为目标的规则,并且该规则使得file.dep发生了更新,则file.dep文件会被再次包含进Makefile文件。
实验五 下面接着对Makefile文件和file.dep文件进行修改,修改后的file.dep文件和Makefile文件的内容分别如下:
<file.dep>
1 file.dep:a.h
2 @echo "file.dep:b.h" > file.dep;
3 @echo " @echo \"hello world.\"" >> file.dep;
4 touch b.h;
<Makefile>
1 all:test
2
3 include file.dep
4 test:c.h
5 @echo "this is c.h";
然后依次执行以下命令:
1 $touch a.h
2 $touch c.h
3 $make
命令行输出结果如下:
1 touch b.h
2 hello world.
3 this is c.h
打开file.dep文件,其内容如下:
1 file.dep:b.h
2 @echo "hello world."
依据命令行输出结果和file.dep文件的内容这两个方面,我们再来分析一下以上结果的产生过程:
首先,include命令将file.dep文件包含进Makefile文件,然后在集合U中查看是否有规则能使file.dep文件发生更新,而集合U2(U2包含于U)中正好有一条能使file.dep发生更新的规则,
file.dep:a.h
@echo "file.dep:b.h" > file.dep;
@echo " @echo \"hello world.\"" >> file.dep;
touch b.h;
接着这条规则中的三条命令被执行。这样,file.dep文件的内容被重写,b.h文件被更新,输出“touch b.h”。这样完成对file.dep文件的更新后,接着把这个刚更新完的file.dep文件再次包含进Makefile文件,然后跳转到断点A继续往下执行。此时,Makefile文件中包含了file.dep文件的内容:
file.dep:b.h
@echo "hello world."
注意,这也是一个以file.dep为目标的规则,且该规则在集合U中;此时,file.dep没有b.h新,根据依赖关系此规则里的命令会被执行,于是有了输出”hello world.”。但该规则并没有使file.dep文件再次发生更新,所以Makefile文件中最终包含的file.dep文件即此时的file.dep文件,内容为:
file.dep:b.h
@echo "hello world."
接下来执行all目标的规则,输出“this is c.h”。
以上实验说明:如果在集合U(这里是U2)中存在以file.dep为目标的规则,并且该规则使得file.dep发生了更新,则file.dep文件会被再次包含进Makefile文件。
实验四和实验五分别分别演示了在集合U1、U2中存在更新被包含文件的规则情况下,被包含文件两次被include进Makefile文件的详细情形。下面我们再演示一种被包含文件多次被include进Makefile文件的情形。
实验六 我们来编写一个简单的小程序,在一个单独的文件夹中新建以下几个文件,main.c,cola.c,cola.h和Makefile文件,它们的文件内容分别如下:
<main.c>
1 #include "cola.h"
2
3 void main()
4 {
5 cola();
6 }
<cola.h>
1 #ifndef __COLA_H
2 #define __COLA_H
3 void cola();
4 #endif
<cola.c>
1 #include <stdio.h>
2 #include "cola.h"
3
4 void cola()
5 {
6 printf("I am Coca Cola!\n");
7 }
<Makefile>
1 dep_files=dep_dir/main.dep dep_dir/cola.dep
2 all:
3 @echo "hello world."
4
5 -include $(dep_files)
6
7 dep_dir:
8 mkdir $@
9
10 dep_dir/%.dep:dep_dir %.c
11 @echo "Making $@..."
12 @set -e; \
13 rm -rf $@; \
14 gcc -E -MM $(filter %.c,$^) > $@;
该程序非常小,只是简单的输出一句话“I am Coca Cola!”。Makefile是用来控制程序的编译流程的,并且要求在程序修改后也要能适用。很明显,上面的这个不完善的Makefile并不能满足对这个简单程序进行编译的要求,但足以用来说明include命令的功能。
我们先在命令行中输入make命令,看看命令行显示的结果:
1 $ make
2 mkdir dep_dir
3 Making dep_dir/cola.dep...
4 Making dep_dir/main.dep...
5 Making dep_dir/cola.dep...
6 Making dep_dir/main.dep...
7 Making dep_dir/cola.dep...
8 Making dep_dir/main.dep...
9 Making dep_dir/cola.dep...
10 Making dep_dir/main.dep...
11 Making dep_dir/cola.dep...
……
从命令行的输出可知,Makefile先创建了文件夹dep_dir,然后不停的创建文件main.dep和cola.dep,看起来像个死循环。下面我们来分析一下该Makefile的功能以及为什么会出现不停的创建文件main.dep和cola.dep的死循环。
首先,我们编译程序的过程一般是先将.c档和.h档编译为.o档,然后对.o档进行链接生成可执行档。从这样的编译过程可以看出,.o档依赖于.c档和.h档,可执行档又依赖于.o档;这样,若对.c档和.h档进行了修改,编译时就可以通过依赖关系将修改反映到可执行档。这种依赖关系在Makefile中的表现形式就是规则,规则中的目标和跟在后面的先决条件表达的就是一种依赖关系,它控制着Makefile对程序的编译过程,.o档对.c档和.h档的依赖以及可执行档对.o档的依赖都可以用规则来表达。所以,在Makefile中用规则描述好各个文件之间的依赖关系是我们成功进行编译的基础。
对于简单的程序,我们可以很容易的写出表达文件依赖关系的规则,比如该例:
cola.o: cola.c cola.h
main.o: main.c cola.h
以上两条规则就分别表达了cola.o依赖于 cola.c和cola.h;main.o依赖于main.c和 cola.h。
但当程序复杂起来时,比如这样一个程序:main.c 包含了10个头文件:a0.h,a1.h,……,a9.h;而这10个头文件每个又包含了10个头文件:00.h,…,09.h;……;90.h,…,99.h;这样要编译生成mian.o,我们需要手工在Makefile中添加以下规则:
mian.o:main.c a0.h … a9.h 00.h … 09.h …… 90.h … 99.h
后面的依赖文件多达111个,这是一件多么繁琐、艰巨且易错的事情!更糟糕的是,当其中任何一个头文件发生更改时,比如a3.h,34.h和79.h又发生了更改,它们又包含了一些新的头文件并去掉了一些没用的头文件,这时还要对上面的规则进行相应的修改,这几乎是一件手工不能完成的事情!谢天谢地,这件事我们可以用命令自动完成^-^。这个命令就是:
gcc -E -MM main.c;
该命令会产生以下输出:
mian.o:main.c a0.h … a9.h 00.h … 09.h …… 90.h … 99.h
它正是我们需要的规则。如果我们把这条自动生成的规则重定向到一个文件(比如main.dep)中,然后把该文件include到当前Makefile中,我们就不用手工在Makefile中添加了,这样看起来很美好~~
假设我们已经在某处生成了main.dep文件,现在我们可以描绘下Makefile的大体轮廓了:
1 all:main.exe
2 include main.dep
3 main.exe: main.o
4 gcc -o $@ $^
5 main.o:main.c
6 gcc -o $@ -c $^
从上面这个大体的轮廓可以看出,main.dep被包含进了Makefile文件,也就是规则
mian.o:main.c a0.h … a9.h 00.h … 09.h …… 90.h … 99.h
被包含进Makefile文件,结合实验一可知,后面的111个依赖文件中的任何一个发生了修改都会导致最终的main.exe文件被重新生成,而这正是我们需要的。
现在我们还有一个问题要面对:刚才我们假设已经在某处生成了main.dep文件,也就是说在Makefile开始程序编译这个主要流程前,某处需要先把main.dep文件准备好,以供Makefile使用,显然某处的内容应该要包含类似gcc -E -MM main.c > main.dep的命令,那我们将某处放在哪里合适呢?
要回答这个问题,我们先要知道某处具有的一个最重要的特点,那就是它的内容的执行先于程序编译的主流程。我们再来看下实验四和实验五,从这两个实验可知,以被包含文件file.dep为目标的规则总是被先执行,而代表着程序编译主流程的all目标所牵连的规则在其后执行。这样,不难想象,如果我们将某处设计成以main.dep为目标的规则,并在规则的命令部分添加上产生main.dep文件的命令,类似“gcc -E -MM main.c > main.dep”,并把某处放置在Makefile文件中,则当用include命令把main.dep包含进Makefile文件后,某处的内容自然会先执行,然后代表程序编译主流程的all目标所牵连的规则之后再执行。
这个方法很精巧,解决了某处到底放哪里合适的问题,而我们需要做的就是把某处设计成以main.dep为目标的规则并且规则的命令部分能够生成main.dep文件,然后将某处放在Makefile文件中。我们面对的问题现在有了明确的方向,一切看起来都很美好!让我们来憧憬一下我们的Makefile文件的大体轮廓,它应该是酱紫滴:
1 all:main.exe
2 include main.dep
3 main.exe: main.o
4 gcc -o $@ $^
5 main.o:main.c
6 gcc -o $@ -c $^
7 某处
8 main.dep:
9 gcc -E -MM main.c > main.dep
现在回过头来看我们的cola程序,分析一下cola程序的Makefile死循环的原因。为了便于观察,再将Makefile文件内容列出如下:
1 dep_files=dep_dir/main.dep dep_dir/cola.dep
2 all:
3 @echo "hello world."
4
5 -include $(dep_files)
6
7 dep_dir:
8 mkdir $@
9
10 dep_dir/%.dep:dep_dir %.c
11 @echo "Making $@..."
12 @set -e; \
13 rm -rf $@; \
14 gcc -E -MM $(filter %.c,$^) > $@;
从该Makefile可以看出,它的主要功能是为当前文件夹中的main.c和cola.c文件自动生成依赖文件main.dep和cola.dep,并且为main.dep和cola.dep在当前文件夹中建立了单独的文件夹dep_dir,以使当前文件夹中的文件有条理。
当该Makefile开始执行时,include先在当前文件夹中查看dep_dir/main.dep和 dep_dir/cola.dep是否存在,结果是否定的。不过没关系,include命令前的符号“-”标示了当被包含文件不存在时不予理会,继续往下执行。下面在集合U中检查是否有以dep_dir/main.dep和 dep_dir/cola.dep为目标的规则,答案是肯定的,就在某处的规则:
dep_dir/%.dep:dep_dir %.c
@echo "Making $@..."
@set -e; \
rm -rf $@; \
gcc -E -MM $(filter %.c,$^) > $@;
从该规则的依赖关系dep_dir/%.dep:dep_dir %.c可知,dep_dir/main.dep和dep_dir/cola.dep依赖于dep_dir,而dep_dir现在不存在,所以以 dep_dir为目标的规则
dep_dir:
mkdir $@
被执行,结果就是我们命令行的第一行输出:
mkdir dep_dir
产生了文件夹dep_dir。
接着,根据依赖关系,会执行该规则中的命令,在命令行产生输出:
Making dep_dir/cola.dep...
Making dep_dir/main.dep...
并在文件夹dep_dir中产生依赖文件cola.dep和main.dep。现在有了新产生的依赖文件cola.dep和main.dep,根据实验四和试验五,include命令会把这两个新产生的文件再次包含进Makefile文件,然后在集合U中检查是否有以dep_dir/main.dep和dep_dir/cola.dep为目标的规则,答案依旧是肯定的:
dep_dir/%.dep:dep_dir %.c
@echo "Making $@..."
@set -e; \
rm -rf $@; \
gcc -E -MM $(filter %.c,$^) > $@;
乍一看,我们没有手工做过对dep_dir,mian.c和cola.c的更新,理论上就不会导致cola.dep和main.dep的更新,include命令应该到此执行结束,可命令行不停的输出:
Making dep_dir/cola.dep...
Making dep_dir/main.dep...
Making dep_dir/cola.dep...
Making dep_dir/main.dep...
却和我们的认识有差别,它分明在向我们强势宣告dep_dir,mian.c或cola.c有更新,而我们自己也真的没有手工对这几个文件进行过任何更新,好无奈~~
为了找出问题究竟在哪,我们来做个小实验。新建文件夹test,在test中新建文件夹test_dir,然后在test_dir中依次新建文件test_file1和test_file2,注意在新建完test_file1后使用stat命令查看一下test_dir和test_file1的时间戳,同样在新建完然后test_file2后也使用stat命令查看一下test_dir和test_file2的时间戳,如下所示:
1 test $ touch test_dir/test_file1
2 test $ stat test_dir/
3 File: ‘test_dir/’
4 Size: 4096 Blocks: 8 IO Block: 4096 directory
5 Device: 80bh/2059d Inode: 3675101 Links: 2
6 Access: (0755/drwxr-xr-x) Uid: ( 1000/ ) Gid: ( 1000/ )
7 Access: 2016-03-25 23:17:22.488157324 +0800
8 Modify: 2016-03-25 23:27:18.084130770 +0800
9 Change: 2016-03-25 23:27:18.084130770 +0800
10 Birth: -
11 test $ stat test_dir/test_file1
12 File: ‘test_dir/test_file1’
13 Size: 0 Blocks: 0 IO Block: 4096 regular empty file
14 Device: 80bh/2059d Inode: 3675102 Links: 1
15 Access: (0644/-rw-r--r--) Uid: ( 1000/ ) Gid: ( 1000/ )
16 Access: 2016-03-25 23:27:18.084130770 +0800
17 Modify: 2016-03-25 23:27:18.084130770 +0800
18 Change: 2016-03-25 23:27:18.084130770 +0800
19 Birth: -
20 test $ touch test_dir/test_file2
21 test $ stat test_dir/
22 File: ‘test_dir/’
23 Size: 4096 Blocks: 8 IO Block: 4096 directory
24 Device: 80bh/2059d Inode: 3675101 Links: 2
25 Access: (0755/drwxr-xr-x) Uid: ( 1000/ ) Gid: ( 1000/ )
26 Access: 2016-03-25 23:27:35.916129975 +0800
27 Modify: 2016-03-25 23:28:07.332128575 +0800
28 Change: 2016-03-25 23:28:07.332128575 +0800
29 Birth: -
30 test $ stat test_dir/test_file2
31 File: ‘test_dir/test_file2’
32 Size: 0 Blocks: 0 IO Block: 4096 regular empty file
33 Device: 80bh/2059d Inode: 3675112 Links: 1
34 Access: (0644/-rw-r--r--) Uid: ( 1000/ ) Gid: ( 1000/ )
35 Access: 2016-03-25 23:28:07.332128575 +0800
36 Modify: 2016-03-25 23:28:07.332128575 +0800
37 Change: 2016-03-25 23:28:07.332128575 +0800
38 Birth: -
上面的输出结果看起来比较繁杂,我们抽出对我们有用的文件更新时间来做一下的对比:
创建test_file1后test_dir和test_file1 的更新时间为
test_dir | 2016-03-25 23:27:18.084130770 |
test_file1 | 2016-03-25 23:27:18.084130770 |
创建test_file2后test_dir和test_file2 的更新时间为
test_dir | 2016-03-25 23:28:07.332128575 |
test_file2 | 2016-03-25 23:28:07.332128575 |
可见,在test_dir文件夹中每次创建(删除)一个新的文件后,系统都会自动修改test_dir的更新时间。这样在创建test_file1后,虽然test_dir和test_file1的更新时间相同,但在创建test_file2后,test_dir的时间戳已经比test_file1文件的时间戳要新了。
现在让我们再来看看这个让我们无奈的死循环。从命令行的输出情况可以看出,Makefile在dep_dir文件夹中先创建依赖文件cola.dep,然后再创建依赖文件main.dep。但此时依赖文件main.dep在文件夹dep_dir中的创建,导致了dep_dir的时间戳发生了更新,dep_dir要比cola.dep新。规则中的命令被执行完后,cola.dep,main.dep和dep_dir都发生了更新。
根据实验四和实验五,include会将cola.dep和main.dep再次包含进Makefile文件,然后在集合U中检查是否有规则能使得cola.dep和main.dep产生更新,过程如下:
对于cola.dep,在以下规则中,
dep_dir/%.dep:dep_dir %.c
@echo "Making $@..."
@set -e; \
rm -rf $@; \
gcc -E -MM $(filter %.c,$^) > $@;
由于此时的dep_dir要比cola.dep新,所以规则中的命令被执行,规则中的删除和新建操作导致dep_dir和cola.dep都得到了更新,这样dep_dir又比main.dep新了。对于main.dep,在上面规则中,它依赖于dep_dir和main.c,由于上一步的操作使得dep_dir比main.dep新,所以规则中的命令也会被执行,规则中的删除和新建操作导致dep_dir和main.dep都得到了更新,这样dep_dir又比cola.dep新了。
这样产生的结果就是 cola.dep,main.dep和dep_dir都发生了更新,且dep_dir要比cola.dep新。include命令会将更新后的cola.dep和main.dep重新包含进Makefile文件中,然后在集合U中检查是否有规则能使得cola.dep和main.dep产生更新。——这一幕重新上演!死循环了!
以上实验和分析说明:include命令在一定条件下可以将被包含文件(cola.dep和main.dep)多次包含进Makefile文件。
现在我们已经知道了产生死循环的原因,即:cola.dep和main.dep在规则中所依赖的dep_dir总是会比这两个文件中的一个要新。那么我们如何避免这个死循环呢?方法很简单,我们可以添加一个判断条件,只有在dep_dir不存在时,才让cola.dep和main.dep依赖于dep_dir,以创建该文件夹;当dep_dir已经存在时,就不让cola.dep和main.dep依赖于dep_dir了,这样就避免了dep_dir比cola.dep或main.dep新的时候,规则中的命令会不停地循环执行情况的发生。修改方法如下所示:
<Makefile>
1 dep_files=dep_dir/main.dep dep_dir/cola.dep
2
3 ifeq ("$(wildcard dep_dir)", "")
4 DEP_DIR_DEPS := dep_dir
5 endif
6
7 all:
8 @echo "hello world."
9
10 -include $(dep_files)
11
12 dep_dir:
13 mkdir $@
14
15 dep_dir/%.dep:$(DEP_DIR_DEPS) %.c
16 @echo "Making $@..."
17 @set -e; \
18 rm -rf $@; \
19 gcc -E -MM $(filter %.c,$^) > $@;
现在我们来执行一下Make命令,命令行输出如下:
1 $ make
2 mkdir dep_dir
3 Making dep_dir/cola.dep...
4 Making dep_dir/main.dep...
5 hello world.
这个输出结果正是我们所预期的,一切看起来都很美好~~
总结:通过以上六个实验,我们一步步详细分析了Makefile中的include命令的功能,看起来和[网上描述]还是有很大的差别。该文章前后修改了多次,共用15页A4纸来描述一个include命令的功能,只是想把其刻画的更准确,也使读者理解起来更易懂。对于文中仍有的不妥之处,还请读者指出。也希望各位读者在网上发文时尽量将内容表达清楚(我也是实在搞不清网上含糊的描述才忍不了写出此文),以便别的读者理解学习。