一、前言
回想自己的第一個(gè)Makefile,是這個(gè)樣子的
后來(lái)有所進(jìn)步,陸續地寫(xiě)了一些大都是這個(gè)樣子的Makefile:
看上去還行,用起來(lái)也不錯,但是隨著(zhù)程序規模的擴大,每次添加一個(gè)新文件,都要手動(dòng)修改Makefile,實(shí)在是不厭其煩。
后來(lái)閱讀了一些開(kāi)源程序的Makefile源代碼,當然,不是automake生成的那種,有了一些心得,幾番進(jìn)化,一段時(shí)間后,感覺(jué)對GNU make算是有了些初步的了解,在此總結一下,也算是溫故而知新了。而且我記性比較差 (IMG:http://acm.hrbeu.edu.cn/forums/style_emoticons/default/F5CC4.gif) ,放在這里算是記錄一下,免得以后忘記。同時(shí)也免得大家再去翻那些繁復的手冊,浪費不必要的時(shí)間。 (IMG:http://acm.hrbeu.edu.cn/forums/style_emoticons/default/D3F5F.gif)
下文中makefile操作的對象有三個(gè)文件: foo.c , bar.c 和bar.h,內容分別如下:
foo.c
bar.c
bar.h
OK,該交代的都交代了,進(jìn)入正題。
二、我的makefile模板
把上個(gè)項目的makefile整理了一下,感覺(jué)結構比較清晰,可以作為模板供以后使用。
文件內容大體是這個(gè)樣子的:
解釋?zhuān)?br>
前幾行都是變量的定義,至于為什么要定義這些變量,理由和編程中使用宏定義是一樣的,那就是改一個(gè)就可以使很多地方同時(shí)生效,避免了重復的工作。
按照慣例:
CC變量指定了使用的編譯器
CFLAGS變量包含了所需的編譯選項
INCLUDE是尋找頭文件的路徑
LFLAGS是加載外部庫時(shí)的指定選項。
TARGET變量代表最終要生成的可執行程序
下面的內容就是關(guān)鍵了,我們將利用一些GNU make內置的函數與推導規則來(lái)完成我們的目標。
首先的任務(wù)是自動(dòng)獲得當前目錄下所有的源文件,好讓我們新添文件后不必再修改Makefile。
完成這個(gè)功能的是這行代碼
SOUCE_FILES = $(wildcard *.c)
wildcard是GNU make程序預定義的一個(gè)函數,作用便是獲取匹配模式文件名,原型為$(wildcardPATTERN)。它的詳細說(shuō)明可以看這里。簡(jiǎn)單來(lái)說(shuō)wildcard函數的參數只有一個(gè),就是函數名之后的文件名模式,這里的模式使用shell可識別的通配符,包括“?”(單字符)、“*”(多字符)等?,F在我們的需求是獲取當前目錄下的所有.c文件,模式自然是*.c。
按照最基本的依賴(lài)規則,生成TARGET文件依賴(lài)于一系列的.o文件,那么如何獲得這些.o文件的列表呢?答案是使用patsubst模式替換函數函數:
$(patsubst %.c,%.o,$(SOUCE_FILES))
模式替換函數patsubst函數原型為$(patsubstPATTERN,REPLACEMENT,TEXT),相比wildcard,它要復雜一些,顧名思義,三個(gè)參數依次代表了匹配模式,替換規則,替換目標字符串。在這里,我們需要把所有.c替換成.o,所以寫(xiě)成上面的樣子就可以了。
現在c源文件列表和obj文件列表都有了,下一步就該為每個(gè)源文件編寫(xiě)規則了。
其實(shí)很多源文件的編譯規則都是一樣的,就像最開(kāi)始那個(gè)Makefile中那樣
僅僅是文件名不同而已,因此就給了我們提取模式的某種可能性。我在一個(gè)關(guān)于winsock的makefile中找到了答案:
這個(gè)規則利用了GNU make的后綴規則。
在這里,當定義了一個(gè)目標是“.c.o”的規則時(shí)。它的含義是所有“.o”文件的依賴(lài)文件是對應的“.c”文件。因此在這條規則下,foo.c將被自動(dòng)編譯成foo,bar.c被編譯成bar。
而特殊目標.SUFFIXES這句的作用是: 在默認后綴的基礎上,增加了可以作為后綴的關(guān)鍵字符串。
其實(shí).c.o是肯定在默認識別的規則中的,不過(guò)為了保險起見(jiàn),還是顯式地聲明一下比較好。
可以看到,這個(gè)規則十分的晦澀,反正我第一眼真是沒(méi)看明白。因此,新版本的GNU make已經(jīng)使用模式規則替代了后綴規則。
同樣的功能,利用模式規則實(shí)現如下:
這樣看起來(lái)便清晰多了。如果考慮到頭文件,完美的寫(xiě)法應該是這樣的:
在上面的規則中,還使用了一些GNU make的自動(dòng)化變量,他們的含義分別如下:
$@ --- 目標文件
$< --- 第一個(gè)依賴(lài)文件
$^ --- 所有的依賴(lài)文件
更多的自動(dòng)化變量可以參見(jiàn)這里
最后的規則就是生成可執行文件了,很普通,不再贅述。
為了方便調試,可以在makefile中定義一些偽目標。(偽目標的解釋和意義可以看這里)
一般調試用的makefile中都會(huì )有兩個(gè)偽目標,一個(gè)clean,一個(gè)debug
對于clean,手冊里說(shuō):“make存在一個(gè)內嵌隱含變量“RM”,它被定義為:“RM = rm–f”。因此在書(shū)寫(xiě)“clean”規則的命令行時(shí)可以使用變量“$(RM)”來(lái)代替“rm”,這樣可以免出現一些不必要的麻煩!”雖然不知道“必要的麻煩”是什么,但是小心不為過(guò),照著(zhù)手冊做比較好。
對于debug,和正常模式不同的就是添加了一些編譯選項,修改CFLAGS的內容就可以了。但目前還沒(méi)搞明白怎么動(dòng)態(tài)地在makefile里修改變量的內容。這個(gè)問(wèn)題以后再說(shuō)。
三、在多文件夾情況使用makefile組織代碼
上一段中給出的makefile,對于一般的小程序已經(jīng)足矣,但是如果代碼文件越來(lái)越多,最后不得不放到幾個(gè)文件夾中,這時(shí)又該怎么辦?
比如說(shuō)我們準備把bar.c中的函數整理成了一個(gè)函數庫libbar放在主程序文件夾中的子文件夾libbar中,這時(shí)該如何利用makefile來(lái)組織這些文件?
比較好的辦法是在libbar文件夾中放置一個(gè)獨立的子makefile,然后在主makefile里調用它。
libbar/Makefile:
主Makefile:
在主makefile中使用了shell的for語(yǔ)句,循環(huán)取出SUBDIRS中的子文件夾名,然后進(jìn)入子文件夾執行make,然后返回。如果在子makefile中出錯,編譯過(guò)程將終止。
四、編譯多個(gè)目標
不知你有沒(méi)有遇到過(guò)這樣的情況,那就是需要從很多的代碼,生成很多的可執行文件。
例如編寫(xiě)了一堆小工具,而每個(gè)工具只有一個(gè)源文件,用foo.c生成foo,用bar.c生成bar。
一個(gè)一個(gè)編譯肯定不現實(shí),這時(shí)該怎么做?讓我們用GNU make來(lái)解決吧!
仔細閱讀手冊,發(fā)現GNU make中的靜態(tài)模式,正好可以滿(mǎn)足這個(gè)要求。
方便閱讀,直接將手冊中關(guān)于靜態(tài)模式的解釋粘貼如下:
對應我們的需求,應該是用符合%.c模式的文件,生成文件名為%的可執行文件,同時(shí)利用自動(dòng)化變量,構造規則如下:
其中$(TARGET_FILES)為最終的可執行文件名,可以用wildcard配合patsubs函數獲得。
因為$(TARGET_FILES)不止一個(gè),所以直接寫(xiě)這個(gè)命令的結果是只會(huì )編譯出一個(gè)可執行文件,即第目標文件列表中的一個(gè)文件,要想成功編譯出所有的,還需要偽目標的幫忙。
完整的makefile如下:
五:收工
如果發(fā)現有什么錯誤的話(huà),歡迎指出~~~ (IMG:http://acm.hrbeu.edu.cn/forums/style_emoticons/default/C72B3.gif)
聯(lián)系客服