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命令的功能,只是想把其刻畫的更准確,也使讀者理解起來更易懂。對於文中仍有的不妥之處,還請讀者指出。也希望各位讀者在網上發文時盡量將內容表達清楚(我也是實在搞不清網上含糊的描述才忍不了寫出此文),以便別的讀者理解學習。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM