The build layer has just four main parts -- metatargets (abstract targets), virtual targets, generators and properties.
Metatargets (see the "targets.jam" module) represent
all the user-defined entities which can be built. The "meta" prefix
signify that they don't really corrspond to files -- depending of
build request, they can produce different set of
files. Metatargets are created when Jamfiles are loaded. Each
metagarget has a
generate method which is given a
property set and produces virtual targets for the passed properties.
Virtual targets (see the "virtual-targets.jam" module) correspond to the atomic things which can be updated -- most typically files.
Properties are just (name, value) pairs, specified
by the user and describing how the targets should be
built. Properties are stored using the
Generators are the objects which encapsulate tools -- they can take a list of source virtual targets and produce new virtual targets from them.
The build process includes those steps:
Top-level code calls the
method of a metatarget with some properties.
The metatarget combines the requested properties
with requirements and passes the result, together with the list
of sources, to the
A generator appropriate for the build properties is
selected and its
run method is
called. The method returns a list of virtual targets
The targets are returned to the top level code. They
are converted into bjam targets (via
virtual-target.actualize) and passed to bjam for building.
There are several classes derived from "abstract-target". The "main-target" class represents top-level main target, the "project-target" acts like container for all main targets, and "basic-target" class is a base class for all further target types.
Since each main target can have several alternatives, all top-level target objects are just containers, referring to "real" main target classes. The type is that container is "main-target". For example, given:
alias a ; lib a : a.cpp : <toolset>gcc ;
we would have one-top level instance of "main-target-class", which will contain one instance of "alias-target-class" and one instance of "lib-target-class". The "generate" method of "main-target" decides which of the alternative should be used, and call "generate" on the corresponding instance.
Each alternative is a instance of a class derived from "basic-target". The "basic-target.generate" does several things that are always should be done:
Determines what properties should be used for building the target. This includes looking at requested properties, requirements, and usage requirements of all sources.
Builds all sources
Computes the usage requirements which should be passes back.
For the real work of constructing virtual target, a new method "construct" is called.
The "construct" method can be implemented in any way by classes derived from "basic-target", but one specific derived class plays the central role -- "typed-target". That class holds the desired type of file to be produces, and calls the generators modules to do the job.
This means that a specific metatarget subclass may avoid using generators at all. However, this is deprecated and we're trying to eliminate all such subsclasses at the moment.
Note that the
build/targets.jam file contains
an UML diagram which might help.
Virtual targets correspond to the atomic things which can be
updated. Each virtual target can be assigned an updating action --
instance of the
action class. The action class, in
turn, contains a list of source targets, properties, and a name of
bjam action block which should be executed.
We try hard to never create equal instances of the
virtual-target class. Each code which creates virtual
targets passes them though the
function, which detects if a target with the same name, sources, and
properties was created. In that case, existing target is returned.
When all virtual targets are produced, they are
"actualized". This means that the real file names are computed, and
the commands that should be run are generated. This is done by the
virtual-target.actualize method and the
action.actualize methods. The first is conceptually
simple, while the second need additional explanation. The commands
in bjam are generated in two-stage process. First, a rule with the
appropriate name (for example
"gcc.compile") is called and is given the names of targets. The rule
sets some variables, like "OPTIONS". After that, the command string
is taken, and variable are substitutes, so use of OPTIONS inside the
command string become the real compile options.
Boost.Build added a third stage to simplify things. It's now possible to automatically convert properties to appropriate assignments to variables. For example, <debug-symbols>on would add "-g" to the OPTIONS variable, without requiring to manually add this logic to gcc.compile. This functionality is part of the "toolset" module.
Note that the
contains an UML diagram which might help.
Above, we noted that metatargets are built with a set of
properties. That set is represented with the
property-set class. An important point is that handling
of property sets can get very expensive. For that reason, we make
sure that for each set of (name, value) pairs only one
property-set instance is created. The
property-set uses extensive caching for all operation,
so most work is avoided. The
property-set.create is the
factory function which should be used to create instances of the