28.2. Optional Dependencies Example

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.