To illustrate optional dependencies, we have a very simple C++
program that calls a function called xdriver
if the xdriver
build item, which supplies
it, is present. The tree containing this build item can be found
at optional-dep/prog
. Here is its
Abuild.conf
file:
optional-dep/prog/Abuild.conf
tree-name: system tree-deps: xdrivers -optional name: prog platform-types: native deps: xdriver -optional
Observe that there is an optional tree dependency declared on a
build tree called xdrivers
and also an
optional item dependency declared on the build item called
xdriver
.
In the implementation of this build item, we use the
Abuild.interface
file to define a
preprocessor symbol if the xdriver
build
item is present. Here is the
Abuild.interface
file:
optional-dep/prog/Abuild.interface
if ($(ABUILD_HAVE_OPTIONAL_DEP_xdriver)) XCPPFLAGS = -DHAVE_XDRIVER endif
This is one approach, but it is by no means the only approach.
Use of a preprocessor symbol in this way can be dangerous because
there is no mechanism to trigger a rebuild if its value changes.
However, as the presence of absence of optional dependencies is
likely to be relatively fixed for any given build environment,
use of a preprocessor symbol may be appropriate. Since the
interface variable is, like all interface variables, exported to
the backend, we could have also done something based on its value
in the Abuild.mk
file. There, the value
would have a value of either 1
or
0
as with all boolean interface variables.
This would be appropriate if we didn't want the results of
whatever we do to be visible to our dependencies. In this case,
we use the Abuild.interface
file, and the
Abuild.mk
file looks completely normal:
optional-dep/prog/Abuild.mk
TARGETS_bin := prog SRCS_bin_prog := prog.cc RULES := ccxx
Let's look at the source code to the program. It's not clever at
all, but it illustrates how this mechanism works. Here is
prog.cc
:
optional-dep/prog/prog.cc
#ifdef HAVE_XDRIVER # include <xdriver.hh> #endif #include <iostream> int main() { std::cout << 3 << " = " << 3 << std::endl; #ifdef HAVE_XDRIVER std::cout << "xdriver(3) = " << xdriver(3) << std::endl; #else std::cout << "xdriver not available" << std::endl; #endif return 0; }
To build this without the optional build tree present, copy the
file optional-dep/Abuild.conf.without
to
optional-dep/Abuild.conf
. Here is that
file:
optional-dep/Abuild.conf.without
child-dirs: prog
Then run abuild from the
optional-dep/prog
directory. This results
in the following output:
optional-without.out
abuild: build starting abuild: prog (abuild-<native>): all make: Entering directory `--topdir--/optional-dep/prog/abuild-<native>' Compiling ../prog.cc as C++ Creating prog executable make: Leaving directory `--topdir--/optional-dep/prog/abuild-<native>' abuild: build complete
The resulting prog executable produces this output:
optional-without-run.out
3 = 3 xdriver not available
Now let's try this again with the optional tree present. First,
we have to copy
optional-dep/Abuild.conf.with
to
optional-dep/Abuild.conf
. Here is that
file:
optional-dep/Abuild.conf.with
child-dirs: prog xdriver
This adds the xdriver
directory as a child.
This directory contains what are effectively “extra
drivers” to be used by prog. Here are
the header and source to the xdriver
function:
optional-dep/xdriver/xdriver.hh
#ifndef __XDRIVER_HH__ #define __XDRIVER_HH__ int xdriver(int); #endif // __XDRIVER_HH__
optional-dep/xdriver/xdriver.cc
#include <xdriver.hh> int xdriver(int val) { return val * val; }
Next, we have to do a clean build since, as pointed out above,
there's no other mechanism for abuild to notice that the tree
has appeared and the preprocessor symbol has since the last
build. (We could implement a dependency on a make variable if we
wanted to. See Section 22.5, “Dependency on a Make Variable”
for an example of doing this.) Once we have set up the new
Abuild.conf
and run abuild -c
all to clean the tree, we can run
abuild from prog
again.
This results in the following abuild output:
optional-with.out
abuild: build starting abuild: xdriver (abuild-<native>): all make: Entering directory `--topdir--/optional-dep/xdriver/abuild-<native>' Compiling ../xdriver.cc as C++ Creating xdriver library make: Leaving directory `--topdir--/optional-dep/xdriver/abuild-<native>' abuild: prog (abuild-<native>): all make: Entering directory `--topdir--/optional-dep/prog/abuild-<native>' Compiling ../prog.cc as C++ Creating prog executable make: Leaving directory `--topdir--/optional-dep/prog/abuild-<native>' abuild: build complete
Running the resulting prog program in this case results in this output:
optional-with-run.out
3 = 3 xdriver(3) = 9
This time, you can see that the xdriver
function was available.