本节将介绍如何扩展 Boost.Build 以支持新的工具。
对于每个附加的工具,必须创建一个名为 generator 的 Boost.Build 对象。该对象接受并生成特定类型的目标。利用这些信息,Boost.Build 可以自动调用生成器。例如,如果您声明一个接受类型为 D
的目标并生成类型为 OBJ
的目标的生成器,那么在源代码列表中放置一个扩展名为 .d
的文件将导致 Boost.Build 调用您的生成器,然后将生成的 obj 文件链接到应用程序中。(当然,这需要您指定 .d
扩展名对应于 D
类型。)
每个生成器都应该是从 generator
类派生的类的实例。在最简单的情况下,您不需要创建派生类,只需创建 generator
类的实例即可。让我们回顾一下我们在 介绍 中看到的示例。
import generators ; generators.register-standard verbatim.inline-file : VERBATIM : CPP ; actions inline-file { "./inline-file.py" $(<) $(>) }
我们声明了一个标准生成器,指定了它的 id、源类型和目标类型。调用时,生成器将创建一个类型为 CPP
的目标,其类型为 VERBATIM
的源目标作为唯一的源。但实际用于生成文件的命令是什么?在 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
函数在将所有源转换为正确的类型以实际创建结果时调用。
当您想要为生成的 target 添加其他属性或使用其他源时,可以覆盖 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 扩展。此时,我们也会根据需要更改扩展的名称。