TutorialHello, worldThe simplest project that Boost.Build can construct is
stored in example/hello/ directory. The
project is described by a file
called Jamfile 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 debug variant of the hello>
executable by compiling and
linking hello.cpp. 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 unneccessary
recompilation. Let's extend the example by adding another line
to our project's Jamfile:
exe hello2 : hello.cpp ;
Now we can build both the debug and release variants of our
project:
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
Properties
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-valueThe and 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 .
Build Requests and Target Requirements
The set of properties specified in 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 is typically a combination of the
build request and properties derived from the
project's Jamfiles. For example, the
locations of #included 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>/home/ghost/Work/boost <threading>multi
;
When hello is built, the two
requirements specified above will normally always be present.
If the build request given on the bjam
command-line explictly contradicts a target's requirements,
the command-line usually overrides (or, in the case of
“free” features like <include>,
See
augments) the target requirements.
Project Attributes
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 (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 the same requirement for
both hello and hello2.
Project HierarchiesSo far we've only considered examples with one project
(i.e. with one Jamfile). A typical large
codebase would be composed of many projects organized
into a tree. The top of the tree is called the
project root. Besides a
Jamfile, the project root directory
contains a file called project-root.jam. Every other
Jamfile in the project has a single parent
project, rooted in the nearest parent directory containing a
Jamfile. For example, in the following
directory layout:
top/
|
+-- Jamfile
+-- project-root.jam
|
+-- src/
| |
| +-- Jamfile
| `-- app.cpp
|
`-- util/
|
+-- foo/
. |
. +-- Jamfile
. `-- bar.cpp
the project root is top/. Because there is
no Jamfile in
top/util/, the projects in
top/src/ and
top/util/foo/ are immediate children of the
root project.
Projects inherit all attributes (such as requirements)
from their parents. Inherited requirements are combined with
any requirements specified by the sub-project.
For example, if top/Jamfile has
<include>/home/ghost/local
in its requirements, then all of its sub-projects will have it
in their requirements, too. Of course, any project can add
include paths to those specified by its parents. Many
features will be overridden,
rather than added-to, in sub-projects. See for more
information
More details can be found in
.
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 sub-projects to be built unless the parent project's
Jamfile explicitly requests it. In our
example, top/Jamfile might contain:
build-project src ;
which would cause the project in top/src/
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/src/.
Libraries and Dependent TargetsTODO: need to make this
section consistent with "examples-v2/libraries".
Targets that are “needed” by other targets are called
dependencies of those other targets. The
targets that need the other targets are called
dependent targets.
To get a feeling of target dependencies, let's continue the
above example and see how src/Jamfile can
use libraries from util/foo. Assume
util/foo/Jamfile contains:
lib bar : bar.cpp ;
Then, to use this library in src/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. When linking the
app executable, the appropriate version of
bar will be built and linked in. What do we mean by
“appropriate”? For example, suppose we build app with:
bjam app optimization=full cxxflags=-w-8080
Which properties must be used to build foo? The
answer is that some properties 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
<cxxflags> is not propagated: its value will be
added as-is to the compiler flags for a.cpp, but won't affect
foo. There are still a couple of problems. First, the library
probably has some headers that must be used when compiling
app.cpp. We could manually add the neccessary
#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 use foo—i.e. targets that have foo
either in their sources or in their dependency properties.
You'd need to
specify usage requirements only once, and programs that use foo
don't have to care about include paths any longer.
Of course, the
path will be interpreted relatively to util/foo and will be
adjusted according to the bjam invocation
directory.
For
example, if building from project root, the final compiler
command line will contain .
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 the
project in util/foo:
project foo
: usage-requirements <include>.
;
Second, we use the project id to refer to the library in
src/Jamfile:
exe app : app.cpp /foo//bar ;
The /foo//bar syntax is used to refer to the target bar in
the project with global id /foo (the slash
is used to specify global id).
This way, users of foo do not depend on its location, only
on id, which is supposedly stable. The only thing left is to make
sure that src/Jamfile knows the project id that it uses. We add to
top/Jamfile the following line:
use-project /foo : util/foo ;
Now, all projects can refer to foo using the symbolic
name. If the library is moved somewhere, only a single line in the
top-level Jamfile should be changed.
Library dependenciesThe previous example was simple, but there are often long chains
of dependencies between libraries. For example, the main application might be a thin
wrapper on top of library with core logic, which uses another library of
utility functions, which in turn uses the 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
that 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.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
<source> property. For example, if /boost/filesystem//fs
should be linked to all applications in your project, you can add
<source>/boost/filesystem//fs to the project's requirements, like this:
project
: requirements <source>/boost/filesystem//fs
;
Static and shared libariesWhile 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 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 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 applications that use it might start to behave differently.
Static libraries do not suffer from these problems, but
can considerably increase the size of an application. Before describing
how to use static libraries, it's reasonable to give another, quite simple
approach. If your project is built with
<hardcode-dll-paths>true property, the application
will include the full paths to all shared libraries, eliminating
the above problems.
Unfortunately, once that's done, you can no longer move that 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 just change
the value of <link> feature from it's default value
shared to static. So, to build everything as
static libraries, you'd say
bjam link=static
on the command line.
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.
A library that can only be built statically (or
dynamically) can be described by putting the
<link> feature in its
requirements:
lib l : l.cpp : <link>static ;
If you need 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, 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.
Conditions and alternativesAs we've just seen, properties can significally affect the
way targets are built.
The processing of the <link> feature is
built in, and is quite complex, but
there are a couple
of mechanisms that allow ordinary users to do different things
depending on properties.
The first mechanism is called a conditional
requirement. For example, you might want to set
specific #defines when a library is built as shared,
or when a target's release variant is built in
release mode.
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.
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
In the example above, when built with gcc
or msvc, demangler
will use a source file specific to the toolset. Otherwise, it
will use a generic source file,
dummy_demangler.cpp.
Prebuilt targets
We've just learned how to use libraries that 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. The Jamfile in util/lib2 can contain:
# util/lib2/Jamfile
lib lib2
:
: <file>lib2_release.a <variant>release
;
lib lib2
:
: <file>lib2_debug.a <variant>debug
;
This code
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 library 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,
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 .