因為呀, Makefile 可以讓我們以簡簡單單一個 make 指令就讓我們打好一堆的指令
(這些指令就是我們先打在 Makefile 裡的)
這個感覺就有點像是巨集的概念
不過...一開始要上手也許不是那麼的容易
(所以才有這篇文章的出現啦XDD)
所以接下來就要開始來帶著大家寫一個 Makefile 囉
說來其實也平淡無奇
其實就是用文字編輯器就可以寫了
你高興的話,用記事本來寫也可以,都沒差
那記得檔名一定要叫做 Makefile,你問為什麼,因為不這樣就不能用。
那為什麼不能用呢,我也不知道,規定就要叫這個名字。記得 M 要大寫
我們接下來會帶著大家一步一步的來慢慢寫一個 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 代表了這一個目標(段落)會用到哪些東西(材料),它可以是你會用到的檔案或是其它目標(紅字的部份),那當然啦,這裡也可以放上很多個材料,它會依序讀取這些材料
若有多個檔案時,我們就可能需要多打幾行來能達到目的,現在來看看這種情形
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
原來啊,我們沒特別指明的時候,它會做第一個目標,也就是 test 啦
如果要特別指明做哪一段的話,我們要特別註記,比如
make test 我們就知道要做 test 這一段的事 (所以,在這裡 make 和 make test 是一樣的結果)
所以我們在這也可以打 make implement.o 表示我們只想做第二段
看到了 test 需要兩個東西,test.c 和 implement.o 嗎?
我們有了 test.c 所以不需要再多做什麼,但是我們沒有第二個的東西,所以電腦就會將這個解讀為目標,也就是電腦會看看這個 Makefile 裡有沒有一個段落是叫做 implement.o 的。這也是這兩個粗體字間必須保持名稱一致的原因
那這段當然也可以不要叫 implement.o 啦,也可以叫 imp 或其它任何名稱皆可(只是 test 的材料項那邊也要跟著改名稱)。只是寫這樣比較看的懂這段要做啥鬼@@
看到了之後,就會先跳到那一段去執行那段的內容再回來這裡繼續執行(因為在 test 裡,implement.o 是材料啊,所以要材料收集完畢才會開始動作)
同樣的,implement.o 這個目標也同樣要收集一些材料,如果遇上了和 test 一樣的情形,一樣要跳去其它段再跳回來。這裡的材料用上了萬用字元(星號)來表示這兩個檔案
就是因為這樣,所以才會做兩段的事,不然它原本也是只會執行一段的而已~
其實也不用,這是這裡為了好讀用的。不過上面這個,粗體的部份名稱要一致,畫底線的名稱要一致才行
粗體部份的名稱要一樣的緣故剛剛已經講過了,那為什麼底線的名稱也要一致呢?
因為 implement.o 是要被拿來給 gcc 用的,如果你生出來的檔名不一樣那麼 gcc 就會不知道要怎麼編譯,所以名稱要一樣囉~
這裡做的...叫做變數代換,不過和數學那個沒有關係啦,這裡做的一樣只是取代的工作(好吧,還是跟數學那個一樣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 段裡我們再來談談它的更高級用法
這段的重點在執行 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 那一段)
整個 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 的時候能更得心應手囉:)
帥!
回覆刪除