Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

This is an old version of boost. Click here for the latest version's documentation home page.

Boost.Build v2 user manual


This document Other documents
Tutorial
Hello, world
Properties
Project hierarchy
Using libraries
Library dependencies
Static and shared libraries
Prebuilt targets
Reference documentation
Overview
Your first project and roadmap
Main targets
Projects
Target identifiers and references
Builtin facilities
Main targets
Features
Detailed reference
Features and properties
Defintions
Property Validity
Feature Attributes
Feature Declaration
Build Variants
Subfeatures
Initialization
Command line
Build process
Alternative selection
Generated headers
Extender manual
Recipes
Architecture

How to use this document

If you've just found out about Boost.Build V2 and want to know if it will work for you, start with tutorial. You can continue with the reference. When you're ready to try Boost.Build in practice, go to installation.

If you are about to use Boost.Build on your project, or already using it and have a problem, look at the reference.

If you're trying to build a project which uses Boost.Build, look at installation and then read about command line.

Finally, if nothing applies to you, write to our mailing list, telling what information you'd like to know.

Installation

Assuming you're installing Boost.Build from released source distribution, the following steps are needed. All paths are given relatively to Boost.Build root directory, which is the directory with the document you are reading.
  1. Go to "jam_src" directory and build Boost.Jam. Two convenient scripts are provided, "build.sh" (for Unix systems) and "build.bat" (for Windows). Run the appropriate one and Boost.Jam will be built to directory bin.{platform_name}.. The Boost.Jam documentation has more details in case you need them.
  2. Place the Boost.Jam binary, called "bjam" or "bjam.exe", somewhere in your PATH. Go to the root directory of Boost.Build and run "bjam --version". You should get
          Boost.Build V2 (Milestone N)
         
    
    (where N is the version you've downloaded).
  3. Configure toolsets to use. Open the user-config.jam file and follow instructions there to specify what compiles/libraries you have and where they are located.
  4. You should now be able to go to example/hello, and run bjam there. A simple application will be built. You can also play with other projects in example.

If you use Boost distribution, or Boost CVS, the Boost.Build root is located at $boost_root/tools/build/v2 and the installation steps are the same. However, don't skip the bjam rebuilding step, even if you have a previous version. CVS version of Boost.Build requires CVS version of Boost.Jam.

When starting a new project which uses Boost.Build, you need to make sure that build system can be found. There are two ways.

If you're trying to use Boost.Build V2 on Boost itself, please note that when building Boost, V1 is used by default. You'd have to add --v2 command line option to all "bjam" invocations.

Tutorial

Hello, world

The simplest project that Boost.Build can construct is stored in example/hello directory. The targets are declared in a file called Jamfile, which contains the following:
   exe hello : hello.cpp ;
Even with this simple setup, you can do some interesting things. First of all, running "bjam" would build binary "hello" from hello.cpp, in debug version. After that, you can run
    bjam release 
which would create release version of the 'hello' binary. Note that debug and release version would be created in different directories, so if you want to switch from debug to release version and back, no recompilation is needed. Let's extend the example by adding another line to Jamfile:
   exe hello2 : hello.cpp ;
You can now rebuild both debug and release versions:
    bjam debug release
You'll see that two versions of "hello2" binary are linked. Of course, hello.cpp won't be recompiled. Now you decide to remove all build products. You do that with the following command
   bjam --clean debug release
It's also possible to create or clean only specific targets. Both following commands are legal and create or clean only files that belonging the the named binary:
   bjam hello2
   bjam --clean hello2

Properties

Boost.Build attempts to allow building different variants of projects, e.g. for debugging and release, or in single and multithreaded mode. In order to stay portable, it uses the concept of features, which is abstract aspect of build configuration. Property is just a (feature,value) pair. For example, there's a feature "debug-symbols", which can have a value of "on" or "off". When users asks to build project is a particual value, Boost.Build will automatically find the appropriate flags to the used compiler.

The "release" and "debug" in bjam invocation that we've seen are just are short form of specifying values of feature "variant". There is a lot of builtin features, and it's possible to write something like:

   bjam release inlining=off debug-symbols=on
The first command line element specified the value of feature "variant". The feature is very common and is therefore special — it's possible to specify only value. Another feature, "inlining" is not special, and you should use
   feature-name=feature-value
syntax for it. Complete description of features can be found here. The set of properties specified in the command line constitute build request — the desired properties for requested targets, or for the project in the current directory. The actual set of properties used for building is often different. For example, when compiling a program you need some include paths. It's not reasonable to ask the user to specify those paths with each bjam invocation, so must be specified in Jamfile and added to the build request. For another example, certain application can only be linked in multithreaded mode. To support such situations, every target is allowed to specify requirements -- properties that are required to its building. Consider this example:
   exe hello 
       : hello.cpp
       : <include>/home/ghost/Work/boost <threading>multi
In this case, when hello is build, the two specified properties will always be present. This leads to a question: what if user explictly requested single-threading. The answer is that requirement can affect build properties only to a certain degree: the requested and actual properties must be link-compatible. See link compatibility below. If they are not link compatible, the bulding of the target is skipped. Previously, we've added "hello2" target. Seems like we have to specify the same requirements for it, which results in duplication. But there's a better way. Each project (i.e. each Jamfile), can specify a set of attributes, including requirements:
   project 
       : requirements <include>/home/ghost/Work/boost <threading>multi 
       ;

   exe hello : hello.cpp ;
   exe hello2 : hello.cpp ;
The effect would be as if we specified this requirement for both "hello" and "hello2".

Project hierarchy

So far we only considered examples with one project (i.e. with one Jamfile). Typically, you'd have a lot of projects organized into a tree. At the top of the tree there's project root. This is a directory which contains, besides Jamfile, a file called "project-root.jam". Each other Jamfile has a single parent, which is the Jamfile in the nearest parent directory. For example, in the following directory layout:

    [top] 
      |
      |-- Jamfile
      |-- project-root.jam
      |
      |-- src
      |    |
      |    |-- Jamfile
      |    \-- app.cpp
      | 
      \-- lib
           |
           |-- lib1
           |    |
           |    |-- Jamfile
                |-- lib1.cpp
project root is at top. Both src/Jamfile and lib/lib1/Jamfile have [top]/Jamfile as parent project. Projects inherit all attributes (such as requirements) from their parents. When the same attributes are specified in the project, they are combined with inherited ones. For example, if [top]/Jamfile has
     <include>/home/ghost/local
in requirements, then all other projects will have that in their requirements too. Of course, any project can add additional includes. More details can be found in the section on projects. Projects are not automatically built when their parents are built. You should specify this explicitly. In our example, [top]/Jamfile might contain:
    build-project src ;
It will cause project in src to be built whenever project in [top] is built. However, targets in lib/lib1 will be built only if required. For example, there may be 10 targets, and two of them are used by targets in src/Jamfile. Then, only those two targets will be built.

Using libraries

Let's continue the above example and see how src/Jamfile can use libraries from lib/lib1. (TODO: need to make this section consistent with "examples-v2/libraries". Assume lib/lib1/Jamfile contains:
     lib lib1 : lib1.cpp ;
Then, to use this library in src/Jamfile, we can write:
     exe app : app.cpp ../lib/lib1//lib1 ;
While "app.cpp" is a regular source file, "../lib/lib1//lib1" is a reference to another target, here, library "lib1" declared in Jamfile at "../lib/lib1". When linking the "app" binary, the needed version of the library will be built and linked in. But what is meant by "needed"? For example, we can request to build "app" with properties
    <optimization>full <cxxflags>-w-8080
Which properties must be used for "lib1"? The answer is that some properties are propagated — Boost.Build attemps to use dependencies with the same value of propagated features. The <optimization> feature is propagated, so both "app" and "lib1" will be compiled with full optimization. But <cxxflags> feature is not propagated: its value will be added as-is to compiler flags for "a.cpp", but won't affect "lib1". There is still a couple of problems. First, the library probably has some headers which must be used when compiling "app.cpp". We could use requirements on "app" to add those includes, but then this work will be repeated for all programs which use "lib1". A better solution is to modify lib/lib1/Jamfilie in this way:
    project 
        : usage-requirements <include>.
        ;

     lib lib1 : lib1.cpp ;
Usage requirements are requirements which are applied to dependents. In this case, <include> will be applied to all targets which use "lib1" — i.e. targets which have "lib1" either in sources or in dependency properties. You'd need to specify usage requirements only once, and programs which use "lib1" don't have to care about include paths any longer. Or course, the path will be interpreted relatively to "lib/lib1" and will be adjusted according to the bjams invocation directory. For example, if building from project root, the final compiler's command line will contain -Ilib/lib1.

The second problem is that we hardcode the path to library's Jamfile. Imagine it's hardcoded in 20 different places and we change the directory layout. The solution is to use project ids — symbolic names, not tied to directory layout. First, we assign a project id to Jamfile in lib/lib1:

    project lib1
        : usage-requirements <include>.
        ;
Second, we use the project id to refer to the library in src/Jamfile:
    exe app : app.cpp /lib1//lib1 ;
The "/lib1//lib1" syntax is used to refer to target "lib1" in project with global id "/lib1" (the slash is used to specify global id). This way, users of "lib1" do not depend on its location, only on id, which is supposedly stable. The only thing left, it to make sure that src/Jamfile knows the project id that it uses. We add to [top]/Jamfile the following line:
    use-project /lib1 : lib/lib1 ;
Now, all projects can refer to "lib1" using the symbolic name. If the library is moved somewhere, only a single line in the top-level Jamfile should be changed.

Library dependencies

The previous example was simple. Often, there are long chains of dependencies between libraries. The main application is a thin wrapper on top of library with core logic, which uses library of utility functions, which uses boost filesystem library. Expressing these dependencies is straightforward:

    lib utils : utils.cpp /boost/filesystem//fs ;   
    lib core : core.cpp utils ;
    exe app : app.cpp core ;

So, what's the reason to even mention this case? First, because it's a bit more complex that it seems. When using shared linking, libraries are build just as written, and everything will work. However, what happens with static linking? It's not possible to include another library in static library. Boost.Build solves this problem by returning back library targets which appear as sources for static libraries. In this case, if everything is built statically, the "app" target will link not only "core" library, but also "utils" and "/boost/filesystem//fs".

So, the net result is that the above code will work for both static linking and for shared linking.

Sometimes, you want all applications in some project to link to a certain library. Putting the library in sources of all targets is possible, but verbose. You can do better by using <library> property. For example, if "/boost/filesystem//fs" should be linked to all applications in your project, you can add <library>/boost/filesystem//fs to requirements of the project, like this:

    project 
       : requirements <library>/boost/filesystem//fs
       ;   

Static and shared libaries

While the previous section explained how to create and use libraries, it omitted one important detail. Libraries can be either static, which means they are included in executable files which use them, or shared (a.k.a. dynamic), which are only referred to from executables, and must be available at run time. Boost.Build can work with both types. By default, all libraries are shared. This is much more efficient in build time and space. But the need to install all libraries to some location is not always convenient, especially for debug builds. Also, if the installed shared library changes, all application which use it might start to behave differently.

Static libraries do not suffer from these problems, but considerably increase the size of application. Before describing static libraries, it's reasonable to give another, quite simple approach. If your project is built with <hardcode-dll-paths>true property, then the application will include the full paths for all shared libraries, eliminating the above problems. Unfortunately, you no longer can move shared library to a different location, which makes this option suitable only for debug builds. Further, only gcc compiler supports this option.

Building a library statically is easy. You'd need to change the value of <link> feature from it's deafault value shared, to static. So, to build everything as static libraries, you'd say

    bjam link=static
on the command line. The linking mode can be fine-tuned on per-target basis.
  1. Suppose your library can be only build statically. This is easily achieved using requirements:
        lib l : l.cpp : <link>static ;
       
    
  2. What if library can be both static and shared, but when using it in specific executable, you want it static? Target references are here to help:
        exe important : main.cpp helpers/<link>static ;
       
    
  3. What if the library is defined in some other project, which you cannot change. But still, you want static linking to that library in all cases. You can use target references everywhere:
        exe e1 : e1.cpp /other_project//lib1/<link>static ;
        exe e10 : e10.cpp /other_project//lib1/<link>static ;
       
    
    but that's far from being convenient. Another way is to introduce a level of indirection: create a local target, which will refer to static version of lib1. Here's the solution:
        alias lib1 : /other_project//lib1/<link>static ;
        exe e1 : e1.cpp lib1 ;
        exe e10 : e10.cpp lib1 ;
       
    
    (Note, that the "alias" target type is not yet implemented, but it's quite simple to do. I bet it's waiting for you to do it ;-))

Prebuilt targets

We've just learned how to use libraries which are created by Boost.Build. But some libraries are not. At the same time, those libraries can have different versions (release and debug, for example), that we should select depending on build properties. Prebuilt targets provide a mechanism for that. Jamfile in lib/lib2 can contain:
    lib lib2
       : 
       : <file>lib2_release.a <variant>release
       ;

    lib lib2
       : 
       : <file>lib2_debug.a <variant>debug
       ;
This defines two alternatives for target "lib2", and for each one names a prebuilt file. Naturally, there are no sources. Instead, the <file> feature is used to specify the file name. Which alternative is selected depends on properties of dependents. If "app" binary should use "lib2", we can write:
    exe app : app.cpp /lib/lib1//lib2 ../lib/lib2//lib2 ;
If we build release version of "app", then it will be linked with "lib2_release.a", and debug version will use "lib2_debug.a". Another important kind of prebuilt targets are system libraries — more specifically, libraries which are automatically found by the compiler. E.g. gcc uses "-l" switch for that. Such libraries should be declared almost like regular ones:
    lib zlib : : <name>z ;
We again don't specify any sources, but give a name which should be passed to the compiler. In this example, and for gcc compiler, the "-lz" option will be added. Paths where library should be searched can also be specified:
    lib zlib : : <name>z <search>/opt/lib ;
And, of course, two variants can be used:
    lib zlib : : <name>z <variant>release ;
    lib zlib : : <name>z_d <variant>debug ;
Of course, you'll probably never in your life need debug version of zlib, but for other libraries this is quite reasonable.

More advanced use of prebuilt target is descibed in recipes.

Reference

This section will document mostly high-level view of Boost.Build, mentioning appropriate modules and rules. The on-line help system must be used to obtain low-level documentation (see the help option).

Overview

The most fundemental entity in Boost.Build is main target. This is object that user want to construct from sources and keep up to date with regard to those sources. Typical examples of main targets are executable files and libraries.

Main targets are grouped in projects. Their main purpose is organization: related targets placed in one project, can then be built together, or share some definitions.

Main targets and projects are created as the result of reading one or several Jamfiles. Each Jamfile is a file written in Boost.Jam interpreted language, and typically contains calls to functions provided by Boost.Build, which create main targets of needed type, declare project attributes and access other projects. The full list of functions provided by Boost.Build is described below. Of course, user can create his own functions, or it can directly access Boost.Build internals from Jamfile, if builtin facilities are not sufficient.

Each main target, or project can be built in a number of ways, say with optimization or without. We'll call such entities "metatargets". To make Boost.Build produce any real targets, user issues build request, which specifies metatargets to be built, and properties to be used.

The properties are just (name,value) pairs that describe various aspects of constructed objects, for example:

<optimization>full <inlining>off

Given the built request, Boost.Build figures out the targets needed for requested metatargets with requested properties, how they can be created, and whether exising files can be reused. It finally issues command to create needed files, automatically converting properties into appropricate command line options.

Your first project and roadmap

Creating your first project requires three steps:

  1. Create an empty file called "Jamfile"
  2. Create an empty file called "project-root.jam"
  3. Either set your BOOST_BUILD_PATH environment variant to Boost.Build root, or create a "boost-build.jam" file with the following content:
    boost-build /path/to/boost.build ;
    

After that, you can run the "bjam" command in the directory where you've created the files. Surely, it won't do anything, but it will run without error, at least. Your next steps might be:

  1. Adding new main targets to the "Jamfile" file. The basic syntax for declaring a main target is described below, and all builtin functions for declaring main targets are listed.
  2. Creating subprojects. Create a directory, put new Jamfile there, and move some main targets to that Jamfile, or declare new ones. The projects reference will help with this part.
  3. Customizing Boost.Build for your needs. You might have additional tools you want to run, or just want different extension for some file. The extender manual is waiting for you.

Main targets

Main target is a user-defined named entity which can be build, for example a named executable file. Declaring a main target is usually done using one of main target functions. The user can also declare custom main target function.

Most main targets rules in Boost.Build use similiar syntax:

    function-name main-target-name 
        : sources 
        : requirements 
        : default-build 
        : usage-requirements 
        ;
   

Some main target rules have shorter list of parameters, and you should consult their documentation for details.

Building of the same main target can differ greatly from platform to platform. For example, you might have different list of sources for different compilers. Therefore it is possible to invoke main target rules several times for a single main target. For example:

    exe a : a_gcc.cpp : <toolset>gcc ;
    exe a : a.cpp ;
Each call to the 'exe' rule defines a new main target alternative for the main target a. In this case, the first alternative will be used for the gcc toolset, while the second alternative will be used in other cases. See below for details.

Sometime a main target is really needed only by some other main target. E.g. a rule that declared test-suite uses a main target that represent test, but those main targets are rarely needed by themself.

It possible to declare target inline, i.e. the "sources" parameter may include call to other main rules. For example:

    exe hello : hello.cpp 
       [ obj helpers : helpers.cpp : <optimization>off ] ;
   
Will cause "helpers.cpp" to be always compiled without optimization. It's possible to request main targets declared inline, but since they are considered local, they are renamed to "parent-main-target_name..main-target-name". In the example above, to build only helpers, one should run "bjam hello..helpers".

Projects

Boost.Build considers every software it build as organized into projects — modules which declare targets. Projects are organized in a hierarchical structure, so each project may have a single parent project and a number of subprojects.

Most often, projects are created as result of loading Jamfile — files which are specially meant to describe projects. Boost.Build will implicitly load Jamfile in the invocation directory, and all Jamfiles referred by the first one, creating the hierarchy of projects.

The exact name of file that describes project is configurable. By default, it's Jamfile, but can be changed by setting global variables JAMFILE, for example in boost-build.jam file. The value of the variable is a list of regex patterns that are used when searching for Jamfile in a directory.

Every Boost.Build modules can decide to act as project and be able to declare targets. For example, the site-config.jam module can declare libraries available on a given host, as described here.

There are three things that can be put in Jamfile: declarations of main targets, calls to a number of predefined rules, and arbitrary user code. The predefined rules are listed below:

Rule Semantic
project Define project attributes.
use-project Make another project known.
build-project Build another project when this one is built.
explicit States that the target should be built only by explicit request.
glob Takes a list of wildcards, and returns the list of files which match any of the wildcards.

Each project is also associated with project root. That's a root for a tree of projects, which specifies some global properties.

Project root

Project root for a projects is the nearest parent directory which contains a file called project-root.jam. That file defines certain properties which apply to all projects under project root. It can:

To facilitate declaration of simple projects, Jamfile and project-root can be merged together. To achieve this effect, the project root file should call the project rule. The semantic is precisely the same as if the call was made in Jamfile, except that project-root.jam will start serve as Jamfile. The Jamfile in the directory of project-root.jam will be ignored, and project-root.jam will be able to declare main targets as usual.

Project attributes

For each project, there are several attributes.

Project id is a short way to denote a project, as opposed to the Jamfile's pathname. It is a hierarchical path, unrelated to filesystem, such as "boost/thread". Target references make use of project ids to specify a target.

Source location specifies the directory where sources for the project are located.

Project requirements are requirements that apply to all the targets in the projects as well as all subprojects.

Default build is the build request that should be used when no build request is specified explicitly.

The default values for those attributes are given in the table below. In order to affect them, Jamfile may call the project rule. The rule has this syntax:

    project id : <attributes> ;
   
Here, attributes is a sequence of (attribute-name, attribute-value) pairs. The list of attribute names along with its handling is also shown in the table below. For example, it it possible to write:
    project tennis 
        : requirements <threading>multi 
        : default-build release
        ;
   
Attribute Name for the 'project' rule Default value Handling by the 'project' rule
Project id none none Assigned from the first parameter of the 'project' rule. It is assumed to denote absolute project id.
Source location source-location The location of jamfile for the project Sets to the passed value
Requirements requirements The parent's requirements The parent's requirements are refined with the passed requirement and the result is used as the project requirements.
Default build default-build none Sets to the passed value
Build directory build-dir If parent has a build dir set, the value of it, joined with the relative path from parent to the current project. Otherwise, empty Sets to the passed value, interpreted as relative to the project's location.

Project relationship

There are three kinds of project relationships.

First is parent-child. This relationship is established implicitly: parent directories of a project are searched, and the first found Jamfile is assumed to define the parent project. The parent-child relationship affects only attribute values for the child project.

Second is build relationship. Some project may request to recursively build other projects. Those project need not be child projects. The build-project rule is used for that:

    build-project src ;   

The third kind is the 'use' relationship. In means that one project uses targets from another. It is possible to just refer to target in other projects using target id. However, if target id uses project id, it is required that the project id is known. The use-project rule is employed to guarantee that.

    use-project ( id : location )
   
It loads the project at the specified location, which makes its project id available in the project which invokes the rule. It is required that the id parameter passed to the use-project rule be equal to the id that the loaded project declared. At this moment, the id paremeter should be absolute project id.

Target identifiers and references

Target identifier is used to denote a target. The syntax is:

    target-id -> (project-id | target-name | file-name )
                  | (project-id | directory-name) "//" target-name   
    project-id -> path
    target-name -> path
    file-name -> path
    directory-name -> path                  
This grammar allows some elements to be recognized as either To determine the real meaning a check is made if project-id by the specified name exists, and then if main target of that name exists. For example, valid target ids might be:
    a                                    -- target in current project
    lib/b.cpp                            -- regular file
    /boost/thread                        -- project "/boost/thread"
    /home/ghost/build/lr_library//parser -- target in specific project

Rationale:Target is separated from project by special separator (not just slash), because:

Target reference is used to specify a source target, and may additionally specify desired properties for that target. It has this syntax:

    target-reference -> target-id [ "/" requested-properties ]
    requested-properties -> property-path
For example,
    exe compiler : compiler.cpp libs/cmdline/<optimization>space ;
would cause the version of cmdline library, optimized for space, to be linked in even if the compiler executable is build with optimization for speed.

Builtin facilities

Main targets

exe
Creates a regular executable file. Sources must be either object files or libraries, and sources of different types will be converted to accepted types automatically.
lib

Creates a library file. Depending on the value of <link> feature the library will be either static or shared. Like with "exe", sources will be converted either to objects or libraries.

The handling of libraries in sources depends on whether linking is static or shared. For shared linking, libraries will be linked in. For static linking the library sources will not be linked in, since it's not possible, and will be passed on. Other main target which depend on this one will see those libraries and link to it. Therefore, putting library in sources of other library works in all cases.

alias
Builds all the source targets and returns them unmodified. Please run "bjam --help alias" for more details.
stage
Copies a number of targets to a single directory. The primary purpose is installing binaries. Please run "bjam --help stage" for more details.
unit-test (from module "testing")
Creates an executable file and runs it. Build won't succeed unless the executable runs successfully. The rule is usefull for creating test program which should be rerun whenever any dependency changes.

Features

variant
The feature which combines several low-level features in order to make building most common variants simple.

Allowed values: debug, release, profile

The value debug expands to

    <optimization>off <debug-symbols>on <inlining>off <runtime-debugging>on
   

The value release expands to

<optimization>speed <debug-symbols>off <inlining>full <runtime-debugging>off

The value profile expands to the same as release, plus:

<profiling>on <debug-symbols>on

Rationale: Runtime debugging is on in debug build so suit expectations of people used various IDEs. It's assumed other folks don't have any specific expectation in this point.

link
Feature which controls how libraries are built.

Allowed values: shared, static

library
For exe and lib main targets, the <library>X feature is equvivalent to putting X in the list of sources. The feature is sometimes more convenient: you can put <library>X in the requirements for a project and it will be linked to all executables.
use
Causes the target referenced by the value of this feature to be constructed and adds it's usage requirements to build properties. The constructed targets are not used in any other way. The primary use case is when you use some library and want it's usage requirements (such as include paths) to be applied, but don't want to link to the library.
dll-path
Specify a path where dynamic libraries should be found at where executable or shared library is run. This feature directly affects binaries with the gcc compiler, allowing them to pick specific libraries, and ignoring all environment settings. On other toolsets, the binary still requires proper environment settings to be run. However, Boost.Build tools which run executables will notice dll-path settings and create this environment automatically.
hardcode-dll-paths
Controls automatic generation of dll-path properties.

Allowed values: off, on When this property is on, usage requirements for each library will include additional dll-path propertry, with the path the the generated library file. This allows to run executables without placing all the dependent libraries to a single location.

Detailed reference

Features and properties

Definitions

A feature is a normalized (toolset-independent) aspect of a build configuration, such as whether inlining is enabled. Feature names may not contain the '>' character.

And what about dash?

Each feature in a build configuration has one or more associated values. Feature values for non-free features may not contain the '<', ':', or '=' characters. Feature values for free features may not contain the '<' character.

A property is a (feature,value) pair, expressed as <feature>value.

A subfeature is a feature which only exists in the presence of its parent feature, and whose identity can be derived (in the context of its parent) from its value. A subfeature's parent can never be another subfeature. Thus, features and their subfeatures form a two-level hierarchy.

A value-string for a feature F is a string of the form value-subvalue1-subvalue2...-subvalueN, where value is a legal value for F and subvalue1...subvalueN are legal values of some of F's subfeatures. For example, the properties <toolset>gcc <toolset-version>3.0.1 can be expressed more conscisely using a value-string, as <toolset>gcc-3.0.1.

A property set is a set of properties (i.e. a collection without dublicates), for instance: <toolset>gcc <runtime-link>static.

A property path is a property set whose elements have been joined into a single string separated by slashes. A property path representation of the previous example would be <toolset>gcc/<runtime-link>static.

A build specification is a property set which fully describes the set of features used to build a target.

Property Validity

For free features, all values are valid. For all other features, the valid values are explicitly specified, and the build system will report an error for the use of an invalid feature-value. Subproperty validity may be restricted so that certain values are valid only in the presence of certain other subproperties. For example, it is possible to specify that the <gcc-target>mingw property is only valid in the presence of <gcc-version>2.95.2.

Feature Attributes

Each feature has a collection of zero or more of the following attributes. Feature attributes are low-level descriptions of how the build system should interpret a feature's values when they appear in a build request. We also refer to the attributes of properties, so that a incidental property, for example, is one whose feature is has the incidental attribute.

Features which are neither free nor incidental are called base features.

TODO: document active features..

Feature Declaration

The low-level feature declaration interface is the feature rule from the feature module:
rule feature ( name : allowed-values * : attributes * )
A feature's allowed-values may be extended wit The build system will provide high-level rules which define features in terms of valid and useful combinations of attributes.

Build Variants

A build variant, or (simply variant) is a special kind of composite feature which automatically incorporates the default values of features that . Typically you'll want at least two separate variants: one for debugging, and one for your release code. [ Volodya says: "Yea, we'd need to mention that it's a composite feature and describe how they are declared, in pacticular that default values of non-optional features are incorporated into build variant automagically. Also, do we wan't some variant inheritance/extension/templates. I don't remember how it works in V1, so can't document this for V2.". Will clean up soon -DWA ]

When the build system tries to generate a target (such as library dependency) matching a given build request, it may find that an exact match isn't possible — for example, the target may impose additonal build requirements. We need to determine whether a buildable version of that target can actually be used.

The build request can originate in many ways: it may come directly from the user's command-line, from a dependency of a main target upon a library, or from a dependency of a target upon an executable used to build that target, for example. For each way, there are different rules whether we can use a given subvariant or not. The current rules are described below.

Two property sets are called link-compatible when targets with those property sets can be used interchangably. In turn, two property sets are link compatible when there's no link-incompatible feature which has different values in those property sets.

When building of a main target is requested from a command line or some project, link-compatibility is not considered. When building is requested by other main target, via sources or dependency properties, the requested and actual property sets must be link-compatible, otherwise a warning is produced.

Rationale:Link-compatibility is not considered when main target is requested by a project, because it causes problems in practice. For example, some parts of a project might be single-threaded, while others — multi-threaded. They are not link-compatible, but they are not linked, either. So, there's no need to issue error or warning. The errors used to be generated, and only caused problems.

Definition of property refinement

When a target with certain properties is requested, and that target requires some set of properties, it is needed to find the set of properties to use for building. This process is called property refinement and is performed by these rules

  1. If original properties and required properties are not link-compatible, refinement fails.
  2. Each property in the required set is added to the original property set
  3. If the original property set includes property with a different value of non free feature, that property is removed.

Conditional properties

Sometime it's desirable to apply certain requirements only for specific combination of other properties. For example, one of compilers that you use issues a poinless warning that you want to suppress by passing a command line option to it. You would not want to pass that option to other compilers. Condititional properties allow to do that. Their systax is:

  property ( "," property ) * ":" property
For example, the problem above would be solved by:
exe hello : hello.cpp : <toolset>yfc:<cxxflags>-disable-pointless-warning ;

Initialization

bjam's first job upon startup is to load the Jam code which implements the build system. To do this, it searches for a file called "boost-build.jam", first in the invocation directory, then in its parent and so forth up to the filesystem root, and finally in the directories specified by the environment variable BOOST_BUILD_PATH. When found, the file is interpreted, and should specify the build system location by calling the boost-build rule:

      rule boost-build ( location ? )
If location is a relative path, it is treated as relative to the directory of boost-build.jam. The directory specified by location and directories in BOOST_BUILD_PATH are then searched for a file called bootstrap.jam which is interpreted and is expected to bootstrap the build system. This arrangement allows the build system to work without any command-line or environment variable settings. For example, if the build system files were located in a directory "build-system/" at your project root, you might place a boost-build.jam at the project root containing:
     boost-build build-system ;
In this case, running bjam anywhere in the project tree will automatically find the build system.

The default "bootstrap.jam", after loading some standard definitions, loads two files, which can be provided/customised by user: "site-config.jam" and "user-config.jam".

Locations where those files a search are summarized below:

search paths for configuration files
site-config.jam user-config.jam
Linux /etc
$HOME
$BOOST_BUILD_PATH
$HOME
$BOOST_BUILD_PATH
Windows $SystemRoot
$HOME
$BOOST_BUILD_PATH
$HOME
$BOOST_BUILD_PATH
Boost.Build comes with default versions of those files, which can serve as templates for customized versions.

Command line

The command line may contain:

Command line arguments

Command line arguments specify targets and build request using the following rules. For example, the command line
    target1 debug gcc/runtime-link=dynamic,static
   
would cause target called target1 to be rebuild in debug mode, except that for gcc, both dynamically and statically linked binaries would be created.

Command line options

All of the Boost.Build options start with the "--" prefix. They are described in the following table.

Command line options
Option Description
--version Prints information on Boost.Build and Boost.Jam versions.
--help Access to the online help system. This prints general information on how to use the help system with additional --help* options.
--clean Removes everything instead of building. Unlike clean target in make, it is possible to clean only some targets.
--debug Enables internal checks.
--dump-projects Cause the project structure to be output.
--no-error-backtrace Don't print backtrace on errors. Primary usefull for testing.
--ignore-config Do not load site-config.jam and user-config.jam

Build request

Build process

Construction of each main target begins with finding properties for this main target. They are found by processing both build request, and target requirements, which give properties needed for the target to build. For example, a given main target might require certian defines, or will not work unless compiled in multithreaded mode. The process of finding properties for main target is described in property refinement.

After that, dependencies (i.e. other main targets) are build recursively. Build request for dependencies is not always equal to those of dependent — certain properties are dropped and user can explicitly specify desired properties for dependencies. See propagated features and target reference for details.

When dependencies are constructed, the dependency graph for this main target and for this property set is created, which describes which files need to be created, on which other files they depend and what actions are needed to construct those files. There's more that one method, and user can define new ones, but usually, this involves generators and target types.

Target type is just a way to classify targets. For example, there are builtin types EXE, OBJ and CPP. Generators are objects that know how to convert between different target type. When a target of a given type must be created, all generators for that type, which can handle needed properties, are found. Each is passed the list of sources, and either fails, or returns a dependency graph. If a generator cannot produce desired type from given sources, it may try to recursively construct types that it can handle from the types is was passed. This allows to try all possible transformations. When all generators are tried, a dependency graph is selected.

Finally, the dependency graph is passed to underlying Boost.Jam program, which runs all actions needed to bring all main targets up-to date. At this step, implicit dependencies are also scanned and accounted for, as described here.

Given a list of targets ids and a build request, building goes this way. First, for each id we obtain the abstract targets corresponding to it. This also loads all necessary projects. If no target id is given, project in the current directory is used. Build request is expanded, and for each resulting property set, the generate method of all targets is called, which yields a list of virtual targets. After that all virtual targets are actualized, and target "all" is set to depend on all created actual targets. Lastly, depending on whether --clean option was given, either target "all" or target "clean" is updated. Generation of virtual target from abstract one is performed as follows:

Alternative selection

When there are several alternatives, one of them must be selected. The process is as follows:

  1. For each alternative condition is defined as the set of base properies in requirements. [Note: it might be better to explicitly specify the condition explicitly, as in conditional requirements].
  2. An alternative is viable only if all properties in condition are present in build request.
  3. If there's one viable alternative, it's choosen. Otherwise, an attempt is made to find one best alternative. An alternative a is better than another alternative b, iff set of properties in b's condition is stict subset of the set of properities of 'a's condition. If there's one viable alternative, which is better than all other, it's selected. Otherwise, an error is reported.

Generated headers

Usually, Boost.Build handles implicit dependendies completely automatically. For example, for C++ files, all #include statements are found and handled. The only aspect where user help might be needed is implicit dependency on generated files.

By default, Boost.Build handles such dependencies within one main target. For example, assume that main target "app" has two sources, "app.cpp" and "parser.y". The latter source is converted into "parser.c" and "parser.h". Then, if "app.cpp" includes "parser.h", Boost.Build will detect this dependency. Moreover, since "parser.h" will be generated into a build directory, the path to that directory will automatically added to include path.

Making this mechanism work across main target boundaries is possible, but imposes certain overhead. For that reason, if there's implicit dependency on files from other main targets, the <implicit-dependency> feature must be used, for example:

    lib parser : parser.y ;
    exe app : app.cpp : <implicit-dependency>parser ;
   
The above example tells the build system that when scanning all sources of "app" for implicit-dependencies, it should consider targets from "parser" as potential dependencies.

Generators

To construct a main target with given properties from sources, it is required to create a dependency graph for that main target, which will also include actions to be run. The algorithm for creating the dependency graph is described here.

The fundamental concept is generator. If encapsulates the notion of build tool and is capable to converting a set of input targets into a set of output targets, with some properties. Generator matches a build tool as closely as possible: it works only when the tool can work with requested properties (for example, msvc compiler can't work when requested toolset is gcc), and should produce exactly the same targets as the tool (for example, if Borland's linker produces additional files with debug information, generator should also).

Given a set of generators, the fundamental operation is to construct a target of a given type, with given properties, from a set of targets. That operation is performed by rule generators.construct and the used algorithm is described below.

Selecting and ranking viable generators

Each generator, in addition to target types that it can produce, have attribute that affects its applicability in particular sitiation. Those attributes are:

  1. Required properties, which are properties absolutely necessary for the generator to work. For example, generator encapsulating the gcc compiler would have <toolset>gcc as required property.
  2. Optional properties, which increase the generators suitability for a particual build.
Generator's required and optional properties may not include either free or incidental properties. (Allowing this would greatly complicate caching targets).

When trying to construct a target, the first step is to select all possible generators for the requested target type, which required properties are a subset of requested properties. Generators which were already selected up the call stack are excluded. In addition, if any composing generators were selected up the call stack, all other composing generators are ignored (TODO: define composing generators). The found generators assigned a rank, which is the number of optional properties present in requested properties. Finally, generators with highest rank are selected for futher processing.

Running generators

When generators are selected, each is run to produce a list of created targets. This list might include targets which are not of requested types, because generators create the same targets as some tool, and tool's behaviour is fixed. (Note: should specify that in some cases we actually want extra targets). If generator fails, it returns an empty list. Generator is free to call 'construct' again, to convert sources to the types it can handle. It also can pass modified properties to 'constuct'. However, a generator is not allowed to modify any propagated properties, otherwise when actually consuming properties we might discover that the set of propagated properties is different from what was used for building sources.

For all targets which are not of requested types, we try to convert them to requested type, using a second call to construct. This is done in order to support transformation sequences where single source file expands to several later. See this message for details.

Selecting dependency graph

After all generators are run, it is necessary to decide which of successfull invocation will be taken as final result. At the moment, this is not done. Instead, it is checked whether all successfull generator invocation returned the same target list. Error is issued otherwise.

Property adjustment

Because target location is determined by the build system, it is sometimes necessary to adjust properties, in order to not break actions. For example, if there's an action which generates a header, say "a_parser.h", and a source file "a.cpp" which includes that file, we must make everything work as if a_parser.h is generated in the same directory where it would be generated without any subvariants.

Correct property adjustment can be done only after all targets are created, so the approach taken is:

  1. When dependency graph is constructed, each action can be assigned a rule for property adjustment.
  2. When virtual target is actualized, that rule is run and return the final set of properties. At this stage it can use information of all created virtual targets.

In case of quoted includes, no adjustment can give 100% correct results. If target dirs are not changed by build system, quoted includes are searched in "." and then in include path, while angle includes are searched only in include path. When target dirs are changed, we'd want to make quoted includes to be search in "." then in additional dirs and then in the include path and make angle includes be searched in include path, probably with additional paths added at some position. Unless, include path already has "." as the first element, this is not possible. So, either generated headers should not be included with quotes, or first element of include path should be ".", which essentially erases the difference between quoted and angle includes. Note: there only way to get "." as include path into compiler command line is via verbatim compiler option. In all other case, Boost.Build will convert "." into directory where it occurs.

Transformations cache

Under certain conditions, an attempt is made to cache results of transformation search. First, the sources are replaced with targets with special name and the found target list is stored. Later, when properties, requested type, and source type are the same, the store target list is retrieved and cloned, with appropriate change in names.

Last modified: Oct 31, 2003

© Copyright Vladimir Prus 2002-2003. Permission to copy, use, modify, sell and distribute this document is granted provided this copyright notice appears in all copies. This document is provided ``as is'' without express or implied warranty, and with no claim as to its suitability for any purpose.