2014年4月29日 星期二

Makefile 編寫教學

為什麼我們要寫 Makefile 呢? 寫這個很麻煩欸~

因為呀, Makefile 可以讓我們以簡簡單單一個 make 指令就讓我們打好一堆的指令

(這些指令就是我們先打在 Makefile 裡的)

這個感覺就有點像是巨集的概念

不過...一開始要上手也許不是那麼的容易

(所以才有這篇文章的出現啦XDD)

所以接下來就要開始來帶著大家寫一個 Makefile 囉

 1. How to create a Makefile?

說來其實也平淡無奇

其實就是用文字編輯器就可以寫了

你高興的話,用記事本來寫也可以,都沒差

那記得檔名一定要叫做 Makefile,你問為什麼,因為不這樣就不能用。

那為什麼不能用呢,我也不知道,規定就要叫這個名字。記得 M 要大寫

2. The first one

我們接下來會帶著大家一步一步的來慢慢寫一個 Makefile

現在我們先來寫一個最簡單(但是足夠拿來用了的) Makefile

以下<TAB>均表示鍵盤上的 TAB 鍵

Example 1: 現只有一個 test.c 檔,所有程式碼都在裡面
file: test.c
<TAB>gcc -g test.c -o test.exe
這個雖然很簡單,但是真的可以拿來執行喔,可以打 make 來試試看成果

系統會幫你執行藍字的部份(gcc -g test.c -o test.exe)

這裡就可稍微看出它的效果了,讓你用四個字母取代一行指令

那當然啦,這裡也可以寫上很多很多行指令,取代起來就會更方便,範例如下

Example 2: 同 Example 1,但是希望結束時顯示完成
file: test.c
<TAB>gcc test.c -o test.exe
<TAB>echo Done!
這樣,make 就會執行這兩行指令,可以自己試試看

切記,藍字部份每行的前面都要加一個 TAB 鍵 (很重要所以要標示起來)

記得一定要加 TAB!!! (很重要所以再講一遍@@)

而且注意的是用空白鍵會出錯,請不要用空白

VIM 的話記得不要設定 expandtab 的功能

紅字的部份(file)表示目標,也就是 "我們想做什麼事"

這個的感覺就像是段落標題,可以讓我們很快的找到我們想做的地方

一個 Makefile 裡可以有很多個目標,等下會看到

這裡我們有個叫做 file 的目標,當然你也可以把它取名成其它任何名稱都可以,它都可以正常執行,但是記得後面要加冒號而且前面不可以有空格

這除了區分段落之外,也可以讓 make 知道我們要做什麼

至於綠色的 test.c 代表了這一個目標(段落)會用到哪些東西(材料),它可以是你會用到的檔案或是其它目標(紅字的部份),那當然啦,這裡也可以放上很多個材料,它會依序讀取這些材料

3. With multiple files

若有多個檔案時,我們就可能需要多打幾行來能達到目的,現在來看看這種情形

Example 3: (single target version) test.c 是主程式,而 implement.c 寫了 test.c 的實作內容,其中 implement.c 又引入了 implement.h
test: test.c implement.c implement.h
<TAB>gcc test.c implement.c -o program
這樣就會把這三支檔案編譯在一起 (記得藍色這一行不要加上 implement.h 喔,因為我們要編譯的是 .c 的檔案而不是 .h,有興趣的讀者們可以自行搜尋相關的資料)

Example 4: (multiple target version) 情況同例3
test: test.c implement.o
<TAB>gcc test.c implement.o -o program
implement.o: implement.*
<TAB>gcc -c implement.c -o implement.o
解說:

這個版本的 Makefile 多了一些東西,我們可以看到這支 Makefile 多了一個目標,第一個叫做 test,而第二個叫做 implement.o

但當我們只打 make 的時候,它哪知道我們要做哪一段呢?

原來啊,我們沒特別指明的時候,它會做第一個目標,也就是 test

如果要特別指明做哪一段的話,我們要特別註記,比如

make test 我們就知道要做 test 這一段的事 (所以,在這裡 make make test 是一樣的結果)

所以我們在這也可以打 make implement.o 表示我們只想做第二段

為什麼我只打了一個 make 就會做兩段的事啊?

看到了 test 需要兩個東西,test.c 和 implement.o 嗎?

我們有了 test.c 所以不需要再多做什麼,但是我們沒有第二個的東西,所以電腦就會將這個解讀為目標,也就是電腦會看看這個 Makefile 裡有沒有一個段落是叫做 implement.o 的。這也是這兩個粗體字間必須保持名稱一致的原因

那這段當然也可以不要叫 implement.o 啦,也可以叫 imp 或其它任何名稱皆可(只是 test 的材料項那邊也要跟著改名稱)。只是寫這樣比較看的懂這段要做啥鬼@@

看到了之後,就會先跳到那一段去執行那段的內容再回來這裡繼續執行(因為在 test 裡,implement.o 是材料啊,所以要材料收集完畢才會開始動作)

同樣的,implement.o 這個目標也同樣要收集一些材料,如果遇上了和 test 一樣的情形,一樣要跳去其它段再跳回來。這裡的材料用上了萬用字元(星號)來表示這兩個檔案

就是因為這樣,所以才會做兩段的事,不然它原本也是只會執行一段的而已~

這裡面寫了好多個 implement.o 啊,它們都要叫同個名稱嗎?

其實也不用,這是這裡為了好讀用的。不過上面這個,粗體的部份名稱要一致,畫底線的名稱要一致才行

粗體部份的名稱要一樣的緣故剛剛已經講過了,那為什麼底線的名稱也要一致呢?

因為 implement.o 是要被拿來給 gcc 用的,如果你生出來的檔名不一樣那麼 gcc 就會不知道要怎麼編譯,所以名稱要一樣囉~

