make工具可以看成是一個智能的批處理工具,它本身并沒有編譯的鏈接的功能,同樣是用類似于批處理的方式,通過調用用戶指定的語句來進行編譯和鏈接。但是,批處理會執行全部命令將全部源文件編譯,包括那些不必重新編譯的源文件,而make工具則可根據目標文件上一次編譯的時間和所依賴的源文件的更新時間自動判斷應當編譯哪些源文件,對沒有更新過的文件不會處理,這樣就可以大大提高程序調試的效率。
舉例說明,我們要寫一個test.exe文件,生成最后的可執行文件有4個步驟:
1,編譯源文件x.cpp,其中用到頭文件common.h,它們經編譯器編譯成x.obj;
2,編譯源文件y.cpp,其中用到頭文件common.h和y.h,它們經編譯器編譯成y.obj;
3,資源腳本文件x.rc,經過資源編譯器編譯成x.res;
4,最后用鏈接器將x.obj,y.obj和x.res鏈接成test.exe。
可以看出,當程序調試的時候,如果修改了x.cpp,也就是說x.obj的文件時間比x.cpp要早,就需要重新進行步驟1和4;如果修改了y.cpp或y.h,那么需要重新執行步驟2和4;如果修改的是x.rc,則步驟3和4必須重新執行;如果同時修改了common.h和x.rc,那么必須重復全部步驟。在這個例子中,文件的依賴關系就是:
1,test.exe依賴于x.obj,y.obj和x.res;
2,x.res依賴于x.rc;
3,x.obj依賴于x.cpp和common.h;
4,y.obj依賴于y.cpp,common.h和y.h。
make可以根據文件的時間正確判斷文件的新舊并執行相應的步驟。但make又是如何知道文件之間的依賴關系呢?這需要用戶用一個描述文件來指定。這個描述文件就叫做makefile,執行make工具的時候,它會默認用makefile做描述文件名來進行相應的工作,書寫描述文件有規定的語法,雖然語法不是很簡單,但寫好以后就省事多了。
Microsoft的make工具文件名為nmake.exe,Borland公司的make工具文件名是make.exe。
兩者默認的描述文件名都是makefile,描述文件的語法也大同小異,只是使用時命令行參數有些不同。
nmake的用法
在命令行鍵入namke/? 可以顯示幫助信息,nmake的語法為:
nmake [選項] [/f 描述文件名] [/x 輸出信息文件名] [宏定義] [目標]
說明如下:
/f :如果描述文件名不使用默認的“makefile”,可以用/f參數指定。
/x :如果想把屏幕輸出的信息存到一個文件中,可以用/x 參數指定。(用DOS下的管道操作符 nmake > 文件名的方法無效)。
宏定義:可以用新的定義覆蓋描述文件中的宏定義。
目標:可以指定建立描述文件中描述的某個文件,如上面的例子中默認是生成最后的test.exe文件,也可以用nmake x.res指定更新x.res文件。
nmake的常用選項:
/A 不檢測文件更新時間,強制更新所有文件
/B 文件更新時間相等時也要更新文件
/D make時顯示文件新舊信息
/N 顯示make時要執行的命令,但并不真正執行
/P 一個比較有用的選擇,make時顯示詳細的信息
由于nmake的應用是基于文件時間的,當計算機的時鐘不準確或文件拷貝到另一臺計算機后文件時間有些偏差,那么可能文件的更新會不正確,這時最好用/A選項強制把所有文件更新一遍。在平時使用的時候,以makefile當做建立的描述文件名,那么僅鍵入不加參數的nmake命令就可以完成所有工件了。
描述文件的語法
make工具最主要也是最基本的功能就是通過描述文件來描述源程序之間的相互關系并自動維護編譯工作,而描述文件需要按照某種語法進行編寫,文件中需要說明如何編譯各個源文件并鏈接生成可執行文件,并要求定義源文件之間的依賴關系,為了更方便使用,文件中同時可以用一些宏定義。描述文件一般需要包含以下內容:
l 注釋
l 宏定義
l 顯式規則
l 隱含規則
在這里,以上面的test.exe的例子寫出一個描述文件,再逐步介紹各部分的書寫語法。為了方便使用,一般都把描述文件的文件名取為默認文件名:makefile(無擴展名)。這個例子的makefile文件如下:
1 # nmake工具的描述文件例子
2 EXE = Test.exe #指定輸出文件
3 OBJS = x.obj \
4 y.obj #需要的目標文件
5 RES = x.res #需要的資源文件
6
7 LINK_FLAG = /subsystem:windows #鏈接選項
8 ML_FLAG = /c /coff #編譯選項
9
10 #定義依賴關系和執行命令
11 $(EXE) : $(OBJS) $(RES)
12 Link $(LINK_FLAG) /out:$(EXE) $(OBJS) $(RES)
13 $(OBJS) : Common.inc
14 y.obj: y.inc
15
16 #定義匯編編譯和資源編譯的默認規則
17 .asm .obj:
18 ml $(ML_FLAG) $<
19 .rc .res:
20 rc $<
21
22 #清除臨時文件
23 clean:
24 del *.obj
25 del *.res
26
1、注釋和換行
makefile中的注釋是以#號開頭一直到行尾的字符,當nmake工具處理到這些字符的時候,它會完全忽略#號及全部注釋字符。
當一行的內容過長的時候,可以用換行符來繼續,makefile的換行符是反斜杠\。
在使用換行符的時候要注意在反斜杠后面不能再加上其他字符,包括注釋和空格,否則nmake檢測到反斜杠不在一行的最后,就不會把它當成換行符解釋,就會出現錯誤。
2、宏定義
makefile中允許使用簡單的宏定義指代源文件及其相關編譯信息,可以把宏稱為變量,在整個描述文件中,只要符合下面語法的行就是宏定義:
變量名=變量內容
如上面例子文件中的2到8行就是宏定義,在引用宏時只需在變量前加$符號,但是要注意的是,如果變量名的長度超過一個字符,在引用時就必須加圓括號(),下面都是有效的宏引用:
$(LINK_FLAG)
$(EXE)
$A
$(A)
其中最后兩個引用是完全一致的。
宏定義的使用可以使makefile的使用更靈活:首先可以使文件便于修改,比如把第8行和第18行中ml的選項部分寫成宏定義,以后要改變編譯選項的時候,只要直接在makefile文件頭部改變宏定義就可以了,不必重新閱讀整個makefile文件;其次,當不止一個地方用到同一個文件的時候,把文件名定義為宏定義可以減少錯誤,增加可讀性,同時也可以便于修改;最大的好處是可以直接在命令行中用新的定義覆蓋,比如在命令行中鍵入:
nmake ML_FLAG – “/c /coff /F1”
那么這時就會以新的/c /coff /F1定義代替makefile中定義的/c /coff,在這種使用中要注意兩個問題,一是宏名稱要區分大小寫,ML_FLAG和ml_flag是不一樣的;二是定義值中有空格的時候要用雙引號引起來,沒有空格時可以不用雙引號,如ML_LAG=/c,這使臨時使用不同的參數編譯文件時可以不必修改makefile。
3、顯式規則
makefile中包含有一些規則,這些規則定義了文件之間的依賴關系和產生命令,一個規則的格式是這樣的:
目標文件:依賴文件;命令 (方法1)
或
目標文件:依賴文件
命令 (方法2)
在規則定義和命令行中,不能包含注釋,例子中的第11行和12行把宏定義展開后就是:
Test.exe : x.obj y.obj x.res
Link /subsystem:windows /out: Test.exe x.obj y.obj x.res
這里的目標文件就是Test.exe,它依賴于3個文件x.obj,y.obj和x.res,如果有必要,產生目標文件的命令就是下面的Link命令,整個規則可以用兩種方法,用第二種方法的時候,命令可以從第二行開始,第二行的分號“;”省略,但是這時命令前面必須有一個Tab字符,否則nmake無法區分這究竟是命令還是別的定義。目標文件可以有多個,依賴文件也可以有多個,同時命令也可以由多個命令行組成,當然這時候就必須用第二種方法定義了。
可以也可以用test.exe生成的規則定義其他文件,如x.obj或x.res的生成方法,但nmake如何知道哪個是最終要make的文件呢?實際上nmake默認將整個描述文件的第一條規則中目標文件認為是最終文件,如果我們把11,12行放到第13行后面,那x.obj和y.obj的建立規則就成了第一條規則,nmake建立了x.obj和y.obj之后就不理會test.exe的建立了,所以我們必須把最終需要生成的文件放在第一條規則定義。當然,在nmake的命令行參數中可以指定要make的目標,如我們要生成x.res文件,那么不必修改makefile將x.res的描述規則移動到最前面,而是直接在命令行鍵入以下命令即可:
nmake x.res
參數中也可以同時帶好幾個目標文件名,nmake會一一處理,如果指定的目標文件沒有對應的規則,nmake會返回一個出錯信息:
fatal error U1073: don’t know how to make ‘xxx 文件’
當用戶要求nmake去建造一個目標時,make會去找到這個目標的依賴規則,這時第二行中的命令并不會立刻就執行,而是首先要做一些事情:nmake先去檢查依賴文件是否是另一條規則的目標文件,如果是則先處理這一條規則,否則不是,nmake再檢查各個依賴文件的時間,看這些文件有沒有比目標文件更新的,如果沒有,nmake會決定不再重新建造目標文件,并給出提示:“xxx 文件”is up-to-date,如果依賴文件有比目標文件更新的,才執行命令。
所以一個順序下來,所有的目標文件以及它們的依賴文件,以信依賴文件的依賴文件都會被檢查并更新,總而言之,一個目標文件的建立包含了順序正確的指令鏈接,這個鏈接結構是樹狀的,目標文件是根,一級級擴展到多個文件,我們要求的是nmake去建立鏈接中處于根部的那個文件,nmake要根據鏈接結構從目標開始向初始狀態前進,最后慢慢回來,在這個過程中執行建立每個文件所必須的命令,一直到最終目標建立完成。
目標也可以沒有依賴文件,而目標也可以不是一個真正存在的文件,如例子第23行到第25行行中的clean是一個目標,但我們并不是要生成一個clean文件,而是希望在文件調試完畢后用nmake來清除臨時文件,當我們鍵入nmake clean的時候,工作目錄下并沒有clean這個文件,那么nmake就會去執行clean定義中的命令,因為nmake把每一個不存在的目標當做是一個過時的目標,如此一來,就會刪除中間過程中的文件*.obj和*.res。
指出了目標文件全名的規則稱為顯式規則,但有些類別的文件的編譯方法可以是雷同的,如從asm文件產生obj文件的命令總是用ml,從rc文件產生res文件的命令總是用rc,對于每個文件都寫一條規則有些多余,這時候就要用到隱含規則。
4、隱含規則
隱含規則可以為某一類的文件指定建立的命令,它具體定義了如何將帶一個特定擴展名的文件轉換成具有另一種擴展名的文件,定義的格式是:
.源擴展名 .目標擴展名 : ;命令 (方法1)
或
.源擴展名 .目標擴展名 :
命令 (方法2)
隱含規則的語法和顯式規則相似,也是用“:”隔開,在分號“;”后面書寫命令,也可以不用分號“;”而將命令寫在第二行,同理,這時命令之前要加一個Tab字符。
隱含規則不能有依賴文件,所以冒號“:”后面沒有內容,例子中的第17、18行定義了從asm文件建立obj文件的隱含規則,第19和20行定義了從rc文件建立res文件的隱含規則,隱含規則中無法指定確定的輸入文件名,因為輸入文件名是泛指的有相同擴展名的一整類文件,這時候就要用到幾個特殊的內定宏來指定文件名,這些宏是$@,$*,$?和$<,它們的含義如下:
$@ 全路徑的目標文件。
$* 除去擴展名的全路徑的目標文件。
$? 所有源文件名。
$< 源文件名(只能用在隱含規則中)。
所以第19、20行中的rc $<用于x.rc的時候就是rc x.rc。
讀者可能注意到一些顯式規則沒有命令行,如第13行的$(OBJS): Common.inc指出了所有的obj文件全部依賴于Common.inc文件,第14行的 y.obj:y.inc則指出了y.obj同時也依賴于y.inc和第13行的規則合并,y.obj依賴于Common.inc也依賴于y.inc,但是這兩個條規則都沒有指出產生這些obj文件的命令,所以nmake處理的時候會到隱含規則中去找命令行,最后會用第18行的ml $(ML_FLAG) $<命令去產生這些obj文件。