Windows 下的 Makefile 編寫


Windows 下的 Makefile 編寫(一)Makefile的基本規則

作者:cntrump

    Makefile對於很多人來說是陌生的,特別是習慣於使用 IDE 的人來說,似乎沒有聽說過 Makefile ,因為Makefile 的工作都由IDE代勞了。但是Makefile 的地位是不可忽略的,從VC誕生到現在Nmake這個實用程序就一直伴隨着VC編譯器一起發行。

    很多大的工程都是基於Makefile編譯和維護的,對於開源項目來說,大多數都使用Makefile進行編譯,使用IDE來編譯大型工程是不可想象的。

    Makefile是什么?它是一個文本文件,里面記錄着項目由哪些目標構成,以及各個目標的生成方式等信息,Makefile的核心任務是定義一系列的規則,然后由Nmake來解釋執行,任何一個文本編輯器都可以用來編寫Makefile。

    先來大概看一下Makefile的基本規則:

代碼:
    Target:dependent;command

       Command

       ........

Target 是目標,目標可以是一個文件,也可以是一個標簽,如果Target用作標簽,則稱之為偽目標。Makefile至少要有一個目標。

Dependent是依賴項目,指明目標所依賴的具體項目。依賴項目和目標之間用 : 號分隔。

Command是命令,如果命令和依賴項目在同一行,則需要使用;號與之相隔,各個命令之間使用空格或Tab鍵分開,如果命令是單獨一行,則需要使用Tab縮進。Command命令由Nmake來執行。

    上述內容簡單地表明了一個依賴關系,生成Target目標依賴dependent中指定的文件,而生成的規則由Command來定義,Nmake負責執行這些命令。

    默認情況下,Nmake會查找當前目錄下任何名稱為Makefile的文件(名稱不區分大小寫,並且沒有后綴),如果你的Makefile文件名稱是其他的,則需要使用 f 參數指定。

    以上就是Makefile的核心內容,任何系統的Makefile都是這樣執行的。但是要寫好一個Makefile,僅僅這些還不夠。

    對於一個新知識,我更喜歡從做中學。下面舉一個例子來說明上面的規則在實際應用中如何操作:

代碼:
    Test.exe:main.obj

       Link.exe main.obj /out:Test.exe

    main.obj:main.cpp

    #僅編譯文件

       cl.exe main.cpp 

     在Makefile中注釋使用 # 號開頭,且僅有這一種注釋方式。它的作用和C++ 語法中的 // 注釋是一樣的。所不同的是 # 號必須放在行首。

上面的Makefile 文件指定了兩個目標,分別是Test.exe 和 main.obj,生成Test.exe需要依賴 main.obj文件,而生成 main.obj文件依賴main.cpp。在目標下方指明了生成該目標方法。

    main.cpp 的內容如下:

代碼:
#include <stdio.h>

int main()

{

  printf("Hello Makefile!\n");

  return 0;

}

 
     將Makefile和main.cpp放置於同一目錄下,在VC的命令提示符窗口中執行 nmake命令,就會自動生成Test.exe和 main.obj兩個文件。再運行生成后的Test.exe測試一下:


    在Makefile中定義了兩個目標,Nmake默認只生成Makefile中的第一個目標,由於main.obj是Test.exe 的依賴項,所以main.obj目標也得以執行。

    是不是每執行一次namke命令就會重新生成一次目標文件呢?答案:不是。每次都重新生成目標顯然是一種資源浪費,nmake是根據時間戳來決定是否需要重新生成目標。只有在依賴文件不存在或者依賴文件時間高於目標文件時,nmake才會生成目標。

    前面的例子已經生成了目標文件,如果修改了main.cpp中的代碼或者刪除了main.obj,都將會重新生成Test.exe。

    一般來說,為了使清理中間文件或重新生成目標更加方便,都會在Makefile中加入一個偽目標來清理生成的中間文件。以刪除main.obj為例,Makefile修改如下:

代碼:
Test.exe:main.obj

    Link.exe main.obj /out:Test.exe

Main.obj:main.cpp

# 僅編譯文件

    cl.exe /c main.cpp

clean:

    @del *.obj

    @echo Project has clean.

 
clean是一個偽目標,只是作為標簽使用。clean下的指令是命令行下的刪除文件命令和顯示一個字符串。在命令前加 @ 符號是為了不顯示命令本身。

默認情況下nmake只會生成第一個Test.exe目標,不會執行到clean目標,如果要指定生成目標,需要顯式指定目標名稱:

代碼:
nmake clean

就指定要執行Makefile中的clean偽目標。執行之后會刪除生成的main.obj並顯示一行文字:


還可以指定多個目標,nmake會從左往右依次生成目標。所以如果要在清除中間之后立即生成Test.exe,可以這么做:

    nmake clean Test.exe


一般來說,總是把最終生成的目標放在最前,而把清理中間文件的偽目標放到最后。下一回,我會結合實際的例子再介紹Makefile的其他內容。

