2011年12月23日 星期五

快快樂樂 Makefile 入門教學

有感於身旁還是很多人對於 Makefile 感到陌生決定來發篇教學文...

要注意的是在本篇文章中所採用的詞彙與正式用語可能有點出入, 主要是為了方便理解


Makefile 對於入門者而言最重要的兩個功能是:
  1. 建置規則 : 該目標要如何完成?
  2. 檔案相依性 : 如果 xxx 更新的話, 代表我要跟著更新(或著是重新編譯)



情境模擬

在這邊先來討論為什麼我們需要 makefile 

假設我們現在有三個檔案 : a.c, b.c, c.c

編譯的時候當然可以很直接的

gcc a.c b.c c.c -o prog

但如果今天是有三十個檔案的話 :  a.c, b.c, c.c ... z.c, aa.c, bb.c, cc.c, dd.c

那麼編譯時便需要

gcc a.c, b.c, c.c ... z.c, aa.c, bb.c, cc.c, dd.c -o prog

當然如果對於一般 shell 有點熟悉的話會很直覺的想到可以

gcc *.c -o prog

大致上是沒有問題的

那幹麼要學 Makefile 阿?

隨著程式碼長大, 編譯時間開始慢慢的變長

從原本的按下去瞬間完成, 慢慢的成長到 30 秒甚至是一分鐘時

會發現按下編譯後, 會想要跑去逛個 ptt 或看個拍賣, 再去逛個奇毛新聞

然後每次編譯的時間就從原本的一瞬間變成了十幾分鐘甚至半小時(疑?

但回頭分析一下上面的兩個編譯方式
  1. gcc a.c, b.c, c.c ... z.c, aa.c, bb.c, cc.c, dd.c -o prog
  2. gcc *.c -o prog
會發現每次編譯都是重新編譯每個檔案


發生這種事的時候很直覺得就會想要分開編譯成很多 .o 檔

結果編譯的過程變成


gcc -o a.o -c a.c
gcc -o b.o -c b.c
gcc -o c.o -c c.c
...
gcc -o z.o -c z.c
gcc *.o -o prog


然後你想修改 a.c, 然後重新編譯

gcc -o a.o -c a.c
gcc *.o -o prog

接著測試過後你發現有 bug , 需要更改 b.c, 然後重新編譯

gcc -o b.o -c b.c
gcc *.o -o prog

...

結果省了編譯時間多了手工時間

雖然可以避免編譯時跑去逛網頁但也很浪費時間

如果看到這邊覺得這樣的過程可以讓自己覺得很忙很認真而不想改善開發流程的話

就直接按上一頁吧



套用 Makefile !

終於要進入正題使用 Makefile 了,

使用 Makefile 主要是避免不要要的時間浪費, 以及一直重新的打字

開始介紹前再度複習 Makefile 兩個基本功能:
  1. 建置規則 : 該目標要如何完成?
  2. 檔案相依性 : 如果 xxx 更新的話, 代表我要跟著更新(或著是重新編譯)
在這邊我們想要透過 Makefile 來建置程式

那要怎樣才能把剛才所作的事換成用 Makefile 作勒?

就開始一步一步分析並從頭開始寫, 我們的目標是要建置 prog 這隻程式

打開一個 Makefile 並且打入

prog:

接著打入 make 他會跟你抱怨, 不知道怎麼建立 prog

make: Nothing to be done for `prog'.

所以這邊要開始進入 Makefile 第一個重要的功能了, 如何加入建置規則?

方式是在該目標下一行先敲一下 tab 然後開始寫指令

要注意, 一定要是 tab 而不能是空白

prog:
    gcc a.o b.o c.o -o prog

在這邊為了簡化還是先退回只有三個檔案

然後按下 make

gcc a.o b.o c.o -o prog

喔喔喔喔喔喔喔喔 現在只打 4 個字就取代 23 個字了!

但目前其他三的檔案還是沒有建立規則, 大致受上一樣畫葫蘆

prog:
    gcc a.o b.o c.o -o prog

a.o:
    gcc a.c -o a.o -c

b.o:
    gcc b.c -o b.o -c

c.o:
    gcc c.c -o c.o -c

在按下 make 後你可能會發現, 他跟你說 prog 已經是最新版了

make: `prog' is up to date.

現在就來修改 a.c 檔案的內容後重新按 make

...

你會發現

他還是跟你說 prog 已經是最新版了

make: `prog' is up to date.

主要原因是我們沒有告訴 make 檔案相依性

他不知道什麼時候該重新建置目標

要加入檔案相依性只要在目標後面加入所相依的檔案名稱即可

prog: a.o b.o c.o 
    gcc a.o b.o c.o -o prog

a.o: a.c 
    gcc a.c -o a.o -c

b.o: b.c 
    gcc b.c -o b.o -c

c.o: c.c 
    gcc c.c -o c.o -c


加入完相依性後再按 make 他就會重新編譯 a.o 跟 prog 了!


gcc a.c -o a.o -c
gcc a.o b.o c.o -o prog



所以用 Makefile 有啥好處 ?

從上面的例子可以看出使用 Makefile 來建置程式的好處 :

  1. 節省打編譯指令的時間
  2. 建立相依性後, 可自動偵測是否需重建
其實在現代的多核心機器下還可以利用 Makefile 的內建功能來達到平行建置

只要你在建置時在 make 後面加入 -jn 其中 n 帶入數字,

例如

make -j4

代表一次平行執行四個任務, 也就是最多同時會編譯四個檔案!

這個功能則是一般手動編譯較難以達成的, 並且可以非常有效的節省編譯時間



Makefile 常見錯誤訊息

一般而言寫 Makefile 最容易看到下列的錯誤訊息

Makefile:2: *** missing separator.  Stop.

但看到後請不要慌張, 這個訊息通常是你的建置規則前面沒有用 tab 所造成的

尋著他後面提示的行號去檢查很快就可以解決



雜談

Makefile 在 linux 環境及泛 Unix 家族底下都是常用的建置工具

但是事實上 Makefile 可以作的更多, 不只是編譯建置程式

在很多情況下我們可以利用 Makefile 的平行建置功能來達到平行執行某些工具

例如如果要跑 benchmark 之類的, 也給他寫成 Makefile

然後在 i7 上面 make -j8 就整個很開心