作者:朱金燦
來源:http://blog.csdn.net/clever101
決心學習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
接下來就是編譯器的一般生成過程:編譯加鏈接命令,具體是:
- # compile
- stdafx.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
- # link
- ConsoleTest.exe: ConsoleTest.obj
- link /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。具體不再詳述,下面列出代碼:
- #設置編譯標記,初始化為FALSE
- CFGSET = FALSE
- #定義debug版本的預處理器
- CCDEBUG = -DWIN32 -D_DEBUG -D_CONSOLE
- #定義release版本的預處理器
- CCNODBG = -DWIN32 -D_NDEBUG -D_CONSOLE
- !IFDEF debug
- CC = $(CCDEBUG)
- OUTDIR = .\Debug
- CFGSET = TRUE
- !ELSE IFDEF release
- CC = $(CCNODBG)
- OUTDIR = .\Release
- CFGSET = 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,范文慶、周彬彬、安靖編著