Boost.Build 教程

作者:Boris Schäling。

目录


简介
独立于编译器和平台的构建系统

Boost.Build 是一个高级构建系统,它使管理 C++ 项目变得尽可能容易。其理念是在配置文件中仅指定构建程序所需的必要信息。例如,不需要告诉 Boost.Build 如何使用特定编译器。Boost.Build 原生支持多种编译器,并知道如何使用它们。如果您创建了一个配置文件,只需告诉 Boost.Build 在哪里找到源文件、可执行文件应该叫什么以及 Boost.Build 应该使用哪个编译器即可。然后,Boost.Build 会尝试找到编译器并自动构建程序。

由于 Boost.Build 支持多种编译器,因此配置文件从不包含任何编译器特定的选项。配置文件完全独立于编译器。当然,可以设置诸如是否应优化代码之类的选项。但是,这些选项是用 Boost.Build 独有的语言编写的。一旦选择了一个编译器来构建程序,Boost.Build 会将配置文件中的选项转换为选定编译器期望的命令行选项。这使得可以编写一次配置文件,并在不同平台上使用不同编译器构建程序。

听起来虽然不错,但 Boost.Build 只能用于 C++ 和 C 项目。Boost.Build 不知道如何使用其他编译器,例如 Java 编译器。虽然 Boost.Build 是可扩展的,但对于用其他编程语言实现的程序,使用不同的构建系统更有意义。

Boost.Build 的创建是为了使用不同的编译器在不同的平台上轻松构建和安装 Boost C++ 库。虽然 Boost.Build 是 Boost C++ 库的一部分,并与它一起发布,但它可以单独用于任何 C++ 或 C 项目。您甚至可以 仅下载 Boost.Build,如果您不想使用 Boost C++ 库。

本文是对 Boost.Build 的介绍,以帮助您将其用于自己的 C++ 或 C 项目。它让您基本了解 Boost.Build 的工作原理以及如何开始使用它。阅读本文后,您不仅能够将 Boost.Build 用于自己的项目,而且还可以更容易地理解 Boost.Build 文档,因为您将了解全局概况。


构建流程
Jamfiles 和一个名为 b2 的解释器

您用于构建由 Boost.Build 管理的项目的程序称为 b2。如果您下载并构建了 Boost C++ 库,那么您已经使用过 b2 了。b2 会查找配置文件、读取它们并相应地构建项目。它还接受各种命令行选项,这些选项在例如显示 b2 用于构建项目的全部命令时很有用。

项目可能很大,可能包含许多组件,其源代码分布在许多目录中。与其为整个项目创建一个大型配置文件,不如为每个组件创建自己的配置文件。Boost.Build 也不例外:在一个大型项目中,将存在许多配置文件,这些配置文件需要由 b2 找到并解释。

对于 Boost.Build,每个包含配置文件的目录都是一个项目:如果目录中存在配置文件,那么就可以构建一些内容。对于 Boost.Build 来说,无论是子目录中的组件还是包含多个组件的软件,都没有区别。

当启动 b2 时,它不会在整个文件系统上搜索配置文件。它仅在当前工作目录中搜索配置文件。如果找不到配置文件,则不会执行任何操作。b2 如果当前工作目录中没有配置文件,则不会在任何其他目录中搜索配置文件。

b2 正在查找的配置文件称为 Jamfile.jam。扩展名为 jam 的文件称为 Jamfiles。如果 b2 在当前工作目录中找到 Jamfile,则会在父目录中搜索更多 Jamfiles。b2 会向上爬到父目录,直到找到名为 Jamroot.jam 的配置文件。Jamroot.jamJamfile.jam 没有区别。它只表明 b2 不需要再搜索了。

b2 在父目录中搜索 Jamfiles 的原因是可以对设置进行分组。如果有一些组件应该使用类似的设置进行构建,则可以将这些设置存储在父目录中的 Jamfile 中,如果构建子目录中的组件,该 Jamfile 会自动使用。

请注意,b2 必须找到名为 Jamroot.jam 的文件。如果不存在 Jamroot.jam,则为错误。如果 Jamroot.jam 位于当前工作目录中,则不需要其他文件 Jamfile.jam。如果 Jamroot.jam 位于父目录中,则当前工作目录中必须存在一个文件 Jamfile.jam - 否则 b2 不会执行任何操作。

如果您将 b2 复制到不包含任何 Jamfiles 的目录中并启动该程序,则会收到错误消息。但是,b2 不会抱怨它找不到 Jamfile。它会抱怨找不到构建系统。

Unable to load Boost.Build: could not find "boost-build.jam"
---------------------------------------------------------------
Attempted search from C:\Users\Boris\Desktop up to the root

Please consult the documentation at 'https://boost.ac.cn'.

b2 做的第一件事不是查找 Jamfile,而是加载构建系统。但构建系统究竟是什么呢?

b2 是一个解释器。它实际上不知道如何构建任何东西。b2 的作用是解释 Jamfiles。Boost.Build 的实现实际上是在 Jamfiles 中。它们包含使 Boost.Build 成为如此强大的工具的所有逻辑。由于 b2 只是执行它在 Jamfiles 中读取的内容,因此它需要知道在哪里找到 Boost.Build 构建的 Jamfiles。

当启动 b2 时,它会在当前工作目录中查找名为 boost-build.jam 的文件。如果找不到该文件,它会在所有父目录中搜索。该文件只需要包含一行,以告诉 b2 在哪里找到构建系统。

boost-build C:/boost_1_57_0/tools/build/src ; 

boost-build 后的路径必须引用包含名为 bootstrap.jam 的文件的目录。这是 b2 用于加载构建系统的文件。由于 Boost C++ 库附带 Boost.Build,因此您可以引用 Boost C++ 库根目录的 tools/build 子目录。并且您始终可以使用斜杠作为路径分隔符 - 即使您在 Windows 上。

请注意,路径和行末的分号之间必须有一个空格。如果缺少空格,则为错误。您将在本文的后面部分了解有关 Jamfiles 中使用的语法的更多信息。

如果 b2 找到 boost-build.jam,它会使用文件中的路径来加载构建系统。加载构建系统后,它还会准备自己使用构建项目所需的特定编译器、链接器以及可能的其他工具。Boost.Build 将这些程序称为工具集。如果未使用任何命令行选项来启动 b2,构建系统会尝试自动找到一个它可以使用的工具集。例如,在 Windows 上,它会搜索 Visual C++。如果检测到已安装 Visual C++,它会使用工具集 msvc。

warning: No toolsets are configured.
warning: Configuring default toolset "msvc".
warning: If the default is wrong, your build may not work correctly.
warning: Use the "toolset=xxxxx" option to override our guess.
warning: For more configuration options, please consult
warning: http://boost.org/boost-build2/doc/html/bbv2/advanced/configuration.html

如果您在未指定应使用哪个工具集的情况下启动 b2,您会看到一个警告。b2 会告诉您它检测到并决定使用的工具集。如果您想抑制警告,则必须自己指定工具集。例如,您可以通过 b2 toolset=msvc 来告诉构建系统使用 Visual C++。如果您想使用 GCC,则输入 b2 toolset=gcc

截至今天,支持超过 10 种工具集。Boost.Build 很可能可以直接与您使用的编译器一起使用。

找到并加载构建系统,并且知道使用哪个工具集后 - 无论是您指定了一个工具集还是构建系统自动检测到一个工具集 - b2 都会在当前工作目录中查找名为 Jamfile.jam 的文件。如果找不到 Jamfile,则会打印错误消息。

error: error: no Jamfile in current directory found, and no target references specified.

如果您创建一个名为 Jamfile.jam 的空文件并再次启动 b2,则会打印另一条错误消息。

error: Could not find parent for project at '.'
error: Did not find Jamfile.jam or Jamroot.jam in any parent directory.

b2 最终正在查找名为 Jamroot.jam 的 Jamfile。如果它不在当前工作目录中,b2 会期望在父目录中找到它。

如果您创建一个名为 Jamroot.jam 的空文件并启动 b2,则错误消息会消失。显然,Boost.Build 没有执行任何操作。但现在您知道了 b2 如何继续构建程序以及 Boost.Build 最小配置的示例。

请注意,如果您在一个小型项目上工作并且只需要一个配置文件,则可以简单地将其命名为 Jamroot.jam。您不需要另一个名为 Jamfile.jam 的文件。


基本任务
规则和功能

如果您查看 Jamfiles,它们的语法可能让您想起其他构建系统使用的配置文件。简单的 Jamfiles 看起来可能像普通的旧配置文件,例如,值似乎被分配给了键。但要理解的关键是 Jamfiles 实际上是脚本文件。有一个编程语言用于编写 Jamfiles。b2 不是 Boost.Build 的核心组件,它知道如何构建程序。Boost.Build 的逻辑位于 Jamfiles 中,它们告诉 b2 如何构建程序。

即使 Boost.Build 基于一种编程语言,您也不需要在创建 Jamfiles 时考虑编程。Boost.Build 使用的编程语言的语法试图让您想起创建普通的旧配置文件。其理念是兼得两全:一个强大且灵活的编程语言,以及您可能从其他构建系统中熟悉的简单语法。

本文不会向您介绍 Boost.Build 基于的编程语言。这种编程语言是专有的,使用起来并不令人愉快。它不是 Javascript 或 Python 等流行脚本语言的竞争对手。Boost.Build 的开发人员意识到了这一点,他们正在开发基于 Python 的另一个版本的 Boost.Build。但是,对于计划使用 Boost.Build 管理其项目的开发人员来说,所有这些都不重要。了解 Boost.Build 内部编程语言的语法有助于更好地理解 Jamfiles 的语法。但学习该编程语言的细节并不是必需的。

让我们看一个简单的 Jamfile,它可以用来从源文件 hello.cpp 构建可执行文件 hello

exe hello : hello.cpp ; 

Boost.Build 提供了许多内置规则,exe 就是其中之一。虽然 Boost.Build 的文档将 exe 称为规则,但您已经知道上面的 Jamfile 实际上是使用编程语言构建的。事实证明,规则只是函数。上面的 Jamfile 包含一个函数调用。

对于构建程序通常需要的多数任务,Boost.Build 提供了预定义规则——或者您也可以称之为函数。与其他编程语言中的函数一样,可以传递参数。在上面的 Jamfile 中,函数 exe 被调用,并带有两个参数 hello 和 hello.cpp。

Boost.Build 基于的编程语言只知道一种数据类型:所有内容都是字符串列表。列表可以为空或包含一个或多个字符串。在上面的 Jamfile 中,函数 exe 被调用,并带有两个参数,每个参数都是包含一个字符串的列表。

exe "hello" : "hello.cpp" ; 

可以使用引号。不过没有必要,因为毕竟列表中的每个项目都是字符串类型。引号只在参数包含空格时使用。

虽然规则和第一个参数之间没有特殊的分隔符,但必须使用冒号来分隔其他参数。还需要在行末加上分号,就像您从 C++ 中习惯的那样。

请注意,Boost.Build 的编程语言要求所有标记周围都有空格。例如,冒号的左侧和右侧必须有空格,分号的左侧必须有空格。如果没有标记周围的空格,b2 将无法正确解析 Jamfile。

如果 b2 在包含上面 Jamfile 和源文件 hello.cpp 的目录中运行,并且在 Windows 上使用 msvc 工具集,那么会创建一个子目录 bin\msvc-9.0\debug 来构建可执行文件 hello.exe

...found 9 targets...
...updating 5 targets...
common.mkdir bin
common.mkdir bin\msvc-9.0
common.mkdir bin\msvc-9.0\debug
compile-c-c++ bin\msvc-9.0\debug\hello.obj
hello.cpp
msvc.link bin\msvc-9.0\debug\hello.exe
msvc.manifest bin\msvc-9.0\debug\hello.exe
...updated 5 targets...

如您所见,在 Jamfile 中只需一行代码即可从源文件构建可执行文件。如果程序是在 Windows 上构建的,还会自动添加正确的文件扩展名 exe

Boost.Build 的主要优势在于,您只需要指定构建系统构建程序所需的信息。Boost.Build 可以自动完成的所有操作都会自动完成。您无需检测程序构建的平台来决定是否应该添加 exe 之类的文件扩展名。您也不需要指定如何实际调用像 Visual C++ 这样的编译器来编译源代码。

Boost.Build 支持开箱即用的许多工具集。由于程序可以使用不同的工具集构建,因此 Boost.Build 使用特定于工具集的目录。这样,就可以使用不同的工具集构建程序,而不会使一个工具集不断覆盖另一个工具集生成的文件。

不仅有特定于工具集的目录,还有特定于变体的目录。变体是指程序的调试版本或发布版本。对于每个变体,都会使用另一个目录来构建程序——同样是为了防止覆盖由另一个变体生成的文件。默认情况下,使用调试变体。这就是为什么创建子目录 bin\msvc-9.0\debug 的原因。如果您希望创建发布版本,可以在命令行上使用 b2 variant=release 指定变体,或者更简单地使用 b2 release

...found 9 targets...
...updating 5 targets...
common.mkdir bin
common.mkdir bin\msvc-9.0
common.mkdir bin\msvc-9.0\release
compile-c-c++ bin\msvc-9.0\release\hello.obj
hello.cpp
msvc.link bin\msvc-9.0\release\hello.exe
msvc.manifest bin\msvc-9.0\release\hello.exe
...updated 5 targets...

将变体设置为发布后,将使用子目录 bin\msvc-9.0\release 来创建可执行文件 hello.exe

选择变体是一项非常常见的操作,因此只需输入 b2 release 即可。Boost.Build 会确定 release 是指选择变体。

如果您不想在命令行上指定变体,而是希望默认构建 hello.exe 的发布版本,则需要更改 Jamfile。

exe hello : hello.cpp : <variant>release ; 

exe 规则(或者如果您喜欢,函数)接受几个可选参数。第三个参数是一个需求列表。您可以将其视为始终设置并传递给用于构建可执行文件的命令的命令行选项。

