This document explains how to extend Boost.Build to accomodate your local requirements. Let's start with a simple but realistic example. Say you're writing an application that generates C++ code. If you ever did this, you know that it's not nice. Embedding large portions of C++ code in string literals is very awkward. A much better solution is:
It's quite easy to achieve. You write special verbatim files that are
just C++, except that the very first line of the file contains the name of a
variable that should be generated. A simple tool is created that takes a
verbatim file and creates a cpp file with a single Let's see what Boost.Build can do. First off, Boost.Build has no idea about "verbatim files". So, you must register a new target type. The following code does it: import type ; type.register VERBATIM : verbatim ; The first parameter to Next, you tell Boost.Build that the verbatim files can be
transformed into C++ files in one build step. A
generator is a template for a build step that
transforms targets of one type (or set of types) into another. Our
generator will be called import generators ; generators.register-standard verbatim.inline-file : VERBATIM : CPP ;
Lastly, you have to inform Boost.Build about the shell
commands used to make that transformation. That's done with an
actions inline-file
{
"./inline-file.py" $(<) $(>)
}
Now, we're ready to tie it all together. Put all the code above in file
exe codegen : codegen.cpp class_template.verbatim usage.verbatim ; The listed verbatim files will be automatically converted into C++ source files, compiled and then linked to the codegen executable.
In subsequent sections, we will extend this example, and review all the
mechanisms in detail. The complete code is available in the
The first thing we did in the intruduction was declaring a new target type: import type ; type.register VERBATIM : verbatim ; The type is the most important property of a target. Boost.Build can automatically generate necessary build actions only because you specify the desired type (using the different main target rules), and because Boost.Build can guess the type of sources from their extensions. The first two parameters for the Sometimes you want to change the suffix used for generated targets
depending on build properties, such as toolset. For example, some compiler
uses extension type.set-generated-target-suffix EXE : <toolset>elf : elf ;
A new target type can be inherited from an existing one. type.register PLUGIN : : SHARED_LIB ;
The above code defines a new type derived from
A type can be defined as "main", in which case Boost.Build will automatically declare a main target rule for building targets of that type. More details can be found later. Sometimes, a file can refer to other files via some include system. To make Boost.Build track dependencies between included files, you need to provide a scanner. The primary limitation is that only one scanner can be assigned to a target type. First, we need to declare a new class for the scanner:
class verbatim-scanner : common-scanner
{
rule pattern ( )
{
return "//###include[ ]*\"([^\"]*)\"" ;
}
}
All the complex logic is in the After that, we need to register our scanner class: scanner.register verbatim-scanner : include ;
The value of the second parameter, in this case
Finally, we assign the new scanner to the type.set-scanner VERBATIM : verbatim-scanner ; That's enough for scanning include dependencies. This section will describe how Boost.Build can be extended to support new tools. For each additional tool, a Boost.Build object called generator
must be created. That object has specific types of targets that it
accepts and produces. Using that information, Boost.Build is able
to automatically invoke the generator. For example, if you declare a
generator that takes a target of the type Each generator should be an instance of a class derived from the
import generators ;
generators.register-standard verbatim.inline-file : VERBATIM : CPP ;
actions inline-file
{
"./inline-file.py" $(<) $(>)
}
We declare a standard generator, specifying its id, the source type
and the target type. When invoked, the generator will create a target
of type
There are two primary kinds of generators: standard and composing,
which are registered with the
generators.register-standard verbatim.inline-file : VERBATIM : CPP ; generators.register-composing mex.mex : CPP LIB : MEX ;
The first (standard) generator takes a single
source of type You should also know about two specific functions for registering
generators: (Need a note about UNIX) Custom generator classesThe standard generators allows you to specify source and target types, an action, and a set of flags. If you need anything more complex, you need to create a new generator class with your own logic. Then, you have to create an instance of that class and register it. Here's an example how you can create your own generator class:
class custom-generator : generator
{
rule __init__ ( * : * )
{
generator.__init__ $(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ;
}
}
generators.register
[ new custom-generator verbatim.inline-file : VERBATIM : CPP ] ;
This generator will work exactly like the
There are two methods of interest. The The
class itrace-generator : generator {
....
rule generated-targets ( sources + : property-set : project name ? )
{
local leaves ;
local temp = [ virtual-target.traverse $(sources[1]) : : include-sources ] ;
for local t in $(temp)
{
if ! [ $(t).action ]
{
leaves += $(t) ;
}
}
return [ generator.generated-targets $(sources) $(leafs)
: $(property-set) : $(project) $(name) ] ;
}
}
generators.register [ new itrace-generator nm.itrace : EXE : ITRACE ] ;
The The
rule run ( project name ? : property-set : sources * )
{
local python ;
for local s in $(sources)
{
if [ $(s).type ] = PY
{
python = $(s) ;
}
}
local libs ;
for local s in $(sources)
{
if [ type.is-derived [ $(s).type ] LIB ]
{
libs += $(s) ;
}
}
local new-sources ;
for local s in $(sources)
{
if [ type.is-derived [ $(s).type ] CPP ]
{
local name = [ $(s).name ] ; # get the target's basename
if $(name) = [ $(python).name ]
{
name = $(name)_ext ; # rename the target
}
new-sources += [ generators.construct $(project) $(name) :
PYTHON_EXTENSION : $(property-set) : $(s) $(libs) ] ;
}
}
result = [ construct-result $(python) $(new-sources) : $(project) $(name)
: $(property-set) ] ;
}
First, we separate all source into python files, libraries and C++
sources. For each C++ source we create a separate Python extension by
calling Often, we need to control the options passed the invoked tools. This is done with features. Consider an example:
# Declare a new free feature
import feature : feature ;
feature verbatim-options : : free ;
# Cause the value of the 'verbatim-options' feature to be
# available as 'OPTIONS' variable inside verbatim.inline-file
import toolset : flags ;
flags verbatim.inline-file OPTIONS <verbatim-options> ;
# Use the "OPTIONS" variable
actions inline-file
{
"./inline-file.py" $(OPTIONS) $(<) $(>)
}
We first define a new feature. Then, the Although you can define any set of features and interpret their values in any way, Boost.Build suggests the following coding standard for designing features. Most features should have a fixed set of values that is portable
(tool neutral) across the class of tools they are designed to work
with. The user does not have to adjust the values for a exact tool. For
example,
Besides such portable features there are special 'raw' features that
allow the user to pass any value to the command line parameters for a
particular tool, if so desired. For example, the
Using portable features is a good idea because:
Steps for adding a feautureAdding a feature requires three steps:
Another exampleHere's another example. Let's see how we can make a feature that refers to a target. For example, when linking dynamic libraries on Windows, one sometimes needs to specify a "DEF file", telling what functions should be exported. It would be nice to use this file like this:
lib a : a.cpp : <def-file>a.def ;
Actually, this feature is already supported, but anyway...
Variants and composite features.Sometimes you want to create a shortcut for some set of
features. For example, It is possible to define your own build variants. For example:
variant crazy : <optimization>speed <inlining>off
<debug-symbols>on <profiling>on ;
will define a new variant with the specified set of properties. You can also extend an existing variant: variant super_release : release : <define>USE_ASM ;
In this case, You are not restricted to using the feature parallelism : mpi fake none : composite link-incompatible ; feature.compose <parallelism>mpi : <library>/mpi//mpi/<parallelism>none ; feature.compose <parallelism>fake : <library>/mpi//fake/<parallelism>none ;
This will allow you to specify the value of feature
A main target rule (e.g “ The first way applies when
your target rule should just produce a target of specific type. In that case, a
rule is already defined for you! When you define a new type, Boost.Build
automatically defines a corresponding rule. The name of the rule is
obtained from the name of the type, by downcasing all letters and
replacing underscores with dashes.
For example, if you create a module
import type ; type.register OBFUSCATED_CPP : ocpp ; import generators ; generators.register-standard obfuscate.file : CPP : OBFUSCATED_CPP ; and import that module, you'll be able to use the rule "obfuscated-cpp" in Jamfiles, which will convert source to the OBFUSCATED_CPP type. The second way is to write a wrapper rule that calls any of the existing rules. For example, suppose you have only one library per directory and want all cpp files in the directory to be compiled into that library. You can achieve this effect using: lib codegen : [ glob *.cpp ] ;
If you want to make it even simpler, you could add the following
definition to the
rule glib ( name : extra-sources * : requirements * )
{
lib $(name) : [ glob *.cpp ] $(extra-sources) : $(requirements) ;
}
allowing you to reduce the Jamfile to just glib codegen ;
Note that because you can associate a custom generator with a target type,
the logic of building can be rather complicated. For example, the
If your extensions will be used only on one project, they can be placed in
a separate The Here are some guidelines that help to make Boost.Build more consistent:
|