程式扎記: [GNU Make] 規則 : 自動產生依存關係

標籤

2010年9月14日 星期二

[GNU Make] 規則 : 自動產生依存關係

前言 : 
在 範例中, 當我們開始使用標頭檔時, 有個棘手問題會開始困惱我們. 雖然在範例中我們可以在 makefile 檔中手動加入目的檔與 C 標頭檔的依存關係, 但在大數程式中, 這幾乎是不可能的事, 因為大多數的標頭檔還會包含其他標頭檔所形成的複雜樹狀結構. 
考慮電腦擅長搜尋以及樣式比對, 讓我們使用一個程式找出這些檔案之間的關係, 我們甚至可以使用此程式以 makefile 的語法撰寫出這些依存關係. 在 gcc 中這是一個選項, 許多的其他C/C++ 編譯器也都會讀進原始檔並寫出 makefile 的依存關係, 例如下面是一個簡單範例尋找 stdio.h 的依存關係 : 
localhost:/home/benjamin/src/gnumkproj> echo "#include " > stdio.c
localhost:/home/benjamin/src/gnumkproj> gcc -M stdio.c
stdio.o: stdio.c /usr/include/stdio.h /usr/include/sys/cdefs.h \
/usr/include/sys/_null.h /usr/include/sys/_types.h \
/usr/include/machine/_types.h


傳統上會有兩種方法可用來自動產生依存關係納入 makefile . 第一種也是最古老的方法就是在 makefile 結尾加上下面這一列 : 
# Automatically generated dependencies follow - Do Not Edit

然後撰寫一支命令稿以便加入這些自動產生的命令稿. 這麼做當然比手動加入的好, 但還不夠好. 第二種方法就是為 make 加入一個 include 指令. 而今大多數的 make 版本都支援 include 指令, 當然 GNU make 一定可以這麼做. 因此訣竅就是撰寫一個 makefile 工作目標, 此工作目標的動作就是以 -M 選項對所有原始檔案執行 gcc, 並將結果存入一個依存檔 (dependency file), 然後重新執行 make 以便把剛才所產生的依存檔引入 makefile , 這樣就可以觸發我們所需要的更新動作. 在 GNU make 中, 你可以使用以下規則來達成此目的 : 
  1. depend: count_words.c lexer.c counter.c  
  2.         $(CC) -M $(CPPFLAGS) $^ > $@  
  3.   
  4. include depend  

執行 make 以建造程式之前, 你首先應該執行 make depend 以產生依存關係. 這麼做雖然不錯, 但是當人們對原始檔案加入或移除依存關係時, 通常不會重新產生 depend 檔. 這會造成無法重新編譯原始檔案, 整個工作又會變成一團糟. 在 GNU make 中你可以透過一個很酷的功能以及一個簡單的演算法來解決此問題. 首先介紹這個簡單的演算法. 如果我們為每個原始檔建立依存關係, 以及將之擺入相應的依存檔 (一個副檔名為 .d 的檔案) 並以該 .d 檔為工作目標, 加入此依存規則 (dependency rule) , 這樣當原始檔遭到變更, make 就會知道需要更新該 .d 檔 (以及目的檔) : 
counter.o counter.d: src/counter.c include/counter.h include/lexer.h

你可以使用如下的樣式規則以及命令稿來產生這項規則 : 
  1. %.d: %.c  
  2.         $(CC) -M $(CPPFLAGS) $< > $@.$$$$;\  
  3.         sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' <$@.$$$$ > $@;\  
  4.         rm -f $@.$$$$  

現在介紹這個很酷的功能. make 將會把 include 指令所指名的檔案視為一個需要更新的工作目標. 所以如果我們表明我們要引入 .d 檔, 則 make 將會在讀進 makefile 檔時自動建立這些 .d 檔. 我們的 makefile 加入了自動產生依存關係的功能之後會變成 : 
* makefile_2-7 檔案內容 :
  1. VPATH = src include  
  2. vpath %.l %.c src  
  3. vpath %.h include  
  4. CPPFLAGS = -I include  
  5. SOURCES = count_words.c \  
  6.                          lexer.c \  
  7.                          counter.c  
  8.   
  9. count_words: count_words.o counter.o lexer.o -lfl  
  10. count_words.o: count_words.c counter.h  
  11. counter.o: counter.c counter.h lexer.h  
  12. lexer.o: lexer.c lexer.h  
  13. lexer.c: lexer.l  
  14.   
  15. include $(subst .c,.d,$(SOURCES))  
  16.   
  17. %.d: %.c  
  18.         $(CC) -M $(CPPFLAGS) $< > $@.$$$$;\  
  19.         sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' <$@.$$$$ > $@;\  
  20.         rm -f $@.$$$$  

include 指令總是應該放在手動撰寫之依存關係的後面, 這樣預定目標才不會被某個依存檔搶走了include 指令可用來指定一串檔案 (檔名之中可以包含通配符). 我們在此處使用了一個 make 函式 subst 來將一串原始檔的檔名換成一串依存檔的檔名. 現在你只要知道 subst 可用來將 $(SOURCES) 裡的文字從 .c 字串換成 .d 字串. 
如果針對此 makefile 以 --just-print 選項來執行 make , 則會的到如下的結果 : 
localhost:/home/benjamin/src/gnumkproj> gmake -f makefile_2-7 --just-print
makefile_2-7:15: count_words.d: No such file or directory <在 include 命令時檢查到 依存檔不存在>
makefile_2-7:15: lexer.d: No such file or directory
makefile_2-7:15: counter.d: No such file or directory

cc -M -I include src/counter.c > counter.d.$$;\ <開始建立依存檔>
sed 's,\(counter\)\.o[ :]*,\1.o counter.d : ,g' counter.d;\
rm -f counter.d.$$
 <刪除臨時檔>
cc -M -I include lexer.c > lexer.d.$$;\
sed 's,\(lexer\)\.o[ :]*,\1.o lexer.d : ,g' lexer.d;\
rm -f lexer.d.$$
cc -M -I include src/count_words.c > count_words.d.$$;\
sed 's,\(count_words\)\.o[ :]*,\1.o count_words.d : ,g' count_words.d;\
rm -f count_words.d.$$
cc -I include -c -o count_words.o src/count_words.c
cc -I include -c -o counter.o src/counter.c
cc -I include -c -o lexer.o lexer.c
cc count_words.o counter.o lexer.o /usr/lib/libfl.a -o count_words

localhost:/home/benjamin/src/gnumkproj>cat counter.d <檢驗臨時檔的內容>
counter.o counter.d : src/counter.c include/lexer.h include/counter.h \
/usr/include/stdio.h /usr/include/sys/cdefs.h /usr/include/sys/_null.h \
/usr/include/sys/_types.h /usr/include/machine/_types.h
 <只要依存條件變更, 都會造成目標檔 counter.o 與 counter.d 的更新>


make 一開始的錯誤訊息不用擔心, 這只是一個警告訊息. 起先 make 會搜尋引入擋, 但是找不到他們, 所以 make 會在搜尋 "用來建立這些檔案的規則" 之前發出 No such file or directory 的警告. 若不想看到這個警告, 只要為 include 指令前置一個連字號 (-) 即可. 警告之後可以看到 make 以 -M 選項調用 cc 以及執行 sed 命令的動作. 請注意 make 必須調用 flex 以建立 lexer.c , 然後在開始滿足預定目標前刪除 lexer.c 這個臨時檔. 

補充說明 : 
* [Linux命令] sed : 檔案內容修改

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!