Windows平臺下Makefile學習筆記 一
作者:朱金燦
決心學習Makefile,一方面是為了解決編譯開原始碼時需要跨編譯平臺的問題(發現一些開原始碼已經在使用VS2010開發,但我還沒安裝VS2010,我想在VS2008下編譯這些程式碼);另一方面原始碼在伺服器端編譯的話,使用IDE的方式編譯還是不太方便。
本文主要分為三部分:第一部分講述namke工具使用makefile的用法;第二部分講述makefile的主要語法;第三部分講述自己動手實踐學習寫makefile檔案。第四部分是編寫一個工具將vc工程檔案轉化為Makefile檔案。
首先要清楚的是在VS環境下使用Makefile的工具是nmake。因此我們需要弄明白nmake的使用Makefile檔案常用命名行用法。nmake使用Makefile檔案常用命名行用法是:
namke /f makefile /x stderrfile [macrodefs] [targets]
其中makefile為makefile檔案,/x stderrfile為可選引數,即把namke錯誤儲存到檔案stderrfile。
接著介紹makefile的主要語法。makefile的註釋以#開頭,如:
# this is my first makefile
Makefile的一個重要組成部分是巨集。Makefile中的巨集和C語言的中巨集類似,其實質就是字串替換。其語法很簡單,如下:
macro name = macro value
直譯就是巨集名 = 巨集的值
VS預定義了很多巨集,如OUTDIR,你可以在你的Makefile重新定義這些巨集以覆蓋原來的值。
巨集可以使用環境變數,如你的系統有一個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函式中新增幾句簡單程式碼(這個用於判斷我們生成的程式是否成功),具體如下:
int _tmain(int argc, _TCHAR* argv[]){ printf("Hello World! \n"); getchar(); return 0;}
然後我們在ConsoleTest資料夾下新建一個makefile.vc。我們開始正式編寫一個makefile檔案了。這時我們的大腦可能會一片空白,雖然你學了很多makefile語法,但邁出第一步依然是困難,這是正常的反應。好吧,讓我們一步步來吧。首先要告訴你makefile的一個基本原則:以終為始,這個似乎和我們平時進行的程序式程式設計的原則相悖。所謂以終為始,就是你通過makefile檔案首先告訴編譯器這個工程是想生成一個exe還是一個dll還是一個靜態庫。然後告訴編譯器要生成這個exe之類需要生成哪些obj檔案。在這個例子中,我們要生成一個exe,所以我們在makefile檔案的第一行就是:
all:ConsoleTest.exe
接下來就是編譯器的一般生成過程:編譯加連結命令,具體是:
# compilestdafx.obj: stdafx.cpp cl -c -D_X86=1 -DWIN32 -D_DEBUG -D_CONSOLE -Istdafx.h stdafx.cpp ConsoleTest.obj: ConsoleTest.cpp stdafx.obj cl -c -D_X86=1 -DWIN32 -D_DEBUG -D_CONSOLE -Istdafx.h ConsoleTest.cpp# linkConsoleTest.exe: ConsoleTest.objlink /INCREMENTAL:YES /NOLOGO /subsystem:console /out:ConsoleTest.exe ConsoleTest.obj kernel32.lib
其中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)
all: $(OUTDIR) $(OUTDIR)\ConsoleTest.exe
#假如不存在$(OUTDIR)資料夾,就建立它
$(OUTDIR) :if not exist "$(OUTDIR)" mkdir $(OUTDIR)
相應地,生成的obj檔案和exe檔案都需要加上輸出檔案的路徑,具體如下:
# compile$(OUTDIR)\stdafx.obj: stdafx.cpp cl -c -D_X86=1 -DWIN32 -D_DEBUG -D_CONSOLE -Istdafx.h /Fo"$(OUTDIR)\\" /Fd"$(OUTDIR)\\" stdafx.cpp $(OUTDIR)/ConsoleTest.obj: ConsoleTest.cpp $(OUTDIR)\stdafx.obj cl -c -D_X86=1 -DWIN32 -D_DEBUG -D_CONSOLE -Istdafx.h /Fo"$(OUTDIR)\\" /Fd"$(OUTDIR)\\" ConsoleTest.cpp# link$(OUTDIR)\ConsoleTest.exe: $(OUTDIR)\ConsoleTest.obj link /INCREMENTAL:YES /NOLOGO /subsystem:console /out:$(OUTDIR)\ConsoleTest.exe $(OUTDIR)\ConsoleTest.obj kernel32.lib
這裡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描述塊的程式碼如下:
clean: if exist $(OUTDIR) del $(OUTDIR)\*.ilk if exist $(OUTDIR) del $(OUTDIR)\*.obj if exist $(OUTDIR) del $(OUTDIR)\*.exe
如果makefile檔案中不存在clean這個描述塊,而你執行下面的命令:
nmake /f makefile.vc clean
會出現下面的錯誤提示:
NMAKE : fatal error U1052: 未找到檔案“clean”
Stop.
我們繼續完善這個makefile。因為現在只能編譯debug版本,我們想使用者能指定編譯debug版本或release版本,使用者只需要輸入“debug”或“release”來指定。我們想到可以設定一個巨集標記來指定,當用戶輸入正確時就編譯相應的版本,錯誤時就提示使用方法。同時我們想到前面提到nmake工具的命令列用法是:
namke /f makefile /x stderrfile [macrodefs] [targets]
其中macrodefs就是允許我們定義一些自定義巨集來控制編譯輸出的。這次我們可以定義兩個巨集debug和release。具體不再詳述,下面列出程式碼:
#設定編譯標記,初始化為FALSECFGSET = FALSE#定義debug版本的前處理器CCDEBUG = -DWIN32 -D_DEBUG -D_CONSOLE#定義release版本的前處理器CCNODBG = -DWIN32 -D_NDEBUG -D_CONSOLE!IFDEF debugCC = $(CCDEBUG)OUTDIR = .\DebugCFGSET = TRUE!ELSE IFDEF releaseCC = $(CCNODBG)OUTDIR = .\ReleaseCFGSET = TRUE!ENDIF# 提示用法#!IF "$(CFGSET)"== "FALSE"!MESSAGE Usage: nmake /f Makefile.vc [<config>] [<target>] !MESSAGE!MESSAGE where <config> is one of:!MESSAGE - release=1 - build release version!MESSAGE - debug=1 - build debug version!MESSAGE!MESSAGE <target> may be:!MESSAGE - clean - clear output file!MESSAGE!MESSAGE!ERROR please choose a valid configuration instead"!ENDIF#這裡增加了一個輸出:$(OUTDIR)all: $(OUTDIR) $(OUTDIR)\ConsoleTest.exe#假如不存在$(OUTDIR)資料夾,就建立它$(OUTDIR) : if not exist "$(OUTDIR)" mkdir $(OUTDIR) clean: if exist $(OUTDIR) del $(OUTDIR)\*.ilk if exist $(OUTDIR) del $(OUTDIR)\*.obj if exist $(OUTDIR) del $(OUTDIR)\*.exe # compile$(OUTDIR)\stdafx.obj: stdafx.cpp cl -c $(CC) -Istdafx.h /Fo"$(OUTDIR)\\" /Fd"$(OUTDIR)\\" stdafx.cpp $(OUTDIR)\ConsoleTest.obj: ConsoleTest.cpp $(OUTDIR)\stdafx.obj cl -c $(CC) -Istdafx.h /Fo"$(OUTDIR)\\" /Fd"$(OUTDIR)\\" ConsoleTest.cpp# link$(OUTDIR)\ConsoleTest.exe: $(OUTDIR)\ConsoleTest.obj link /machine:x86 /INCREMENTAL:YES /NOLOGO /subsystem:console /out:$(OUTDIR)\ConsoleTest.exe $(OUTDIR)\ConsoleTest.obj kernel32.lib
該makefile的用法是:
#編譯debug版本nmake /f makefile.vc debug=1#編譯release版本nmake /f makefile.vc release=1#清除debug版本nmake /f makefile.vc debug=1 clean#清除release版本nmake /f makefile.vc release=1 clean
參考文獻:
1. MSDN 2008,Microsoft Corporation
2. VC命令列編譯C++
3. 精通Windows API,範文慶、周彬彬、安靖編著
如果你覺得我的部落格對你有幫助,請在下面網址中部落格之星評選活動投我一票:http://vote.blog.csdn.net/item/blogstar/clever101(單擊候選人介紹下面的投他一票那個按鈕)參與投票有機會獲獎: 最佳貢獻獎:通過微博分享活動就有機會獲得30元充值卡一張(每週抽選5名) 幸運獎:凡參與投票使用者就有機會獲得精美小禮品一份。(每週抽選5名) 積極參與獎:所有參與投票並符合條件的使用者均可獲得20個下載積分。