Makefile中的include命令详解

2023-11-20

转载地址 点击打开链接

关于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命令的功能,只是想把其刻画的更准确,也使读者理解起来更易懂。对于文中仍有的不妥之处,还请读者指出。也希望各位读者在网上发文时尽量将内容表达清楚(我也是实在搞不清网上含糊的描述才忍不了写出此文),以便别的读者理解学习。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Makefile中的include命令详解 的相关文章

  • Makefiles:从一个目录获取.cpp,并将编译后的.o放在另一个目录中

    我正在开发适用于移动设备 Windows Mobile 6 和 Android 的跨平台 2D 引擎 我的 Windows 版本已基本准备就绪 但我仍需要确保 Android 上也提供相同的功能 我想要的是一个Makefile在项目的根目录
  • 使用 gnu make 正确构建 git 子模块

    我目前尝试编写一个 Makefile 来正确构建一个包含 git 子模块的项目 该子模块有自己的一组 makefile 并一次生成多个目标 包括一些库 该 Makefile 应具有以下属性 即使使用并行构建 也不要重建两次子模块 当子模块代
  • Readelf 报告程序是共享库而不是可执行文件

    使用独立的 Android NDK r10e 工具链 使用 toolchain x86 clang3 6 开关构建 出现这种奇怪的行为 交叉编译的环境变量已设置在运行makefile之前 SYSROOT指向Android工具链位置 CXX等
  • 在 Unix 中,我可以在目录中运行“make”而无需先 cd 到该目录吗?

    在 Unix 中 我可以运行make在没有的目录中cd首先进入该目录 make C path to dir
  • /usr/sbin/install 到底有什么作用?

    我正在尝试安装discount https github com Orc discount on my VPS http no de它基于Solaris 设置一些环境变量后编译效果很好 但是安装失败 https gist github co
  • 使用 GNU make “从源代码树中”构建 C 程序

    我想使用 GNU make 工具为我的微控制器构建一个 C 项目 我想以一种干净的方式来做这件事 这样我的源代码在构建后就不会被目标文件和其他东西弄乱 想象一下我有一个名为 myProject 的项目文件夹 其中有两个文件夹 myProje
  • bash 函数保留制表符补全

    我把函数 make color make 1 ccze A in bashrc获得彩色的 make 输出 他的作品很好 但是make用于选择目标的制表符补全功能丢失 有什么方法可以保留函数中命令的制表符完成 或者我可以做其他事情来实现制表符
  • 用于发布和调试目标的 Makefile

    我正在尝试构建一个 Makefile 它可以通过指定目标而不是变量 例如make debug 1 不太好 我这里有一个精简的简化示例 它模拟了我想要实现的目标 ifdef debug BINARY my binary debug MODUL
  • 什么是“制定目标”?

    为什么我需要制作一个make target在能够构建我的源代码之前 更具体地说 什么是制定目标 http publib boulder ibm com infocenter rsdvhelp v6r0m1 index jsp topic o
  • 将环境变量从 Makefile 导出到用户态环境

    我正在研究如何从 Makefile 环境变量导出以在用户环境中公开 因此应该可以从用户 shell 访问从 Makefile 导出这些变量 我努力了make s export https www gnu org software make
  • 具有两个同名目标的 Makefile

    我有一个包含包含语句的 makefile 我无法控制包含的 makefile 的内容 不过 我希望能够在 某些 不是全部 目标之前添加一些预处理步骤 考虑以下示例 install echo install target include ot
  • Makefile 和 .Mak 文件 + CodeBlocks 和 VStudio

    我对整个 makefile 概念有点陌生 所以我对此有一些疑问 我正在 Linux 中使用 CodeBlocks 创建一个项目 我使用一个名为 cbp2mak 的工具从 CodeBlocks 项目创建一个 make 文件 如果有人知道更好的
  • 如果未设置,则从控制台读取 Makefile 变量

    我正在更新一个从外部源访问某些资源的 Makefile 即存在以下形式的规则 External cvs up 对于不受限制的资源 它可以按预期工作 现在 出现了功能漂移 外部资源需要更复杂的登录 因此规则已更改为与此没有太大不同的内容 Ex
  • 在 Ubuntu 16.04 上编译 PCL 1.7,CMake 生成的 Makefile 中出现错误

    我正在尝试让 PCL 1 7 点云库 而不是其他 pcl 在 Ubuntu 16 04 上运行 我最终希望用于 C 的东西 但现在我只是想让这些例子工作 我使用的是 Ubuntu GNU 5 3 1 附带的默认编译器和 Cmake 版本 3
  • 致命错误:向量:没有这样的文件或目录

    我有一个 Android 项目 其中包含大量 C 本机代码 但是 我无法构建我的库 因为它无法找到 vector h 头文件 可能是什么问题 我在几乎所有页面中包含的示例 include
  • makefile 目标依赖项取决于目标名称

    我有以下规则 SPECIAL file1 file2 o cpp a h CC c CFLAGS lt o 我希望如果 is in SPECIAL then b h已添加到依赖项列表中 有没有办法做到这一点 而不重复规则 您可以单独分配其他
  • 从哪里获取 iostream.h

    我正在尝试在 Linux 中做一些事情 但它抱怨找不到 iostream h 我需要安装什么才能获取此文件 这个标准头的正确名称是iostream没有扩展名 如果您的编译器仍然找不到它 请尝试以下操作 find usr include na
  • Makefiles - ar: *.a: 没有这样的文件或目录....但是有...它就在那里

    尝试编译朋友的代码 但他没有包含 Makefile 我构建了自己的代码 并对我遇到的问题感到困惑 我认为最好将 Makefile 的完整内容发布在下面 我尽量保持简短 CFLAGS Wall pedantic LFLAGS CC gcc R
  • 是否可以使用现有的 Makefile 在 Code::Blocks 中构建项目?

    编辑 我发现项目属性中有一个选项可以设置自定义生成文件 现在项目构建良好 现在 我偶然发现了如何在单击 运行 时指定要运行的目标可执行文件 代码 块 http www codeblocks org is an IDE https en wi
  • 错误:-march= 开关的值错误

    我写了一个Makefile 但无法让它工作 我有一个选项应该选择编译到哪个处理器 然而 当我跑步时make从命令行它说 tandex tandex P 6860FX emulators nintendo sdks 3DS SDK HomeB

随机推荐

  • 【Arthas】Arthas 类查找和反编译原理

    1 概述 转载 Arthas 类查找和反编译原理 2 开篇 Arthas支持通过类相关的操作命令 包括sc sm jad等 sc Search Class 命令搜索出所有已经加载到 JVM 中的 Class 信息 sm Search Met
  • Windows实例如何通过本地安全策略限制远程登录的IP地址

    Windows实例如何通过本地安全策略限制远程登录的IP地址 阿里云 禁止所有的IP地址连接服务器的RDP端口 远程连接登录服务器 单击 开始 选择 运行 输入gpedit msc 单击 确定 打开本地组策略编辑器 在左侧依次找到 计算机配
  • 新版jdk的keytool没有md5,怎么解决?

    第一种方案 降级你的java sdk版本 jre1 8 0 201 jdk8以下的版本是可以的 参考 Android平台签名证书 keystore 生成指南 DCloud问答 第二种方法参考 http www 360doc com cont
  • Qt中两个类通过信号槽进行传输数据遇到的问题

    1 场景需求 在QGraphicsItem类中发生重绘后 希望将改动的信息在主窗口MainWindow中的textedit控件中显示出来 例如在选中的QGraphicsItem生成的对象item后 在被鼠标选中时 在textedit中显示这
  • openwrt 进入failsafe模式

    OpenWrt固件通过reset键恢复出厂设置方法 此方式适用于 忘记 OpenWrt 登入密码 配错路由器IP地址导致无法访问路由器 配错firewall或路由表导致无法登入路由器甚至无法ping路由器 操作步骤 1 把电脑的IP地址设为
  • 利用jmeter模拟手机接口测试

    利用jmeter模拟手机接口测试 由于批量上传图片不方便 可以到博主网站查阅原版博客 https blog zhenglin work jmeter use jmeter test app html 本文示例是从网上找到的月光茶人APP程序
  • 微信小程序支付完成后如何跳转指定页面

    很多人说使用reLaunch方式来跳转 此方法在 IOS 正常跳转 而在 Android 报错 relaunch fail can not invoka lelaunch in background 是因为支付成功后就会立马试图执行rela
  • Ubuntu下安装Android Studio

    Ubuntu下安装Android Studio 作者 gaolei xj更新于 05月18日访问 1568 评论 11 大晚上的睡不着 最近老失眠 索性玩玩Android Studio 我电脑的环境是Ubuntu 12 04 64位 And
  • C++包管理器vcpkg安装Opencv

    转载自 https www cnblogs com ttweixiao IT program p 12419876 html 电脑配置 window10 电脑型号 HUAWEI MateBook D 64位操作系统 基于x64的处理器 Mi
  • 磁盘调度算法笔记和练习题

    磁盘调度算法 先来先服务FCFS 最短寻道时间优先SSTF 扫描调度SCAN 练习题 先来先服务FCFS 最短寻道时间优先SSTF 扫描调度SCAN 它是一次只响应一个方向上的请求 这个方向上的请求都响应完了 再掉头处理另一个方向上的 有点
  • Centos7 安装Redis详细教程

    本文主要介绍如果在Centos7下安装Redis 1 安装依赖 redis是由C语言开发 因此安装之前必须要确保服务器已经安装了gcc 可以通过如下命令查看机器是否安装 gcc v 如果没有安装则通过以下命令安装 yum install y
  • 【C语言】 文本文件读取中文汉字出现乱码问题的解决方法

    include
  • 手把手教你如何写一个三子棋/N子棋的小游戏

    这里写目录标题 第一步 游戏进入界面 第二步 初始化棋盘 第三步 打印棋盘 第四步 玩家和电脑下棋 第五步 判断输赢 三子棋或者N子棋怎么写 让我们先来玩一把 再来看看怎么写 程序运行界面 1为玩游戏 2为清屏 0为退出游戏 我们选1 然后
  • 前端多个参数传参js

    function getparm 返回当前 URL 的查询部分 问号 之后的部分 var urlParameters location search 声明并初始化接收请求参数的对象 var requestParameters new Obj
  • PPTP中的PAC 和PNS

    http blog csdn net galdys article details 6682298 网络服务器 PNS 访问集线器 PAC PAC 可编程自动化控制器 的概念是由ARC咨询集团的高级研究员Craig Resnick提出的 在
  • rostcm6情感分析案例分析_基于情感词典的情感分析方法

    上节课我们介绍了基于SnowNLP快速进行评论数据情感分析的方法 本节课老shi将介绍基于情感词典的分析方法 基于情感词典的分析方法是情感挖掘分析方法中的一种 其普遍做法是 首先对文本进行情感词匹配 然后汇总情感词进行评分 最后得到文本的情
  • LeetCode -- 1833. 雪糕的最大数量

    使用的算法 计数排序 贪心算法 计数排序 1 基于比较的排序算法 2 在对一定范围内的整数排序时 它的复杂度为 n k 其中k是整数的范围 快于任何比较排序算法 当O k gt O nlog n 的时候其效率反而不如基于比较的排序 基于比较
  • Kali Linux进阶篇:Nmap扫描网络空间存活主机技巧

    课前声明 1 本分享仅做学习交流 请自觉遵守法律法规 2 搜索 Kali与编程 学习更多网络攻防干货 一 背景介绍 nmap是一个网络连接端扫描软件 用来扫描网上电脑开放的网络连接端 确定哪些服务运行在哪些连接端 并且推断计算机运行哪个操作
  • Java对象的快速复制的几种方式

    浅拷贝 深度复制 BeanUtils copyProperties 对象的克隆是指创建一个新的对象 且新的对象的状态与原始对象的状态相同 当对克隆的新对象进行修改时 不会影响原始对象的状态 注释 clone 是object类的protect
  • Makefile中的include命令详解

    转载地址 点击打开链接 关于Makefile中的include命令 网上有很多介绍 比较普遍的说法是 Makefile中的include命令与C语言中的include命令类似 命令include file dep 即把file dep文件在