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 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 . 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. 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//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.