上傳的附件   Windows 下的 Makefile 編寫(一)Makefile的基本規則.pdf

Windows 下的 Makefile 編寫(二)宏和預處理的簡單示例

作者:cntrump

在Makefile中使用宏和預處理能明顯提高工作的效率。



    宏的語法為:

代碼:
macroname=string
   

macroname 是字母、數字和下划線 (_) 的組合,最多 1,024 個字符且區分大小寫。macroname 可以包含調用的宏。如果 macroname 完全是由調用的宏組成的,則正調用的宏不能為空或未定義。

    宏的使用:

    定義了一個宏之后就可以使用了。使用的方法很簡單,如下所示就是一個簡單的調用過程:

代碼:
$(macroname)
 
使用括號將宏名稱括起來,再在前面加上 $ 符號就可以了。在實際中的使用:

代碼:
objects=stdafx.obj main.obj

Test.exe:$(objects)

    .......
 
如果string的長度太長或者需要分行顯示。可以使用 \ 。反斜框后緊跟着回車就表示換行:

代碼:
objects=stdafx.obj \

         main.obj

Test.exe:$(objects)
    nmake還內置了用於指定文件名的宏,叫作 文件名宏

文件名宏被預定義為依賴項中指定的文件名(而不是磁盤上的完整文件名指定)。在調用時不需要將這些宏括在括號內;只需按如下方式指定 $。


 意義
 
$@
 當前所指定的當前目標的全名(路徑、基名稱、擴展名)。
 
$$@
 當前所指定的當前目標的全名(路徑、基名稱、擴展名)。僅在作為依賴項中的依賴項時有效。
 
$*
 當前目標的路徑和基名稱,沒有文件擴展名。
 
$**
 當前目標的所有依賴項。
 
$?
 時間戳比當前目標的時間戳晚的所有依賴項。
 
$<
 時間戳比當前目標的時間戳晚的依賴文件。僅在推理規則的命令中有效。
 

使用文件名宏對編寫Makefile是很有幫助的,特別是在文件數量多的時候,可以節省大量時間。例如上面的例子,使用的文件名宏后:

代碼:
objects=stdafx.obj \

         main.obj

Test.exe:$(objects)

    link.exe $**
 
這樣只需使用 $** 就替代了Test.exe所依賴的所有.obj文件,相當方便。

生成文件預處理

預處理指令不區分大小寫。初始感嘆號 (!) 必須出現在行首。感嘆號后面可以有零個或多個空格或制表符,用於縮進。

下面是經常會用到的預處理:

!MESSAGE text

      用來顯示一段文本信息,顯示的內容就是text所指定的內容。

!INCLUDE [<]filename[>]

      作用類似於C++ 中的 #include ,將filename包含進來,如果filename里的指令可執行則會先執行其中的指令然后再繼續。

!IF constantexpression 

如果 constantexpression 計算結果為非零值,則處理 !IF 和下一個 !ELSE 或 !ENDIF 之間的語句。 

!IFDEF macroname 

如果定義了 macroname,則處理 !IFDEF 和下一個 !ELSE 或 !ENDIF 之間的語句。空宏將被視為尚待定義。 

!IFNDEF macroname 

如果沒有定義 macroname,則處理 !IFNDEF 和下一個 !ELSE 或 !ENDIF 之間的語句。 

!ELSE[IF constantexpression | IFDEF macroname | IFNDEF macroname]

如果前面的 !IF、!IFDEF 或 !IFNDEF 語句計算結果為零值,則處理 !ELSE 和下一個 !ENDIF 之間的語句。可選關鍵字提供了進一步的預處理控制。 

!ELSEIF 

!ELSE IF 的同義詞。 

!ELSEIFDEF 

!ELSE IFDEF 的同義詞。 

!ELSEIFNDEF 

!ELSE IFNDEF 的同義詞。 

!ENDIF 

標記 !IF、!IFDEF 或 !IFNDEF 塊的結尾。同一行上 !ENDIF 后面的所有文本被忽略。 

!UNDEF macroname 

取消定義 macroname。

預處理數量雖然不少,但是很多都有其同義預處理。只需要記憶其中一個就可以了。

最后用一個簡單的示例來說明宏和預處理的應用,附件中的例子是使用VC6生成的一個Hello World控制台程序,及其相應的Makefile編寫方法。

在VC6的命令提示符下,生成Release版的命令為:

代碼:
nmake clean cfg=Release
生成Debug版的命令為:

代碼:
    nmake clean cfg=Debug
現在,你已經具有編寫簡單Makefile的能力了。

btw:
PDF文件中還有附件,里面是例子代碼。
上傳的附件   Windows 下的 Makefile 編寫(二)宏和預處理的簡單示例.pdf

Windows 下的 Makefile 編寫(三)推理規則

作者:cntrump

推理規則是Makefile中自動化的核心功能之一,掌握了推理規則會讓Makefile的編寫更簡單和更易維護。

推理規則

推理規則提供命令來更新目標並推理目標的依賴項。推理規則是用戶自定義的或者是預定義的,預定義的推理規則可以被重新定義。

