Tutorial
Hello, world The 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-value The 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 Hierarchies So 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 Targets TODO: 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 dependencies The 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 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 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 alternatives As 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 .