为了强制构建发布版本,必须将变体设置为发布,就像之前在命令行上所做的那样。但是,在 Jamfile 中设置变体的语法不同。

Boost.Build 定义了类似 XML 标签的功能。Boost.Build 支持的功能之一是 <variant>。如果某个功能应该设置为某个值,则需要将其放在功能旁边——中间不留空格。某些功能是免费的,这意味着它们可以设置为任何您想要的值。 <variant> 是一个非免费功能,因为它只能设置为调试或发布。不允许使用其他值。如果设置了其他值,b2 将报告错误。

如果您运行 b2 variant=debug 并尝试构建 hello.exe 的调试版本,它将无法正常工作,因为 Jamfile 包含构建 hello.exe 作为发布版本的必要条件。如果您希望能够在命令行上覆盖此功能,则需要将该功能作为第四个参数而不是第三个参数传递。

exe hello : hello.cpp : : <variant>release ; 

第四个参数包含默认情况下使用的功能,但这些功能可以被覆盖。

如果您希望默认情况下构建 hello.exe 的调试版本和发布版本,则需要将 <variant> 功能设置为调试和发布两次。

exe hello : hello.cpp : : <variant>debug <variant>release ; 

重要的是,<variant> 必须在指定默认值的第四个参数中设置两次。如果它是在指定需求的第三个参数中设置的,b2 将报告错误。在需求中可以多次设置功能,但前提是这些值不能相互排斥。由于程序不能同时是调试版本和发布版本,因此 <variant> 必须在默认值中设置。只有这样,Boost.Build 才能理解应该构建 hello.exe 的两个版本。

exe hello : hello.cpp : <define>WIN32 <define>_WIN32 : <variant>debug <variant>release ; 

上面的 Jamfile 是在需求中多次设置功能的示例。功能 <define> 用于定义预处理器指令。定义多个预处理器指令没有问题。因此,现在构建了 hello.exe 的两个版本,这两个版本都定义了两个指令 WIN32_WIN32

exe hello : hello.cpp : : <variant>debug <variant>release <define>WIN32 <define>_WIN32 ; 

如果将这些定义移动到第四个参数中,并运行 b2,则会构建 hello.exe 的相同两个版本,这两个版本都定义了两个指令 WIN32_WIN32。由于 <define> 不希望相互排斥的值,因此不会生成其他可执行文件集。此 Jamfile 与上一个 Jamfile 的唯一区别在于,传递到第四个参数中的指令是默认值,可以删除,而作为第三个参数传递的任何内容都是不可变的需求。

以下是另一个示例,其中功能的值相互排斥。

exe hello : hello.cpp : : <variant>debug <variant>release <optimization>speed <optimization>off ; 

b2 创建 hello.exe 的四个版本:一个为速度优化的调试版本,一个没有优化的调试版本,一个为速度优化的发布版本,一个没有优化的发布版本。所有这些版本都在单独的目录中构建,这些目录会自动创建。

到目前为止,唯一使用的规则是 exe。但是,Boost.Build 当然还提供了许多其他内置规则。另一个重要的规则是 lib。它用于构建库。

lib world : world.cpp ; 

上面的 Jamfile 从源文件 world.cpp 构建一个共享库。在 Windows 上,会创建一个文件 world.dll。Boost.Build 会自动添加通常的文件扩展名。

默认情况下,构建共享库。如果您希望生成静态库,则将 <link> 功能设置为 static。

lib world : world.cpp : <link>static ; 

另一个有用的规则是 install。在构建可执行文件和库之后,可以使用此规则来安装它们。

exe hello : hello.cpp ; 
install "C:/Program Files/hello" : hello ; 

上面的 Jamfile 将可执行文件 hello.exe 安装到目录 C:\Program Files\hello。第二个参数 hello 是对第一行中定义的目标 hello 的引用。请注意,路径必须放在引号中,因为它包含空格。

这里体现了其他构建系统中已知的概念:不是考虑函数调用,而是每行都定义一个目标。通过引用其他目标来创建依赖项。这就是 Boost.Build 如何知道应该按什么顺序构建目标。

但是,通常 install 规则的写法有所不同。不是将安装目录作为第一个参数传递,而是使用功能 <location> 在第三个参数中设置安装目录。

exe hello : hello.cpp ; 
install install-bin : hello : <location>"C:/Program Files/hello" ; 

使用 <location> 更好的主要原因是,第一个参数始终定义一个目标。其他规则可能会引用目标。这就是为什么最好使用以后不需要更改的目标名称。假设程序应该安装到不同的目录。如果使用了 <location> 功能,则更改安装目录会更容易,因为不需要更新可能引用 install-bin 的其他规则。

使用功能还有另一个原因。Boost.Build 支持条件属性,这些属性可以根据程序构建的平台使用不同的安装目录。

exe hello : hello.cpp ; 
install install-bin : hello : <target-os>windows:<location>"C:/Program Files/hello" <target-os>linux:<location>/usr/local/bin ; 

功能 <target-os> 是另一个具有相互排斥值的功能。例如,可以将其设置为 windows 或 linux,但不能同时设置为两者。

功能 <location> 紧跟在 <target-os> 之后,两者之间只用冒号分隔。这种结构称为条件属性:Boost.Build 根据操作系统选择安装目录。

当然,条件属性也可以与其他规则一起使用。例如,可以在构建程序或库时定义根据变体不同的预处理器指令。

Boost.Build 提供了许多其他内置规则。另一个有用的规则是 glob,它可以使使用通配符成为可能。在包含许多源文件的较大项目中,就不需要逐个列出所有源文件,而是可以使用 glob 来引用它们。

exe hello : [ glob *.cpp ] ; 

上面的 Jamfile 包含一个嵌套的函数调用:规则 glob 的结果作为第二个参数传递给 exe。由于 Boost.Build 基于的编程语言的要求,必须对嵌套函数调用使用括号。


项目管理
多个 Jamfile

在包含许多 Jamfile 的大型项目中,需要以某种方式连接 Jamfile。项目根目录中通常有一个 Jamroot.jam 文件,子目录中有多个 Jamfile.jam 文件。如果在根目录中运行 b2,开发人员可能希望构建整个项目,包括子目录中的所有组件。由于 b2 会在父目录中查找 Jamfile,而不会在子目录中查找 Jamfile,因此 Jamfile 需要显式地引用子目录中的 Jamfile。

build-project hello ; 

如果 Jamfile 看起来像上面的示例,它会引用子目录 hello 中的 Jamfile。 build-project 是一个规则,它需要路径作为其唯一的参数。然后使用该路径来查找 Jamfile。

build-project hello ; 
build-project world ; 

如果您希望构建多个项目,则必须多次使用 build-project

除了引用子目录中的 Jamfile,将构建项目中的组件时应该使用的选项分组起来也是有意义的。

project : default-build release ; 
build-project hello ; 
build-project world ; 

The project 规则接受各种参数来设置当前工作目录和子目录中 Jamfile 的选项。

虽然其他规则(如 exelib)期望参数以特定顺序传递,但 project 使用命名参数。在上面的示例中,参数的名称是 default-build。这就是为什么可以将值 release 传递给一个非常不同的参数。

project : : : : : : : : : default-build release ; 
build-project hello ; 
build-project world ; 

将 release 作为第十个参数传递是没有意义的。但它起作用了,因为 project 不关心顺序。由于第十个参数被称为 default-build,因此它被接受。

project 只支持少数命名参数。另一个是 requirements,它可以用来设置不能被覆盖的选项。

project : requirements <variant>release ; 
build-project hello ; 
build-project world ; 

上面的 Jamfile 只构建发布版本。不再可能构建调试版本,因为 requirements 不能被覆盖。这就是与前面示例中使用的名为 default-build 的命名参数的区别:它可以被覆盖。

当使用 build-project 时,Boost.Build 假设参数是子目录的引用。我们之前已经见过另一种类型的引用。

exe hello : hello.cpp ; 
install install-bin : hello : <location>"C:/Program Files/hello" ; 

在上面的 Jamfile 中,install 规则引用了第一行中定义的目标 hello。

在大型项目中,可能需要引用在其他目录中的 Jamfile 中定义的目标。可以使用双斜杠将 Jamfile 的路径与目标连接起来。

install install-bin : subdir//hello : <location>"C:/Program Files/hello" ; 

现在,install 规则引用了子目录 subdir 中的 Jamfile 中的目标 hello。

假设可执行文件 hello 依赖于另一个目录 world 中的库。该库也使用 Boost.Build 使用 lib 规则构建。

lib world : world.cpp ; 

在构建可执行文件的 Jamfile 中,需要引用库的 Jamfile。没有必要直接引用目标 world,因为 Jamfile 中的所有目标默认都会被构建。

exe hello : hello.cpp world : : <variant>debug <variant>release ; 

上面的 Jamfile 假设库及其 Jamfile 位于子目录 world 中。

当构建可执行文件时,会生成两个版本 - 调试版本和发布版本。然而,库的 Jamfile 没有设置 <variant> 特性。但 Boost.Build 假设它也应该构建库的两个版本。特性 <variant> 被称为传播。

传播特性简化了项目管理,因为您不需要在各种 Jamfile 中设置相同的特性。但是,它也使得理解组件的构建方式变得更加复杂,因为这完全取决于哪些特性被传播。您可以假设 Boost.Build 知道它应该做什么。但当然,这并不意味着您很容易理解它在做什么。

让我们看一下另一个使用特性 <define> 的示例。

exe hello : hello.cpp world : <define>WIN32 : <variant>debug <variant>release ; 

上面的 Jamfile 为程序 hello 定义了一个预处理器指令 WIN32。但是,WIN32 会为库定义吗?

它不会,因为 <define> 不是一个传播特性。如果您想知道如何知道:唯一的方法是查找文档以了解哪些特性被传播。

如果您安装了 Boost C++ 库,您可能希望链接到其中的一些库。您需要以某种方式向项目的 Jamfile 添加对相应 Boost C++ 库的依赖关系。如果您没有删除解压缩 Boost C++ 库源文件的目录,您可以引用根目录中 Jamfile 中的目标。

exe hello : hello.cpp world C:/boost_1_39_0//filesystem/ ; 

现在 hello 也依赖于 Boost.Filesystem 库。由于目标 filesystem 在 Boost C++ 库根目录中的 Jamfile 中定义,因此 exe 规则可以引用它。不仅会链接适当的 Boost C++ 库,而且还会将包含目录传递给编译器以查找头文件。如果 hello.cpp 包含 boost/filesystem.hpp,则会找到头文件。

在上面的 Jamfile 中,Boost C++ 库根目录的路径是硬编码的。以某种方式,b2 需要知道在哪里找到 Boost C++ 库。但如果路径只在项目的多个组件需要链接到一些 Boost C++ 库的情况下硬编码一次,那就更好了。

project : requirements <variant>release ; 
use-project /boost : C:/boost_1_39_0 ; 
build-project hello ; 
build-project world ; 

使用 use-project 规则为另一个目录中的 Jamfile 定义别名。子目录中的 Jamfile 然后使用别名引用 Boost C++ 库。

exe hello : hello.cpp world /boost//filesystem ; 

b2 确定 hello.cpp 是一个源文件,world 是一个子目录,而 /boost//filesystem 是对 C:\boost_1_39_0 中的 Jamfile 中的目标 filesystem 的引用。

请注意,如果引用应该引用一个项目,则它必须以斜杠开头。

由于库可以以不同的方式链接,因此可以设置与链接器相关的特性。

exe hello : hello.cpp world /boost//filesystem/<link>static ; 

默认情况下,库是动态链接的。如果库应该静态链接,则特性 <link> 必须设置为 static。

特性可以用斜杠追加。如果应该设置多个特性,则将它们追加到上一个特性后的另一个斜杠。

exe hello : hello.cpp world /boost//filesystem/<link>static/<threading>multi ; 

<threading> 是另一个可以设置为 single 或 multi 的特性。如果 hello 应该链接到 Boost.Filesystem 的线程安全版本,则可以相应地设置该特性。

通过引用 Jamfile 来链接 Boost C++ 库可能并不总是有效。例如,如果 Boost C++ 库以不同的方式安装,因为它们不是从源代码构建的,那么就不会有任何 Jamfile 可以引用。

lib filesystem : : <name>libboost_filesystem <search>C:/libs ; 
exe hello : hello.cpp world filesystem : <include>C:/include ; 

lib 规则不仅可以用来从源代码构建库。它还必须用来引用现有的预构建库。

如果 lib 不应该从源代码构建库,则第二个参数必须为空。相反,在第三个参数中,使用特性 <name><search> 来指定库的名称和 Boost.Build 将找到库的位置。

以平台无关的方式指定库的名称非常重要。例如,对于上面的 Jamfile,Boost.Build 将尝试在 Windows 上找到文件 libboost_filesystem.lib。通常的文件扩展名会自动附加。

如果您想通过指定文件的确切名称来引用文件,可以使用 <file> 特性。

如果您要引用一个系统库,您期望 Boost.Build 知道在哪里找到它,则可以省略 <search> 特性。

还可以使用 project 规则来确保项目中的所有目标都自动链接到库。

lib filesystem : : <name>libboost_filesystem <search>C:/libs ; 
explicit filesystem ; 
project : requirements <include>C:/include <library>filesystem ; 
lib world : world.cpp ; 

一个名为 <library> 的特性必须用来向 project 规则添加库依赖关系。 <library> 必须引用一个 lib 规则,该规则使用已知的特性 <name><search>

现在,明确 lib 规则非常重要。这是通过使用 explicit 规则来完成的。这很重要,因为默认情况下,Jamfile 中的所有目标都会被构建。由于 project 规则为 Jamfile 中的所有目标定义了 requirements,因此它们也是 lib 规则的 requirements。因此,lib 规则引用自身。但是,如果 lib 规则被明确定义,它就不会被构建,并且不会发生递归引用。

请注意,Jamfile 中规则的顺序只在规则引用目标时才重要:在引用目标之前,它必须被定义。


最佳实践
其他人如何使用 Boost.Build

由于 Boost.Build 是一个高级构建系统,因此如果您保持 Jamfile 与平台和编译器无关,您将获得最大的收益。毕竟,这个想法是在任何平台上使用任何编译器构建您的 C++ 或 C 项目,而无需修改或维护多个 Jamfile。

您会遇到的一个典型问题是,您想要使用的第三方库将安装在不同的目录中。如果您想在 Windows 和 Unix 平台上构建您的项目,路径看起来也截然不同。此外,您可能需要在某个平台上链接到一些系统库,而在另一个平台上则不需要。

与其尝试将各个平台的路径放在项目的 Jamfile 中,不如在每个系统的配置文件中依赖于系统特定的设置。事实证明,b2 在启动时确实会查找另外两个配置文件。

文件 site-config.jam 应该用来设置整个系统的选项。由于它是机器相关的,因此 b2 预计在 Windows 平台的 C:\Windows 中找到它,而在 Unix 系统的 /etc 中找到它。由于 site-config.jam 是机器相关的,因此对本地库的路径没有问题。

但是,用户可能无法创建或更改 site-config.jam。他们要么需要等待系统管理员更新文件,要么再次被迫将系统特定的路径添加到他们自己的 Jamfile 中。由于这两种方法都不是好的解决方案,因此 b2 还会在用户的 home 目录中查找文件 user-config.jam。在 Windows 上,它是 C:\Users 的子目录,在 Unix 上,它是 /home 的子目录。由于文件 user-config.jam 可以由用户维护,因此它可能比 site-config.jam 更常被使用。

您可以像使用其他 Jamfile 一样使用 site-config.jamuser-config.jam。由于这些配置文件不属于项目,而是属于机器或机器上的用户,因此它们被允许包含机器特定的选项。例如,它们可以包含一个 using 规则。

using msvc ; 

上面的 using 规则告诉 b2 使用 msvc 工具集。如果您知道系统上只安装了 Visual C++,那么将此行放入配置文件中是有意义的。然后,b2 不需要再猜测要使用哪个工具集,并且不会发出警告。

如果您在 site-config.jamuser-config.jam 中定义了目标,并且想要在 Jamfile 中引用这些目标,则必须使用 project 规则来设置名称。

using msvc ; 
project user-config ; 
lib xml : : <name>libxml <search>C:/lib : : <include>C:/include ; 

lib 规则用来引用一个预构建库,其基名为 libxml,可以在 C:\lib 中找到。使用此 XML 库的程序可能需要包含来自此库的头文件。这就是为什么在使用 requirements 中 - 这是第五个参数 - <include> 特性被设置为 C:\include:任何使用此规则的人都会继承 <include> 特性。

由于 project 规则被用来设置名称 user-config,因此 Jamfile 可以通过 /user-config//xml 引用 XML 库。

exe xmlparser : xmlparser.cpp : <library>/user-config//xml ; 

为了构建 xmlparser,程序必须链接到 XML 库。即使库的位置及其头文件可能有所不同,但 Jamfile 也不包含任何系统特定的路径。Jamfile 预计在 user-config 项目中找到目标 xml。如果这是一个配置文件,那么使用系统特定的路径没有问题,因为毕竟配置文件与机器或机器上的用户绑定在一起。

由于 Boost.Build 是为了构建和安装 Boost C++ 库而创建的,因此它内置支持更轻松地使用预构建的 Boost C++ 库。

using msvc ; 
project user-config ; 
using boost : 1.39 : <include>C:/include/boost-1_39 <library>C:/lib ; 

必须使用 using 规则来引用名为 boost 的工具集。此工具集不同于您迄今为止了解的 msvc 等工具集:它不包含任何将在以后运行的程序。尽管在工具集中实现了对预构建的 Boost C++ 库的支持,但仍需要使用 using 规则。

与其他库一样,Boost C++ 库的位置可能会有所不同。因此,将 using 规则放到两个配置文件之一中是有意义的。

可以将参数传递给 using 规则:第一个是版本号,第二个是选项列表。在上面的 Jamfile 中,使用了位于作为选项传递的目录中的 Boost C++ 库 1.39。

一旦使用了 boost 工具集,就可以使用 Boost C++ 库,而无需自己定义目标。

import boost ; 
boost.use-project 1.39 ; 
exe hello : hello.cpp : <library>/boost//thread ; 

如果一个程序使用 Boost C++ 库,它可以引用名为 boost 的项目中的目标。为了识别项目 boost,必须导入 boost 模块并使用 boost.use-project 规则:导入 boost 模块使 boost.use-project 规则可用。此规则期望版本号作为其唯一的参数。由于可以使用 using 规则引用 Boost C++ 库的各个版本,因此项目可以指定要使用的版本。在上面的 Jamfile 中,程序 hello 使用了版本 1.39 中的 Boost.Thread。


规则参考
Jamfile 的构建块

如果您使用 Boost.Build 管理项目并创建 Jamfile,那么您会经常使用规则。因此,您应该知道哪些规则存在以及如何使用它们。下表概述了最重要的规则。

一些参数后面有一个星号、加号或问号。星号表示可以有任意多个值,加号表示必须至少有一个值,问号表示必须有零个或正好一个值。

表 1. 规则
名称 参数 描述
alias name : sources * : requirements * : default-build * : usage-requirements * 通过新名称引用源代码或任何其他目标。
build-project dir 引用另一个目录中的 Jamfile 来构建项目。
conditional condition + : requirements * 创建条件要求,而无需使用条件属性。
exe name : sources * : requirements * : default-build * : usage-requirements * 构建可执行文件。
explicit target-names * 使目标显式。
glob wildcards + : excludes * 通过通配符引用目录中的文件。
glob-tree wildcards + : excludes * 通过通配符引用目录及其所有子目录中的文件。
install name-and-dir : sources * : requirements * : default-build * 将文件安装到目录。
lib names + : sources * : requirements * : default-build * : usage-requirements * 构建库。
project id ? : options * : * 设置项目选项。
unit-test target : source : properties * 构建并运行可执行文件。
use-project id : where 引用另一个目录中的 Jamfile 以将项目 id 作为目标使用。
using toolset-module : * 选择一个工具集。

您的 Boost.Build 版本可能支持上面列出的更多规则。如果您想知道支持哪些规则,您应该查看 Boost.Build 安装目录的 build 子目录中的文件。


功能参考
构建过程的配置选项

功能允许您准确地指定如何构建二进制文件。由于有许多可用的配置选项,因此功能列表相当长。下表介绍了最重要的功能。

表 2. 功能
名称 描述
<address-model> 16, 32, 64, 32_64 生成 16 位、32 位或 64 位代码。
<architecture> x86, ia64, sparc, power, mips1, mips2, mips3, mips4, mips32, mips32r2, mips64, parisc, arm, combined, combined-x86-power 设置处理器系列以生成代码。
<c++-template-depth> 1, 2, 3, ... 设置最大模板深度。
<cflags> ... 将标志传递给 C 编译器。
<cxxflags> ... 将标志传递给 C++ 编译器
<debug-symbols> on, off 创建调试符号。
<def-file> ... 设置 def 文件的路径(特定于 Windows DLL)。
<define> ... 定义预处理器指令。
<embed-manifest> on, off 嵌入清单(特定于 msvc 工具集)。
<host-os> aix, bsd, cygwin, darwin, freebsd, hpux, iphone, linux, netbsd, openbsd, osf, qnx, qnxnto, sgi, solaris, unix, unixware, windows 如果功能依赖于主机操作系统,则在条件属性中使用。
<include> ... 设置包含目录。
<inlining> off, on, full 内联函数。
<library> ... 链接到库(在 project 规则中使用)。
<link> shared, static 链接到共享或静态版本的库。
<linkflags> ... 将标志传递给链接器。
<location> ... 设置目录(在 install 规则中使用)。
<name> ... 设置库的基本名称(在 lib 规则中使用)。
<optimization> off, speed, space 生成优化代码。
<profiling> off, on 生成已分析的代码。
<runtime-link> shared, static 链接到单线程或线程安全的运行时库。
<search> ... 设置搜索库的目录(在 lib 规则中与 <name> 一起使用)。
<source> ... project 规则的 requirements 参数或条件属性中设置源代码。
<target-os> aix, appletv, bsd, cygwin, darwin, freebsd, hpux, iphone, linux, netbsd, openbsd, osf, qnx, qnxnto, sgi, solaris, unix, unixware, windows 如果功能依赖于目标操作系统,则在条件属性中使用。
<threading> single, multi 构建单线程或线程安全的版本。
<toolset> gcc, msvc, intel-linux, intel-win, acc, borland, como-linux, cw, dmc, hp_cxx, sun 如果功能依赖于工具集,则在条件属性中使用。
<undef> ... 取消定义预处理器指令。
<use> ... 仅接管引用目标的使用要求,但不做任何其他操作。
<variant> debug, release, profile 构建调试、发布或概要分析版本。
<warnings> on, all, off 关闭警告。
<warnings-as-errors> off, on 将警告视为错误。

有关 Boost.Build 功能的完整和最新参考,请查看 Boost.Build 安装目录的 tools 子目录中的 builtin.jam 文件。搜索以 feature.feature 开头的行 - 这是用于定义功能的内部规则。


版权所有 Boris Schäling 2009。根据 Boost 软件许可证版本 1.0 分发。 (参见附带的 LICENSE_1_0.txt 文件或复制到 https://boost.ac.cn/LICENSE_1_0.txt