Boost C++ Libraries

PrevUpHomeNext

工具和生成器

本节将介绍如何扩展 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-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 函数在将所有源转换为正确的类型以实际创建结果时调用。

当您想要为生成的 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 扩展。此时,我们也会根据需要更改扩展的名称。


PrevUpHomeNext