make工具最主要也是最基本的功能就是通過描述檔案來描述來源程式之間的相互關係並自動維護編譯工作,而描述檔案需要按照某種文法進行編寫,檔案中需要說明如何編譯各個源檔案並連結產生可執行檔,並要求定義源檔案之間的依賴關係,為了更方便使用,檔案中同時可以用一些宏定義。描述檔案一般需要包含以下內容:
● 注釋
● 宏定義
● 顯式規則
● 隱含規則
前些天一直在編譯創維的機頂盒,開始接觸到nmake,需要搭建一個最小的編譯系統,然後各種尋找資料,試圖學習一下基礎的nmake的使用,首先說一下自己對nmake的理解,nmake不同與gnumake 也就是Linux下的makefile,雖然nmake也是管理項目代碼的工具,但是nmake更多的與一些exe工具和dat批次檔打交道,最後需要額外強調的是nmake與Microsoft Visual
Studio有著非常的致命的重要聯絡,這個你的源碼無法編譯成功可能就與此有關,需要補充的是,nmake的輸出資訊很容易迷惑人,有時沒有報錯的編譯並不代表你編譯是正確的!
下面先看一些簡單的例子和基礎知識:
在這裡,首先為有關test.exe的例子寫出一個描述檔案,再逐步介紹各部分的書寫文法。為了方便使用,一般都把描述檔案的檔案名稱取為預設檔案名稱:makefile。這個例子的makefile檔案如下(注意前面括弧裡的是行號,不是檔案的真正內容):
(001) # nmake工具的描述檔案例子
(002) EXE = Test.exe #指定輸出檔案
(003) OBJS = x.obj \
(004) y.obj #需要的目標檔案
(005) RES = x.res #需要的資源檔
(006)
(007) LINK_FLAG = /subsystem:windows #連結選項
(008) ML_FLAG = /c /coff #編譯選項
(009)
(010) #定義依賴關係和執行命令
(011) $(EXE): $(OBJS) $(RES)
(012) Link $(LINK_FLAG) /out:$(EXE) $(OBJS) $(RES)
(013) $(OBJS): Common.inc
(014) y.obj: y.inc
(015)
(016) #定義彙編編譯和資源編譯的預設規則
(017) .asm.obj:
(018) ml $(ML_FLAG) $<
(019) .rc.res:
(020) rc $<
(021)
(022) #清除臨時檔案
(023) clean:
(024) del *.obj
(025) del *.res
1. 注釋和換行
makefile中的注釋是以#號開頭一直到行尾的字元,當nmake工具處理到這些字元的時候,它會完全忽略#號及其後面的全部字元。
當一行的內容過長的時候,可以用分行符號來繼續,makefile的分行符號是\,如例子中的第三行和第四行可以合并為:
OBJS = x.obj y.obj #需要的目標檔案
在使用分行符號的時候要注意在“\”後面不能再加上其他字元,包括注釋和空格,否則nmake檢測到“\”不在一行的最後,就不會把它當成分行符號解釋,從而出現錯誤。
2. 宏定義
makefile中允許使用簡單的宏定義指代源檔案及其相關編譯資訊,可以把宏稱為變數,在整個描述檔案中,只要符合下面文法的行就是宏定義:
變數名=變數內容
如上面例子檔案中的第2到第8行就是宏定義,在引用宏時只需在變數前加$符號,但是要注意的是,如果變數名的長度超過一個字元,在引用時就必須加圓括弧(),下面都是有效宏引用:
$(LINK_FLAG)
$(EXE)
$A
$(A)
其中最後兩個引用是完全一致的。
宏定義的使用可以使makefile的使用更靈活:首先可以使檔案便於修改,比如把第8行和第18行中ml的選項部分寫成宏定義,以後要改變編譯選項的時候,只要直接在makefile檔案頭部改變宏定義就可以了,不必閱讀修改整個makefile檔案;其次,當不止一個地方用到同一個檔案的時候,把檔案名稱定義為宏定義可以減少錯誤,增加可讀性,同時也可以便於修改;最大的好處是可以直接在命令列中用新的宏定義覆蓋,比如在命令列中鍵入:
nmake ML_FLAG="/c /coff /Fl"
那麼這時就會以新的/c /coff /Fl定義代替makefile中定義的/c /coff,在這種使用中要注意兩個問題:一是宏名稱要區分大小寫,ML_FLAG和ml_flag是不一樣的;二是定義值中有空格的時候要用雙引號引起來(沒有空格時可以不用雙引號,如ML_FLAG=/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命令。規則可以用兩種方法,用方法2的時候,命令可以從第二行開始,第一行的“;”省略,但是這時命令前面必須有一個Tab字元,否則nmake無法區分這究竟是命令還是別的定義。
在同一個規則中,目標檔案可以有多個,依賴檔案也可以有多個,同時命令也可以由多個命令列組成,當然這時候就必須用第二種方法定義了,否則無法在同一行中寫入多條命令。
我們也可以用和上例中類似的方法定義其他規則,如x.obj或x.res的產生方法,但nmake如何知道哪個是最終要make的檔案呢?實際上nmake預設將整個描述檔案的第一條規則中的目標檔案認為是最終檔案,如果我們把11,12行放到第13行後面,那麼x.obj和y.obj的建立規則就成了第一條規則,nmake建立了x.obj和 x.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,而用於y.rc時就是rc y.rc了。
讀者可以注意到一些顯式規則沒有命令列,如第 13行的“$(OBJS): Common.inc”指出了所有的obj檔案都依賴於Common.inc檔案,第14行的“y.obj: y.inc”則指出了y.obj除了依賴第13行的規則外,還依賴於y.inc。但是第13行和第14行的兩條規則都沒有指出產生這些obj檔案的命令,所以nmake處理的時候會到隱含規則中去找命令列,最後會用第18行的“ml $(ML_FLAG) $<”命令去產生這些obj檔案。