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 flags
invocation
says that whenever verbatin.inline-file action is run, the value of
the verbatim-options
feature will be added to the
OPTIONS
variable, and can be used inside the action body.
You'd need to consult online help (--help) to find all the features of
the toolset.flags
rule.
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, <optimization>speed
has the same meaning for
all C++ compilers and the user does not have to worry about the exact
options passed to the compiler's command line.
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
<cxxflags>
feature allows you to pass any command line
options to a C++ compiler. The <include>
feature
allows you to pass any string preceded by -I
and the interpretation
is tool-specific. (See the section called “
Can I get capture external program output using a Boost.Jam variable?
” for an example of very smart usage of that
feature). Of course one should always strive to use portable
features, but these are still be provided as a backdoor just to make
sure Boost.Build does not take away any control from the user.
Using portable features is a good idea because:
When a portable feature is given a fixed set of values, you can build your project with two different settings of the feature and Boost.Build will automatically use two different directories for generated files. Boost.Build does not try to separate targets built with different raw options.
Unlike with “raw” features, you don't need to use specific command-line flags in your Jamfile, and it will be more likely to work with other tools.
Adding a feature requires three steps:
Declaring a feature. For that, the "feature.feature" rule is used. You have to decide on the set of feature attributes:
if you want a feature value set for one target to automaticaly propagate to its dependant targets then make it “propagated”.
if a feature does not have a fixed list of
values, it must be “free.” For example, the include
feature is a free feature.
if a feature is used to refer to a path relative
to the Jamfile, it must be a “path” feature. Such features will
also get their values automatically converted to Boost.Build's
internal path representation. For example, include
is a path feature.
if feature is used to refer to some target, it must be a “dependency” feature.
Representing the feature value in a
target-specific variable. Build actions are command
templates modified by Boost.Jam variable expansions. The
toolset.flags
rule sets a target-specific
variable to the value of a feature.
Using the variable. The variable set in step 2 can be used in a build action to form command parameters or files.
Here'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...
Since the feature refers to a target, it must be "dependency".
feature def-file : : free dependency ;
One of the toolsets that cares about DEF files is msvc. The following line should be added to it.
flags msvc.link DEF_FILE <def-file> ;
Since the DEF_FILE variable is not used by the msvc.link action, we need to modify it to be:
actions link bind DEF_FILE { $(.LD) .... /DEF:$(DEF_FILE) .... }
Note the bind DEF_FILE
part. It tells
Boost.Build to translate the internal target name in
DEF_FILE
to a corresponding filename in
the link
action. Without it the expansion of
$(DEF_FILE)
would be a strange symbol that is
not likely to make sense for the linker.
We are almost done, except for adding the follwing code to msvc.jam
:
rule link { DEPENDS $(<) : [ on $(<) return $(DEF_FILE) ] ; }
This is a workaround for a bug in Boost.Build engine, which will hopefully be fixed one day.
Sometimes you want to create a shortcut for some set of
features. For example, release
is a value of
<variant>
and is a shortcut for a set of features.
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, super_release
will expand to all properties
specified by release
, and the additional one you've specified.
You are not restricted to using the variant
feature
only.
Here's example that defines a brand new feature:
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
parallelism
, which will expand to link to the necessary
library.