![]() |
本節將說明如何延伸 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-standard
和 generators.register-composing
規則進行註冊。例如
generators.register-standard verbatim.inline-file : VERBATIM : CPP ; generators.register-composing mex.mex : CPP LIB : MEX ;
第一個(標準)產生器接受 單一 類型為 VERBATIM
的來源並產生一個結果。第二個(組成)產生器會接受任何數量來源,這些來源可以是 CPP
或 LIB
類型的。組成產生器通常用於產生頂層目標類型。例如,在建構 exe
目標時,首先會呼叫一個組成產生器對應到正確的連結器。
您還應該了解兩個用於註冊產生器的特殊函式:generators.register-c-compiler
和 generators.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 擴充程式。此時,我們也會在必要時變更擴充程式的名稱。