Boost C++ Libraries

PrevUpHomeNext

工具和產生器

本節將說明如何延伸 Boost.Build 以支援新工具。

對於每個額外的工具,都必須建立一個稱為產生器的 Boost.Build 物件。該物件具有特定的目標類型可以接受並產生。透過使用該資訊,Boost.Build 可以自動呼叫產生器。例如,如果您宣告一個產生器可以接受類型為 D 的目標,並產生類型為 OBJ 的目標,當在資料來源清單中放置延伸模組為 .d 的檔案時,Boost.Build 會呼叫您的產生器,然後將產生的物件檔案連結到應用程式中。(當然,這需要您指定 .d 延伸模組對應到 D 類型。)

每個產生器都應該是派生自 generator 類別的類別實例。在最簡單的情況下,您不需要建立派生類別,只需建立 generator 類別的實例即可。讓我們檢閱我們在 引言 中看到的範例。

import generators ;
generators.register-standard verbatim.inline-file : VERBATIM : CPP ;
actions inline-file
{
    "./inline-file.py" $(<) $(>)
}

我們宣告一個標準產生器,指定其 ID、來源類型和目標類型。呼叫後,產生器會以類型為 VERBATIM 的來源目標作為唯一的來源,建立類型為 CPP 的目標。但實際上是用什麼指令來產生檔案?在 Boost.Build 中,動作是用指定的「動作」區塊來定義,並且在建立目標時應指定動作區塊的名稱。慣例上,產生器會使用與它們自己的 ID 相同名稱的動作區塊。因此,在上方的範例中,將使用「inline-file」動作區塊將來源轉換為目標。

產生器有兩種主要類型:標準和組成類型,它們分別使用 generators.register-standardgenerators.register-composing 規則進行註冊。例如

generators.register-standard verbatim.inline-file : VERBATIM : CPP ;
generators.register-composing mex.mex : CPP LIB : MEX ;

第一個(標準)產生器接受 單一 類型為 VERBATIM 的來源並產生一個結果。第二個(組成)產生器會接受任何數量來源,這些來源可以是 CPPLIB 類型的。組成產生器通常用於產生頂層目標類型。例如,在建構 exe 目標時,首先會呼叫一個組成產生器對應到正確的連結器。

您還應該了解兩個用於註冊產生器的特殊函式:generators.register-c-compilergenerators.register-linker。第一個函式設定 C 檔案的標頭相依性掃描,而第二個函式則處理諸如搜尋函式庫等各種複雜部分。因此,在新增對編譯器和連結器的支援時,您應該始終使用這些函式。

(需要有關 UNIX 的說明)

自訂產生器類別

標準產生器可讓您指定來源和目標型式、動作和一組旗標。如果您需要更複雜的功能,您需要用自己的邏輯建立一個新的產生器類別。然後,您必須建立該類別的一個執行個體並註冊它。以下是您如何建立自己的產生器類別的範例

class custom-generator : generator
{
    rule __init__ ( * : * )
    {
        generator.__init__ $(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ;
    }

}

generators.register
  [ new custom-generator verbatim.inline-file : VERBATIM : CPP ] ;

這個產生器將與我們在上面定義的 verbatim.inline-file 產生器完全相同,但可以透過覆寫 generator 類別的方法來自訂行為。

有兩個需要關注的方法。 run 方法負責整體程序 - 它將許多來源目標轉換成正確的型式,然後建立結果。 generated-targets 方法會在所有來源轉換成正確的型式時呼叫,以實際建立結果。

當您想將附加的屬性新增到已產生的目標或使用其他來源時,可以覆寫 generated-targets 方法。假設您有一個程式分析工具,您應該提供一個可執行檔名稱和所有來源的清單。當然,您不想要手動列出所有來源檔案。以下是 generated-targets 方法如何自動尋找來源清單

class itrace-generator : generator {
....
    rule generated-targets ( sources + : property-set : project name ? )
    {
        local leaves ;
        local temp = [ virtual-target.traverse $(sources[1]) : : include-sources ] ;
        for local t in $(temp)
        {
            if ! [ $(t).action ]
            {
                leaves += $(t) ;
            }
        }
        return [ generator.generated-targets $(sources) $(leafs)
          : $(property-set) : $(project) $(name) ] ;
    }
}
generators.register [ new itrace-generator nm.itrace : EXE : ITRACE ] ;

generated-targets 方法會用 EXE 型式的單一來源目標呼叫。呼叫 virtual-target.traverse 將回傳可執行檔依賴的所有目標,而且我們進一步尋找並非從任何事物產生出來的檔案。找到的目標會被新增到來源中。

run 方法可以覆寫,以完全自訂產生器的運作方式。特別是,可以完全自訂將來源轉換成所需型式。這裡有另一個實際範例。Boost Python 函式庫的測試通常包含兩個部分:一個 Python 程式和一個 C++ 檔案。C++ 檔案會編譯成 Python 擴充程式,該擴充程式會由 Python 程式載入。但如果這兩個檔案有相同名稱,則建立的 Python 擴充程式必須重新命名。否則,Python 程式將它自己匯入,而非匯入擴充程式。以下是它如何運作

rule run ( project name ? : property-set : sources * )
{
    local python ;
    for local s in $(sources)
    {
        if [ $(s).type ] = PY
        {
            python = $(s) ;
        }
    }
    
    local libs ;
    for local s in $(sources)
    {
        if [ type.is-derived [ $(s).type ] LIB ]
        {
            libs += $(s) ;
        }
    }

    local new-sources ;
    for local s in $(sources)
    {
        if [ type.is-derived [ $(s).type ] CPP ]
        {
            local name = [ $(s).name ] ;    # get the target's basename
            if $(name) = [ $(python).name ]
            {
                name = $(name)_ext ;        # rename the target
            }
            new-sources += [ generators.construct $(project) $(name) :
              PYTHON_EXTENSION : $(property-set) : $(s) $(libs) ] ;
        }
    }

    result = [ construct-result $(python) $(new-sources) : $(project) $(name)
                 : $(property-set) ] ;
}

首先,我們將所有來源分離成 python 檔案、函式庫和 C++ 來源。對於每個 C++ 來源,我們透過呼叫 generators.construct 並傳遞 C++ 來源和函式庫來建立一個獨立的 Python 擴充程式。此時,我們也會在必要時變更擴充程式的名稱。


PrevUpHomeNext