來源:http://blog.csdn.net/clever101編程
決心學習Makefile,一方面是爲了解決編譯開源代碼時須要跨編譯平臺的問題(發現一些開源代碼已經在使用VS2010開發,但我還沒安裝VS2010,我想在VS2008下編譯這些代碼);另外一方面源碼在服務器端編譯的話,使用IDE的方式編譯仍是不太方便。windows
本文主要分爲三部分:第一部分講述namke工具使用makefile的用法;第二部分講述makefile的主要語法;第三部分講述本身動手實踐學習寫makefile文件。第四部分是編寫一個工具將vc工程文件轉化爲Makefile文件。服務器
首先要清楚的是在VS環境下使用Makefile的工具是nmake。所以咱們須要弄明白nmake的使用Makefile文件經常使用命名行用法。nmake使用Makefile文件經常使用命名行用法是:函數
其中makefile爲makefile文件,/x stderrfile爲可選參數,即把namke錯誤存儲到文件stderrfile。工具
接着介紹makefile的主要語法。makefile的註釋以#開頭,如:學習
Makefile的一個重要組成部分是宏。Makefile中的宏和C語言的中宏相似,其實質就是字符串替換。其語法很簡單,以下:ui
macro name = macro valuethis
直譯就是宏名 = 宏的值spa
VS預約義了不少宏,如OUTDIR,你能夠在你的Makefile從新定義這些宏以覆蓋原來的值。.net
宏可使用環境變量,如你的系統有一個OPEN_SOURCE的環境變量,而後你能夠這樣定義宏:
THIRD_PARTY = $(OPEN_SOURCE)
宏的引用用法是 $(宏名)。
接着介紹Makefile的第二個重要組成部分預處理指令。Makefile的預處理指令和C語言的預處理指令相似,其經常使用指令以下:
!ERROR string —— 顯示錯誤「string」, 而後中止執行,錯誤代碼爲U1050
!MESSAGE string —— 顯示字符串,這個通常用於信息顯示C語言的#pragma message
!INCLUDE [<]filename[>] —— 包含makefile。
!IF const —— 若是成立(非零),則處理!F和下一個!ELSE或!ENDIF之間的語句
還有諸如!IFDEF macroname、!IFNDEF macroname、!ELSE、!ELSEIF、!ELSEIFDEF、!ELSEIFNDEF、!ENDIF和C語言的#if之類的指令的意義是一致的,這裏就不一一詳述了。
Makefile的第三個主要組成部分是描述塊。描述塊的結構以下:
目標:依賴項
命令
這裏略微解釋下什麼叫目標、依賴項和命令。所謂目標就是用戶最終但願獲得的結果,也就是nmake須要生成的結果。目標能夠是一個文件、目錄,也能夠什麼都不是。若是目標不存在或者目標的時間戳(文件的最後修改時間)比依賴項早,或者目標類型不是文件,nmake將運行描述塊中的「命令」。
依賴項是指在生成目標所須要使用到的對象。一個目標能夠有一個或多個依賴項,也能夠沒有依賴項。多個依賴項以空格分隔。若是指定的依賴項不存在,則在其餘描述塊的目標中尋找,但首先須要生成這個目標。
命令是nmake在生成目標時所調用的命令。與用戶本身在命令行中執行效果是同樣的。
在使用namke進行程序構建時,nmake採用了時間戳判斷機制。在生成一個目標時,會判斷目標文件是否存在或目標的最後修改時間是否晚於全部依賴項的最後修改時間。若是全部依賴項的最後修改時間都比目標的最後修改時間晚,則說明當前的目標文件是使用現有的依賴項生成,是最新的,沒有必要再進行生成。
介紹到這裏,可能你對Mdakefile的語法細節有了大體的瞭解,但估計你對Makefile的經常使用文件結構還不瞭解。若是缺乏對這一層的理解,你仍是對如何編寫Makefile文件一頭霧水。下面介紹一下經常使用的Makefile文件結構。Makefile文件結構能夠是以下的結構:
# 宏定義
……
# 描述塊
學了這麼多,咱們來實踐一下。首先咱們來一個簡單的控制檯工程——ConsoleTest。一切根據工程嚮導採用默認設置便可。而後在main函數中添加幾句簡單代碼(這個用於判斷咱們生成的程序是否成功),具體以下:
而後咱們在ConsoleTest文件夾下新建一個makefile.vc。咱們開始正式編寫一個makefile文件了。這時咱們的大腦可能會一片空白,雖然你學了不少makefile語法,但邁出第一步依然是困難,這是正常的反應。好吧,讓咱們一步步來吧。首先要告訴你makefile的一個基本原則:以終爲始,這個彷佛和咱們平時進行的過程式編程的原則相悖。所謂以終爲始,就是你經過makefile文件首先告訴編譯器這個工程是想生成一個exe仍是一個dll仍是一個靜態庫。而後告訴編譯器要生成這個exe之類須要生成哪些obj文件。在這個例子中,咱們要生成一個exe,因此咱們在makefile文件的第一行就是:
接下來就是編譯器的通常生成過程:編譯加連接命令,具體是:
其中cl語句是VC編譯器的編譯器的命令行編譯,link語句是VC連接器的命令行用法,這裏只簡單敘述cl和link的用法。
cl的一些經常使用選項:
-c: 編譯但不連接
-D: 定義預處理器,如-D_X86=1:指定在x86平臺上編譯,-D_DEBUG:定義預處理器_DEBUG,
-I:包含的頭文件
cl的最後一個參數是所編譯的文件。
link的一些經常使用選項:
/INCREMENTAL:是否啓用增量連接,YES爲啓用,NO爲不啓用,
/NOLOGO: 取消顯示啓動版權標誌
/SUBSYSTEM:指定子系統,在PC桌面程序上通常是兩個選項:console(控制檯程序)和WINDOWS(非控制檯程序)。
/out: 指定輸出的文件。
link最後的參數是須要連接的obj文件和庫文件。
cl和link的詳細用法請參考MSDN和參考文獻2《VC命令行編譯C++》。
咱們看到生成的obj文件和ConsoleTest.exe是放到當前的源碼文件夾下。通常咱們想把它放到debug文件夾下。那麼咱們該怎麼作呢?這時就能夠用到makefile中的一個經常使用部分——宏。咱們能夠這樣定義一個宏,而後建立debug文件夾,具體代碼是:
OUTDIR = .\Debug
#這裏增長了一個輸出:$(OUTDIR)
#假如不存在$(OUTDIR)文件夾,就建立它
相應地,生成的obj文件和exe文件都須要加上輸出文件的路徑,具體以下:
這裏cl工具增長了兩個選項
/Fo:指定obj文件的放置路徑
/Fd:指定pdb文件的放置路徑
這裏須要值得注意的,Windows平臺下文件反斜槓應該採用\,而不是跨平臺的/,由於我曾把OUTDIR = .\Debug寫成OUTDIR = ./Debug,結果形成if not exist不識別$(OUTDIR)而形成語法錯誤。/在windows平臺下的makefile中大多地方能夠識別,但在一些地方不能識別(例如if not exist語句),而\在任何地方都能識別的。
還有就是命令語句必須至少空出一格,而不能頂格寫。若是if not exist"$(OUTDIR)" mkdir $(OUTDIR)頂格,就會出現錯誤:
makefile.vc(5) : fatal error U1034: 語法錯誤 : 缺乏分隔符
Stop.
除開命令語句,其它語句都應該頂格寫。
咱們繼續完善這個makefile。咱們想增長一個清理輸出文件的指令,就是經常使用的clean指令。咱們能夠在描述塊all後面加一個描述塊:clean,clean描述塊的代碼以下:
若是makefile文件中不存在clean這個描述塊,而你運行下面的命令:
nmake /f makefile.vc clean
會出現下面的錯誤提示:
NMAKE : fatal error U1052: 未找到文件「clean」
Stop.
咱們繼續完善這個makefile。由於如今只能編譯debug版本,咱們想用戶能指定編譯debug版本或release版本,用戶只須要輸入「debug」或「release」來指定。咱們想到能夠設定一個宏標記來指定,當用戶輸入正確時就編譯相應的版本,錯誤時就提示使用方法。同時咱們想到前面提到nmake工具的命令行用法是:
其中macrodefs就是容許咱們定義一些自定義宏來控制編譯輸出的。此次咱們能夠定義兩個宏debug和release。具體再也不詳述,下面列出代碼:
該makefile的用法是: