![]() |
Home | Libraries | People | FAQ | More |
This section will guide you though the most basic features of Boost.Build V2. We will start with the “Hello, world” example, learn how to use libraries, and finish with testing and installing features.
The simplest project that Boost.Build can construct is
stored in example/hello/
directory. The
project is described by a file
called Jamroot
that contains:
exe hello : hello.cpp ;
Even with this simple setup, you can do some interesting
things. First of all, just invoking bjam will
build the hello
executable by compiling and
linking hello.cpp
. By default, debug variant
is built. Now, to build the
release variant of hello
, invoke
bjam release
Note that debug and release variants are created in different
directories, so you can switch between variants or even build
multiple variants at once, without any unnecessary
recompilation. Let's extend the example by adding another line
to our project's Jamroot
:
exe hello2 : hello.cpp ;
Now let us build both the debug and release variants of our project again:
bjam debug release
Note that two variants of hello2
are linked.
Since we have already built both variants
of hello
, hello.cpp won't be recompiled;
instead the existing object files will just be linked into the
corresponding variants of hello2
. Now
let's remove all the built products:
bjam --clean debug release
It's also possible to build or clean specific targets. The
following two commands, respectively, build or clean only the
debug version of hello2
.
bjam hello2 bjam --clean hello2
To portably represent aspects of target configuration such as
debug and release variants, or single- and multi-threaded
builds, Boost.Build uses features with
associated values. For
example, the debug-symbols
feature can have a value of on
or
off
. A property is just a (feature,
value) pair. When a user initiates a build, Boost.Build
automatically translates the requested properties into appropriate
command-line flags for invoking toolset components like compilers
and linkers.
There are many built-in features that can be combined to
produce arbitrary build configurations. The following command
builds the project's release
variant with inlining
disabled and debug symbols enabled:
bjam release inlining=off debug-symbols=on
Properties on the command-line are specified with the syntax:
feature-name
=feature-value
The release
and debug
that we've seen
in bjam invocations are just a shorthand way to
specify values of the variant
feature. For example, the command
above could also have been written this way:
bjam variant=release inlining=off debug-symbols=on
variant
is so commonly-used that it has
been given special status as an implicit
feature—Boost.Build will deduce the its identity just
from the name of one of its values.
A complete description of features can be found in the section called “Features and properties”.
The set of properties specified on the command line constitute
a build request—a description of
the desired properties for building the requested targets (or,
if no targets were explicitly requested, the project in the
current directory). The actual
properties used for building targets are typically a
combination of the build request and properties derived from
the project's Jamroot
(and its other
Jamfiles, as described in the section called “Project Hierarchies”). For example, the
locations of #include
d header files are normally
not specified on the command-line, but described in
Jamfiles as target
requirements and automatically combined with the
build request for those targets. Multithread-enabled
compilation is another example of a typical target
requirement. The Jamfile fragment below
illustrates how these requirements might be specified.
exe hello : hello.cpp : <include>boost <threading>multi ;
When hello
is built, the two
requirements specified above will always be present.
If the build request given on the bjam
command-line explictly contradicts a target's requirements,
the target requirements usually override (or, in the case of
“free”” features like
<include>
,
[5]
augments) the build request.
![]() |
Tip |
---|---|
The value of the |
If we want the same requirements for our other
target, hello2
, we could simply duplicate
them. However, as projects grow, that approach leads to a great
deal of repeated boilerplate in Jamfiles.
Fortunately, there's a better way. Each project 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 the same requirement for
both hello
and hello2
.
So far we've only considered examples with one project
—a. with one user-written Boost.Jam file,
Jamroot
). A typical large codebase would be
composed of many projects organized into a tree. The top of the
tree is called the project root. Every
subproject is defined by a file called
Jamfile
in a descendant directory of the
project root. The parent project of a subproject is defined by
the nearest Jamfile
or
Jamroot
file in an ancestor directory. For
example, in the following directory layout:
top/ | +-- Jamroot | +-- app/ | | | +-- Jamfile | `-- app.cpp | `-- util/ | +-- foo/ . | . +-- Jamfile . `-- bar.cpp
the project root is top/
. The projects in
top/app/
and
top/util/foo/
are immediate children of the
root project.
![]() |
Note |
---|---|
When we refer to a “Jamfile,” set in normal
type, we mean a file called either
|
Projects inherit all attributes (such as requirements)
from their parents. Inherited requirements are combined with
any requirements specified by the subproject.
For example, if top/Jamroot
has
<include>/home/ghost/local
in its requirements, then all of its subprojects will have it in their requirements, too. Of course, any project can add include paths to those specified by its parents. [6] More details can be found in the section called “Projects”.
Invoking bjam without explicitly specifying
any targets on the command line builds the project rooted in the
current directory. Building a project does not automatically
cause its subprojects to be built unless the parent project's
Jamfile explicitly requests it. In our example,
top/Jamroot
might contain:
build-project app ;
which would cause the project in top/app/
to be built whenever the project in top/
is
built. However, targets in top/util/foo/
will be built only if they are needed by targets in
top/
or top/app/
.
When a building a target X
depends on first
building another target Y
(such as a
library that must be linked with X),
Y
is called a
dependency of X
and
X
is termed a
dependent of Y
.
To get a feeling of target dependencies, let's continue the
above example and see how top/app/Jamfile
can
use libraries from top/util/foo
. If
top/util/foo/Jamfile
contains
lib bar : bar.cpp ;
then to use this library in top/app/Jamfile
, we can
write:
exe app : app.cpp ../util/foo//bar ;
While app.cpp
refers to a regular source file,
../util/foo//bar
is a reference to another target:
a library bar
declared in the Jamfile at
../util/foo
.
![]() |
Tip |
---|---|
Some other build system have special syntax for listing dependent
libraries, for example |
Suppose we build app
with:
bjam app optimization=full define=USE_ASM
Which properties will be used to build foo
? The answer is
that some features are
propagated—Boost.Build attempts to use
dependencies with the same value of propagated features. The
<optimization>
feature is propagated, so both
app
and foo
will be compiled
with full optimization. But <define>
is not
propagated: its value will be added as-is to the compiler flags for
a.cpp
, but won't affect foo
.
Let's improve this project further.
The library
probably has some headers that must be used when compiling
app.cpp
. We could manually add the necessary
#include
paths to app
's
requirements as values of the
<include>
feature, but then this work will
be repeated for all programs
that use foo
. A better solution is to modify
util/foo/Jamfile
in this way:
project : usage-requirements <include>. ; lib foo : foo.cpp ;
Usage requirements are applied not to the target being declared
but to its
dependents. In this case, <include>.
will be applied to all
targets that directly depend on foo
.
Another improvement is using symbolic identifiers to refer to
the library, as opposed to Jamfile
location.
In a large project, a library can be used by many targets, and if
they all use Jamfile
location,
a change in directory organization entails much work.
The solution is to use project ids—symbolic names
not tied to directory layout. First, we need to assign a project id by
adding this code to
Jamroot
:
use-project /library-example/foo : util/foo ;
Second, we modify app/Jamfile
to use the
project id:
exe app : app.cpp /library-example/foo//bar ;
The /library-example/foo//bar
syntax is used
to refer to the target bar
in
the project with id /library-example/foo
.
We've achieved our goal—if the library is moved to a different
directory, only Jamroot
must be modified.
Note that project ids are global—two Jamfiles are not
allowed to assign the same project id to different directories.
![]() |
Tip |
---|---|
If you want all applications in some project to link
to a certain library, you can avoid having to specify it directly the sources of every
target by using the
project : requirements <source>/boost/filesystem//fs ; |
Libraries can be either static, which means they are included in executable files that 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 create and use both kinds.
The kind of library produced from a lib
target is
determined by the value of the link
feature. Default
value is shared
, and to build static library, the value
should be static
. You can either requiest static build
on the command line:
bjam link=static
or in the library's requirements:
lib l : l.cpp : <link>static ;
We can also use the <link>
property
to express linking requirements on a per-target basis.
For example, if a particular executable can be correctly built
only with the static version of a library, we can qualify the
executable's target
reference to the library as follows:
exe important : main.cpp helpers/<link>static ;
No matter what arguments are specified on the bjam
command line, important
will only be linked with
the static version of helpers
.
Specifying properties in target references is especially useful if you use a library defined in some other project (one you can't change) but you still want static (or dynamic) linking to that library in all cases. If that library is used by many targets, you could use target references everywhere:
exe e1 : e1.cpp /other_project//bar/<link>static ; exe e10 : e10.cpp /other_project//bar/<link>static ;
but that's far from being convenient. A better approach is
to introduce a level of indirection. Create a local
alias target that refers to the static (or
dynamic) version of foo
:
alias foo : /other_project//bar/<link>static ; exe e1 : e1.cpp foo ; exe e10 : e10.cpp foo ;
The alias
rule is specifically used to rename a reference to a target and possibly
change the properties.
![]() |
Tip |
---|---|
When one library uses another, you put the second library in the source list of the first. For example: lib utils : utils.cpp /boost/filesystem//fs ; lib core : core.cpp utils ; exe app : app.cpp core ;
This works no matter what kind of linking is used. When
|
![]() |
Note |
---|---|
(Note for non-UNIX system). Typically, shared libraries must be
installed to a directory in the dynamic linker's search
path. Otherwise, applications that use shared libraries can't be
started. On Windows, the dynamic linker's search path is given by the
|
Sometimes, particular relationships need to be maintained
among a target's build properties. For example, you might want to set
specific #define
when a library is built as shared,
or when a target's release
variant is built.
This can be achieved with conditional requirements.
lib network : network.cpp
: <link>shared:<define>NEWORK_LIB_SHARED
<variant>release:<define>EXTRA_FAST
;
In the example above, whenever network
is
built with <link>shared
,
<define>NEWORK_LIB_SHARED
will be in its
properties, too. Also, whenever its release variant is built,
<define>EXTRA_FAST
will appear in its properties.
Sometimes the ways a target is built are so different that describing them using conditional requirements would be hard. For example, imagine that a library actually uses different source files depending on the toolset used to build it. We can express this situation using target alternatives:
lib demangler : dummy_demangler.cpp ; # alternative 1 lib demangler : demangler_gcc.cpp : <toolset>gcc ; # alternative 2 lib demangler : demangler_msvc.cpp : <toolset>msvc ; # alternative 3
When building demangler
, Boost.Build will compare
requirements for each alternative with build properties to find the best match.
For example, when building with with <toolset>gcc
alternative 2, will be selected, and when building with
<toolset>msvc
alternative 3 will be selected. In all other
cases, the most generic alternative 1 will be built.
To link to libraries whose build instructions aren't given in a Jamfile,
you need to create lib
targets with an appropriate
file
property. Target alternatives can be used to
associate multiple library files with a single conceptual target. For
example:
# util/lib2/Jamfile lib lib2 : : <file>lib2_release.a <variant>release ; lib lib2 : : <file>lib2_debug.a <variant>debug ;
This example defines two alternatives for 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.
Once a prebuilt target has been declared, it can be used just like any other target:
exe app : app.cpp ../util/lib2//lib2 ;
As with any target, the alternative selected depends on the
properties propagated from lib2
's dependents.
If we build the the release and debug versions of app
will be linked
with lib2_release.a
and lib2_debug.a
, respectively.
System libraries—those that are automatically found by the toolset by searching through some set of predetermined paths—should be declared almost like regular ones:
lib pythonlib : : <name>python22 ;
We again don't specify any sources, but give a
name
that should be passed to the
compiler. If the gcc toolset were used to link an executable
target to pythonlib
, -lpython22
would appear in the command line (other compilers may use
different options).
We can also specify where the toolset should look for the library:
lib pythonlib : : <name>python22 <search>/opt/lib ;
And, of course, target alternatives can be used in the usual way:
lib pythonlib : : <name>python22 <variant>release ; lib pythonlib : : <name>python22_d <variant>debug ;
A more advanced use of prebuilt targets is described in the section called “Targets in site-config.jam”.
[6] Many features will be overridden, rather than added-to, in subprojects. See the section called “Feature Attributes” for more information