歡迎您光臨本站 註冊首頁

linux編程初步之跟我學makefile中的自動依賴

←手機掃碼閱讀     火星人 @ 2014-03-26 , reply:0

最近學習了一下makefile,對自動依賴感覺不是很懂,通過閱讀info make和網上的一些文章,然後做了一下測試,感覺有了一些認識,所以把學習的一些過程寫出來,希望對像我一樣的初學者有所幫助。其中可能有很多錯誤,還請各位大俠指正。
yeahnix,04/5/26

1.依賴及自動依賴
makefile的執行是以依賴關係為基礎的,即如果要生成某個目標,那麼該目標所依賴的文件必須存在,或者根據一些規則可以構造出來,然後再根據構造該目標的規則生成目標。makefile中的規則可以分為:顯式規則、靜態規則、模式規則和隱含規則(具體含義參見info make)。
在Makefile中,我們的依賴關係可能會需要包含一系列的頭文件,比如,如果我們的main.c中有一句「#include "defs.h"」,那麼我們的依賴關係應該是:
main.o : main.c defs.h
但是,如果是一個比較大型的工程,你必需清楚哪些C文件包含了哪些頭文件,並且,你在加入或刪除頭文件時,也需要小心地修改Makefile,這是一個很沒有維護性的工作。為了避免這種繁重而又容易出錯的事情,我們可以使用C/C++編譯器的一個功能。大多數的C/C++編譯器都支持一個「-M」的選項,即自動找尋源文件中包含的頭文件,並生成一個依賴關係。例如,如果我們執行下面的命令:
cc -M main.c
其輸出是:
main.o : main.c defs.h
於是由編譯器自動生成的依賴關係,這樣一來,你就不必再手動書寫若干文件的依賴關係,而由編譯器自動生成了。需要提醒一句的是,如果你使用GNU的C/C++編譯器,你得用「-MM」參數,不然,「-M」參數會把一些標準庫的頭文件也包含進來。
那麼如何讓編譯器生成的這些依賴關係自動包含在makefile中呢?通過查閱網上的一些文章和info make,我看到了三種方法,在方便理解的情況下姑且稱之為info make的sed方法,depend方法和編譯器指令-MD方法。

2.info make中的自動依賴及遇到的問題
info make中提到了sed方法:
為每一個「name.c」的文件都生成一個「name.d」的Makefile文件,[.d]文件中就存放對應[.c]文件的依賴關係。
於是,我們可以寫出[.c]文件和[.d]文件的依賴關係,並讓make自動更新或自成[.d]文件,並把其包含在我們的主Makefile中,這樣,我們就可以自動化地生成每個文件的依賴關係了。
下面這個模式規則產生[.d]文件:
%.d: %.c
set -e; $(CC) -M $(CPPFLAGS) $< \
| sed 's/\($*\)\.o[ :]*/\1.o $@ : /g' > $@; \
[ -s $@ ] || rm -f $@
這個規則的意思是,所有的[.d]文件依賴於[.c]文件,set -e 指定shell遇到錯誤即退出。sed命令做了一個替換,把依賴關係:
main.o : main.c defs.h //1
轉成: //1
main.o main.d : main.c defs.h //1
於是,[.d]文件也會自動更新了,並會自動生成了。[ -s $@ ] || rm -f $@的意思是刪除產生的空.d文件,從而減少不必要的處理過程。
然後使用Makefile的「include」命令,
sources = foo.c bar.c
include $(sources:.c=.d)
上述語句中的「$(sources:.c=.d)」中的「.c=.d」的意思是做一個替換,把變數$(sources)所有[.c]的字串都替換成[.d]。這樣當include發現沒有.d文件時候就會自動生成.d文件,同時根據.d文件的生成規則,也就生成了.o文件對應.c和.h文件的依賴關係。

開始時對info make中描述的//1部分的作用沒有能很好的理解,.d文件的自動更新也理解不深,於是決定測試一下。編寫了一個簡單的程序,如下:
[root@yeahnix test_makefile]#pwd
/root/Projects/test_makefile
[root@yeahnix test_makefile]# ls -R
.:
aa.c bb.c bb.h dir Makefile
./dir:
echomsg.c echomsg.h
其中aa.c調用bb.c,bb.c調用echomsg.c。
最開始時,echomsg.c和echomsg.h並沒有在dir目錄下,於是利用上述規則構造makefile,發現很正常,程序夠造了出來,.d文件也出來了(好像info make中說.d文件應該是作為中間文件,可以自動刪除的,先不管他,反正程序出來了),但是make開始會報錯,aa.d沒有找到......,後來重新讀info make,發現可以用-include替代,因為剛開始沒有找到是正常的,替換后,沒有找到就不會報錯了。
於是想看看是不是包含子目錄也正確呢?將echomsg.c和echomsg.h放在dir目錄下,發現sed命令執行不正確,怎麼會執行不正確呢?難道info make也會講錯,想不通。是不是-include有問題。於是將include前加了一個TAB符號,一切正常,程序出來了,連.d文件也沒有了,以為真的自動刪除了。心裡很高興,原來-include應該這麼用就對了嘛。但是當用make -d察看調試信息時,發現根本就沒有生成.d文件的過程,這說明這個模式規則根本就沒有起作用。
後來終於發現,include命令前根本就不該加TAB,否則include指令被認為是shell命令,根本就沒有起作用!!!看來還是應該多讀手冊啊。重新讓include起作用,當然還是sed命令出錯,然後才想起由於子目錄中包括「/」,而sed的s命令以/為分隔符,結果當然是sed命令搞不清楚該怎麼辦了。想到vim中可以以+替換/作為分隔符,於是馬上試,在shell中執行:
echo dir/echomsg.o : dir/echomsg.c | sed 's+\($*)\.o[ :]*+\1.o \1.d : dir/echomsg.c+g'
結果顯示正確,於是放到makefile中,make,發現.d文件生成了,但是其中並不包含main.o main.d : main.c這樣的內容,而只是包含main.o : main.c這樣的內容,好像sed命令根本沒有執行過,而只是由編譯器產生依賴關係后直接放在.d文件中的,但是實際上sed命令又是執行過的(通過make -d察看)。這個問題實在想不通,到現在也想不通。於是到處查資料,決定試試另外兩種方法。

3.depend文件和-MD方法
所謂depend方法即用$(CC) -MM $(CPPFLAGS) $^ > depend將依賴關係放到depend文件中,然後再include depend就行了。發現這個方法確實行,通過make -d察看調試信息,發現當更新了.c文件時,depend文件自動重新生成,但是當更新.h時,depend並不自動重新生成.不知道這個會不會影響正確性,同時,在make之前,還得make depend,好像不是很好。而且,在自動重新生成depend時,必須掃描全部的源文件。info make中說這種方法過時了.
-MD方法則是利用編譯器參數-MD代替-M,從而由編譯器自動生成.d文件,可以用下面的模式規則來定義。
%.d:%.c
$(CC) $(CFLAGS) -MD -E $<
其中-E指示編譯器僅僅處理預處理階段,而不繼續進行編譯和鏈接,否則,會報鏈接錯誤。這種方法生成的.d文件中也不包括main.o main.d : main.c這樣的內容,而僅僅包含main.o : main.c這樣的內容。所以,當更新了.c文件時,.d文件自動重新生成,但是當更新.h時,.d並不自動重新生成。不過,由於每個.c文件都對應了一個.d文件,所以,僅僅掃描更改過的源文件,從而生成對應的.d文件。
4.修改sed方法
在用前面的sed方法不能達到目的時,考慮到sed命令的目的是生成main.o main.d : main.c這樣的內容,所以考慮是否有另外的方法達到目的。由於$*中有目錄部分時就會有/,所以考慮是不是可以只要文件名部分。實際上應該是可以的,因為編譯器生成的依賴關係中,.o文件本身就只有文件名部分,而至於以後的編譯過程,則編譯器可以通過-I來指定的搜索路徑找到對應的文件。在重新閱讀info make后,發現可以用$(*F)來代替$*,從而解決了問題。以下是一個測試上述方法的makefile文件,寫的並不好,但是能夠達到測試的目的 :-)

default_target : all
#dir:=/root/Projects/test_makefile /root/Projects/test_makefile/dir
dir:= . ./dir
INCLUDE:= $(foreach dir_internal, $(dir), $(addprefix -I, $(dir_internal)))
CC=gcc
src:=$(foreach dir_internal, $(dir), $(wildcard $(dir_internal)/*.c))
CFLAGS += $(INCLUDE)
%.o:%.c
$(CC) -c $(CFLAGS) $< -o $*.o
obj=$(src:.c=.o)
%.d:%.c
set -e; $(CC) -MM $(CFLAGS) $(CCFLAGS) $< \
| sed 's,\($(*F)\)\.o[ :]*,\1.o $*.d : ,g' > $*.d; \
[ -s $@ ] || rm -f $@
-include $(src:.c=.d)
all : $(obj)
$(CC) -o exe $(CFLAGS) $^

那麼.d文件又是如何被重新生成的呢?在通過make -d察看調試信息時,大致明白了makefile的處理過程,從而也對所謂的makefile總是被自動重新生成有了一些理解。對於最開始執行的makefile和通過$(MAKE) -C方法執行的makefile總是會在處理完依賴關係后(生成了依賴關係圖)被重新執行一遍(這就是make的兩階段過程)第二次執行中,執行target部分,而對於其中include的makefile文件,如果對應的文件存在,它先讀入其內容,然後還會搜尋是否有規則可以更新這個文件,如果有,且其依賴新於這個makefile,就會重新生成,然後再一次include。因此,當aa.d文件被include時,由於其內容是
aa.o aa.d : aa.c \
/root/Projects/test_makefile/bb.h \ ....
這樣的形式,所以當對應.o文件所依賴文件中的一個任何一個被更新后,.d文件被更新,從而被重新include,因此,依賴關係也被重建。

5.總結
搞了好多天,總算對info make中自動依賴的實現所給出的模式規則有了一定的了解,但是還是有一些問題不是很明白。
a:其中好像說depend方法對應生成的.o文件由於不是intermediate文件,所以不會被自動刪除,而sed方法則使其過時,其潛台詞是否是指.d文件會被自動刪除,我測試結果並沒有刪除,而且好像也不應該刪除,要不又要完全重新生成,不是跟make clean后的效果一樣了嗎,不解!
b:在沒有使用$(*F)替換$*前,sed的s命令中用=和,作為分隔符,在shell中測試都可以,為什麼在makefile中就不能達到同樣的效果呢?
c:關於.d文件對於.h文件的依賴性,當任何.h文件改變時,依賴於它的.d文件都被更新,這個更新的作用是什麼,有這個必要嗎?

還請各位大俠指點。不勝感激!

[火星人 ] linux編程初步之跟我學makefile中的自動依賴已經有607次圍觀

http://coctec.com/docs/linux/show-post-185046.html