定義規則

代碼:
.fromExt.toExt:

    commands
 
fromExt是依賴文件的擴展名,toExt是目標文件的擴展名。在依賴文件擴展名前面的 . 號必須被放在行首。

代碼:
All:main.obj func.obj

    link $**

main.obj:main.cpp

    cl /c main.cpp

func.obj:func.cpp

    cl /c func.cpp
在應用了推理規則之后,可以簡單地寫為

代碼:
All:main.obj func.obj

    link $**

.cpp.obj:

    cl /c $<
在命令行下的輸出結果如下:
代碼:
F:\我的文章\hello>nmake

Microsoft (R) Program Maintenance Utility   Version 6.00.9782.0

Copyright (C) Microsoft Corp 1988-1998. All rights reserved.

 

        cl /c main.cpp

Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8804 for 80x86

Copyright (C) Microsoft Corp 1984-1998. All rights reserved.

 

main.cpp

        cl /c func.cpp

Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8804 for 80x86

Copyright (C) Microsoft Corp 1984-1998. All rights reserved.

 

func.cpp

        link main.obj func.obj

Microsoft (R) Incremental Linker Version 6.00.8447

Copyright (C) Microsoft Corp 1992-1998. All rights reserved.
    由此可以看出來,推理規則已經從 .obj 目標文件自動推導出相應的 .cpp 文件,並且為每一個目標執行一次編譯命令。

    上述的推理規則叫做標准推理規則,標准推理規則會被調用多次。在文件數量很多的時候,這樣顯然會降低速度,為此在1.62及以后的版本中,還定義了批模式規則。

    當 N 條命令通過推理規則時,批模式推理規則只調用一次該推理規則。如果沒有批模式推理規則,將需要調用 N 條命令。N 是觸發推理規則的依賴項的數目。

由於較低版本的nmake不支持批模式推理規則,所以在使用批模式規則時最好先檢查一下當前nmake的版本:

_NMAKE_VER 宏可以返回當前nmake的文件版本,返回的是一個字符串文本。

代碼:
!message $(_NMAKE_VER)
在VC6 SP6下的輸出結果為:

代碼:
6.00.9782.0
批模式和標准模式唯一的差異就是在目標后多加上一個冒號:

代碼:
All:main.obj func.obj

    link $**

.cpp.obj::

    cl /c $<
執行后的輸出結果如下:
代碼:
        cl /c main.cpp func.cpp

Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8804 for 80x86

Copyright (C) Microsoft Corp 1984-1998. All rights reserved.

 

main.cpp

func.cpp

Generating Code...

        link main.obj func.obj

Microsoft (R) Incremental Linker Version 6.00.8447

Copyright (C) Microsoft Corp 1992-1998. All rights reserved.
 
不同的地方我標識了紅色,可以看到編譯命令只執行一次,並且一次編譯多個文件。這樣cl.exe 在編譯期間只會被調用一次,相對於多次調用速度要快。

需要注意的一個地方是:由於一次處理多個文件,這就得要求執行命令的程序本身支持處理多個文件,假如cl.exe 一次只能處理單個源文件,那么使用批模式肯定會導致失敗。

在上面的例子中,目標文件和依賴文件都沒有指定路徑,所以默認使用當前目錄。如果目標文件或者依賴文件不在當前目錄下,就需要為其指定搜索路徑:

代碼:
{fromDir}.fromExt{toDir}.toExt:

    command
    fromDir是依賴文件所在目錄,toDir是依賴文件所在目錄。目錄可以使用宏來表示。大括號是必須的,且每個擴展名只能指定一個目錄路徑,{}(空的大括號)表示當前目錄。

例如,.obj文件被編譯輸出到Debug目錄,源文件在當前目錄下:

代碼:
outdir=.\Debug

objects=$(outdir)\main.obj \

         $(outdir)\func.obj

All:$(objects)

    link $**

.cpp{$(outdir)}.obj::

    cl /Fo"$(outdir)/" /c $<
在執行命令之前 Debug目錄必須存在,否則編譯命令將失敗。命令成功執行后,會在Debug目錄下生成 .obj文件,最終的可執行文件生成在當前目錄下。

為了能更好的理解上面的推理規則,可以將上面的宏進行展開:

代碼:
All:.\Debug\main.obj .\Debug\func.obj

    link $**

.cpp{.\Debug}.obj::

    cl /Fo".\Debug/" /c $<
    最后需要說明的是,在批模式下,必須使用 $< 宏來指定依賴文件項目。

    如果你理解了宏,預處理和推理規則,那么已經可以閱讀大多數Windows下開源項目的Makefile文件了,並且已經可以自己寫出實用的Makefile。

    剩下的就是多加練習,試着為IDE向導生成的項目編寫Makefile,多嘗試幾次之后你會發現原來Makefile很簡單。
上傳的附件   Windows 下的 Makefile 編寫(三)推理規則.pdf
from: http://www.pediy.com/kssd/pediy11/126998.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM