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 "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 here.
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" feautures like <include> See , augments) the target requirements. However, when a contradiction of a target's requrements involves certain link-incompatible features, the target will be skipped. See for more information.
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 software project would be composed of sub-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 additional includes. Many features will be overridden, rather than added-to, in sub-projects. See for more information More details can be found in the section on 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 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> feature is not propagated: its value will be added as-is to compiler flags for "a.cpp", but won't affect "foo". 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 "foo". A better solution is to modify util/foo/Jamfilie in this way: project : usage-requirements <include>. ; lib foo : foo.cpp ; Usage requirements are requirements which are applied to dependents. In this case, <include> will be applied to all targets which use "foo" — i.e. targets which have "foo" either in sources or in dependency properties. You'd need to specify usage requirements only once, and programs which use "foo" don't have to care about include paths any longer. Or course, the path will be interpreted relatively to "util/foo" 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 . 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 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 target "foo" in 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, it 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. 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 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 requirements of the project, 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 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. Suppose your library can be only build statically. This is easily achieved using requirements: lib l : l.cpp : <link>static ; 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 ; 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//bar/<link>static ; exe e10 : e10.cpp /other_project//bar/<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 foo. Here's the solution: alias foo : /other_project//bar/<link>static ; exe e1 : e1.cpp foo ; exe e10 : e10.cpp foo ; Note that the alias rule is specifically used for rename a reference to a target and possibly change the properties.
Conditions and alternatives As we've just figured out, properties can significally affect the way targets are built. The processing of the <link> feature is built in the build system, and is quite complex. But there is a couple of mechanisms which allow ordinary users to do different things depending on properties. The first mechanism is called conditinal requirement. For example, you might want to set specific defines when the library is build as shared, or you have your own define to be used in release mode. Here's a piece of Jamfile. lib network : network.cpp : <link>shared:<define>NEWORK_LIB_SHARED <variant>release:<define>EXTRA_FAST ; This will have exactly the effect we wanted: whenever <link>shared is in properties, <define>NEWORK_LIB_SHARED will be in properties as well. Sometimes different variant of a target are so different, that describing them using conditional requirements would be hard. Imagine that a library has different sources on two supported toolsets, and dummy implementation for all the other toolset. We can express this situation using target alternatives: lib demangler : dummy_demangler.cpp ; lib demangler : demangler_gcc.cpp : <toolset>gcc ; lib demangler : demangler_msvc.cpp : <toolset>msvc ; The proper alternative will be automatically selected.
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 util/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 ../util/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 described in recipes.