4. Variables

這裡做的...叫做變數代換,不過和數學那個沒有關係啦,這裡做的一樣只是取代的工作(好吧,還是跟數學那個一樣XD)

事實上,整個 Makefile 到這裡差不多就製作完成了:有了這麼一個 Makefile,我們就可以很輕鬆的打完一串指令了,但是我們還可以更進一步節省我們要打的字數喔:)

Example 5: 有三支程式碼,每個要分別編譯後再結合在一起。
編譯選項要設 -g -O3 -Wall -lm

file: a.c b.c c.c
<TAB>gcc a.c -c -g -O3 -Wall -lm -o a.o
<TAB>gcc b.c -c -g -O3 -Wall -lm -o b.o
<TAB>gcc c.c -c -g -O3 -Wall -lm -o c.o
<TAB>gcc *.o -g -O3 -Wall -lm -o TEST.exe
可以看到啦,用這個方式我們可以用 make 來取代這四行指令,很方便。不過我每個都得再加上 -g -O3 -Wall -lm,這樣不會嫌太麻煩嗎?

所以說囉~ 我們可以把 -g -O3 -Wall -lm 設成是一個變數,這樣我們就可以直接用這個變數的名稱來代表這一串長長的參數囉,所以我們可以重新來改寫我們的 Makefile
setting = -g -O3 -Wall -lm
file: a.c b.c c.c
<TAB>gcc a.c -c $(setting) -o a.o
<TAB>gcc b.c -c $(setting) -o b.o
<TAB>gcc c.c -c $(setting) -o c.o
<TAB>gcc *.o -g $(setting) -o TEST.exe
我們先約定好 setting 這個變數就代表了我們要掛給 gcc 的參數,所以只要我們打下 $(setting) 的時候,它就會被炸成 -g -O3 -Wall -lm,結果就和我們上面一樣囉:) 記得 $ () 都要加

事實上啦,這除了代換起來好像比較少字之外,比較優的功能是可以很簡便的代換這些變數:想像啦,我們今天不想要 -g 這個參數了,那麼我是不是只要改一下 setting 這個變數就全部解決了呢?不然我就得一個個去每一行改參數設定,多麻煩啊!

其它不少地方這個功能也都能派上用場喔:)

在第 6 段裡我們再來談談它的更高級用法

5. Commands

這段的重點在執行 UNIX 的指令。剛剛我們也在做同樣的事情,只是這個指令一直叫做 gcc 而已。我們來看看它還可以做些什麼事吧

Example 6: 加入清除編譯好的執行檔及目的檔
file: a.c b.c c.c
<TAB>gcc a.c -c -g -O3 -Wall -lm -o a.o
<TAB>gcc b.c -c -g -O3 -Wall -lm -o b.o
<TAB>gcc c.c -c -g -O3 -Wall -lm -o c.o
<TAB>gcc *.o -g -O3 -Wall -lm -o TEST.exe
clean:
<TAB>rm *.o TEST.exe
我們可以看到,我們只是在 Example 5 上多加了一段上去而已,讓它可以刪除不必要的檔案。

前面提到啦,我們如果想要做 clean 這一段的內容,我們就要打 make clean,這樣就OK囉

其它的應用像是把程式碼打包成一個檔案啦,同步檔案啦,都可以用這種方法做到

(註:只打 make 只會跑 test 那一段)

6. Advanced variables

整個 Makefile 的介紹差不多都結束了,這裡要講更深入一點的用法,如果這裡看不懂的話,也不會影響整個的寫作流程

在例 5 中我們有三支 code,我們可以運用變數代換的方式來避免我們打那麼多重複的字,但是我們不免還是打了三行出來,雖然我們可以不斷的代換,但終究我們還是得寫三遍,所以有這裡的用法產生囉

Example 7: based on Example 5
TARGET = program
SOURCES  = a b c
SET = -Wall -O3 -s
SRCS = ${SOURCES:%=%.c}
OBJS = ${SRCS:%.c=%.o}
$(TARGET) : $(OBJS)
        gcc -o $(TARGET) $(OBJS)
%.o: %.c
        gcc -c -o $@ $<
解說:

TARGET: 只是用來標記開始的地方及輸出檔名稱而已,和前面提過的基本代換相同

SOURCES: 程式碼,這裡不用加上 .c 的副檔名,理由下見

SRCS: 這裡比較複雜,先大概有個概念這會變成 a.c b.c c.c

裡面的 SOURCES: 代表 SCOURCES 裡的每個字(記得 SOURCES 裡面有 a b c 三個字嗎),對每個字(%)我都要指定它為這個字(%)加上 .c,就像 a = a.c,原本的 a 就被掛上副檔名了,b c 兩個同樣也會被掛上副檔名。

所以 SRCS 實際上會變成 a.c b.c c.c

OBJS: 同理,內容為 a.o b.o c.o

第一個目標 $(TARGET) 需要 a.o b.o c.o (將 OBJS 炸開,內容剛剛寫過囉)

最後 gcc -o program a.o b.o c.o  (將變數炸開的結果)

%.o 這個目標,會解決 TARGET 裡需要的 a.o b.o c.o 這三個材料,而 % 將會被取代為 a b c 三項其中一個,而後面 %.c 的 % 也會同時被取代,所以這相當於

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

注意到 $@ 代表了目標(紅字的部份),而 $< 則是材料(綠字) 所以才會炸開成為這種樣子

(b c 的處理就不寫囉~)

這樣子我們只要寫成這個樣子就可以不必寫那麼多行了,尤其是在有上百支程式碼要一起用時更顯得其優勢

這篇文章到此告一段落了,希望能幫助各位在編寫 Makefile 的時候能更得心應手囉:)

1 則留言: