程式扎記: [GNU Make] 如何撰寫一個簡單的 makefile

標籤

2010年9月13日 星期一

[GNU Make] 如何撰寫一個簡單的 makefile

前言 : 
make 程式可以讓 "原始程式碼轉成可執行檔" 之類的例行工作自動化. 相較指令搞, make 優點在於你可以把程式中各元素之間關係告訴 make, 之後 make 會依據這些關係與時間戳記判斷應該重新進行哪些步驟, 以產生你要的程式

make 一般會將工作細節放在一個名為 makefile 的檔案中. 下面是一個 makefile 的範例檔 : 
* makefile 內容 : 
  1. hello: hello.c  
  2.            gcc hello.c -o hello  
要建造此程式, 你可以在命令提示符後輸入 : 
$ make

以便執行 make. 這將 make 程式讀入 makefile 檔, 並且建造他在檔案中遇到的第一個工作目標 (此處為 hello). 如果將工作目標 (target) 指定為命令列參數, make 就會針對該項工作目標進行更新動作. 如果修改原始檔中任何內容並重新調用 make, 將使這些步驟中有某些被重複進行, 因此原始檔的變動會影響到產出的執行檔. 這個描述檔或 makefile 中描述了原始擋, 中間檔以及可執行檔間的關係, 使得 make 能以最少工作完成更新執行檔的目標

工作目標與必要條件 : 
基本上, makefile 檔案包含了一組用來建造應用程式的規則make 所看到的第一項規則會被當成預訂規則 (default rule) 使用. 一項規則可分為三個部分 : 工作目標(target), 它的必要條件(prerequisites) 以及所要執行的命令 (commands) : 
target: prereq1 prereq2
^Icommands

Ps. 這裡以符號 '^I' 代表鍵盤的 Tab 鍵. 

工作目標是一個必須建造的檔案或東西. 必要條件或依存對像 (dependents) 是工作目標得以被成功建造之前, 必須事先存在的那些檔案. 而要執行的命令則是必要條件成立時將會執行的 shell 命令. 下面規則用一個 C 檔案 foo.c 編譯成一個目的檔 foo.o : 
foo.o: foo.c foo.h
^Igcc -c foo.c

工作目標 foo.o 出現在冒號前, 必要條件 foo.c 與 foo.h 出現在冒號後面. 命令稿則出現在後續的文字列上, 而且前面必須要有跳格符號 (鍵盤的 Tab 鍵). 下面這個程式會在它的輸入計算 fee, fie, foe 與 fun 等詞彙(單字) 出現的次數. 這個程式使用了一個 flex 掃描器, 並使用 makefile 進行編譯與產出執行檔 : 
* count_words.c 代碼 : 
  1. #include   
  2.   
  3. extern int fee_count, fie_count, foe_count, fum_count;  
  4. extern int yylex(void);  
  5.   
  6. int main(int argc, char** argv){  
  7.     yylex();  
  8.     printf("%d %d %d %d\n",fee_count, fie_count, foe_count, fum_count);  
  9.     exit(0);  
  10. }  
* 掃描器 lexer.l 內容 : 
  1.         int fee_count = 0;  
  2.         int fie_count = 0;  
  3.         int foe_count = 0;  
  4.         int fum_count = 0;  
  5. %%  
  6. fee fee_count++;  
  7. fie fie_count++;  
  8. foe foe_count++;  
  9. fum fum_count++;  
* 建立程式的 makefile : 
  1. count_words: count_words.o lexer.o -lfl  
  2.         gcc count_words.o lexer.o -lfl -o count_words  
  3.   
  4. count_words.o: count_words.c  
  5.         gcc -c count_words.c  
  6.   
  7. lexer.o: lexer.c  
  8.         gcc -c lexer.c  
  9.   
  10. lexer.c: lexer.l  
  11.         flex -t lexer.l > lexer.c  

當 makefile 首次執行時, 我們會看到 : 
[benjamin@localhost ~/src/gnumkproj]$ make
gcc -c count_words.c
count_words.c: In function 'main':
count_words.c:9: warning: incompatible implicit declaration of built-in function 'exit'
gcc -c lexer.c
gcc count_words.o lexer.o -lfl -o count_words

現在我們已經建造好一個可執行的程式, 然而這是一個簡化的範例, 並未用到 make 大部分的特性. 當這個 makefile 執行時, 你可能會發現執行順序會跟你在 makefile 裡描述的順序相反, 而這種通則的工作目標會先擺在前面而後跟著細節的 "從上而下" (top-down) 風格是 makefile 常見的手法. 

檢查依存關係 : 
讓我們藉由前面的例子說明 make 的工作順序. 首先 make會注意到命令列尚未指定任何工作目標, 所以會以第一個工作目標(count_words)開始建造. 當 make 檢查其必要條件時看到三個項目 : count_words.o, lexer.o, 以及 -lfl. 現在 make 會想建造 count_words.o, 並看到相應規則. 接著 make 又發現建造 count_words.o 的必要條件 count_words.c 而其並未關聯到任何規則, 但存在 count_words.c 這個檔案, 所以會執行相應命令把 count_words.c 轉換成 count_words.o : 
gcc -c count_words.c

這種 "從工作目標到必要條件, 從必要條件再到工作目標" 的鏈結機制就是 make 分析 makefile 決定要執行哪些命令的典型作法. 必要條件會讓下一個項目 lexer.o 被建造. 因此規則會將 make 導向到 lexer.c, 但這次 lexer.c 並不存在. 所以 make 會找尋 lexer.c 的工作目標或是產出 lexer.c 的規則. 所以 make 會執行 flex 程式. 現在 lexer.c 存在了, 接著make 會執行 gcc 命令建立 lexer.o. 

最後 make 看到了 -lfl. 而其中 -l 是個選項, 用來要求 gcc 必須將其所指定的系統程式庫連結進應用程式. 此處指定了 fl 這個參數, 代表實際個程式庫名稱為 "libfl.a" . GNU make 對這個語法提供了特別的支援. 當 -l<NAME> 形式的必要條件被發現時, makke 會搜尋 libNAME.so 形式的檔案 ; 如果找不到相符的檔案, make 接著會搜尋 libNAME.a 形式的檔案. 在此例中, make 會找到 /usr/lib/libfl.a 而且會進行最後的動作 - 連結. 

減少重新建造的工作量 : 
接下來因為某些需求我們必須修改 lexer.l 而完成新功能, 故 lexer.l 修改如下 : 
  1.         int fee_count = 0;  
  2.         int fie_count = 0;  
  3.         int foe_count = 0;  
  4.         int fum_count = 0;  
  5. %%  
  6. fee fee_count++;  
  7. fie fie_count++;  
  8. foe foe_count++;  
  9. fum fum_count++;  
  10. .  

重新編輯檔案後, 我們還需要重新建造應用程式以便測試修正結果 : 
[benjamin@localhost ~/src/gnumkproj]$ make
flex -t lexer.l > lexer.c
gcc -c lexer.c
gcc count_words.o lexer.o -lfl -o count_words

請注意, 這次 count_words.c 檔案並未被重新編譯. 分析規則時, make 發現 count_words.o 已經存在而且該檔案的時間戳記在其必要條件 count_words.c 之後, 所以不需要採取任何更新的動作. 不過分析 lexer.c 時 make 發現必要條件 lexer.l 的時間戳記在其工作目標 lexer.c 之後, 所以 make 必須更新 lexer.c. 這個動作會依次造成 lexer.o 與 count_words 的更新. 

沒有留言:

張貼留言

網誌存檔

關於我自己

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