CMake can use a local Boost

One of my current projects relies on Boost.Python, which requires a more recent version of Boost (1.64) than the one (1.54) provided by my Linux distribution (Ubuntu 14.04). My first guess was to remove Boost from my package manager and install its newer version from source... with dramatic consequences! At first, I only experienced a few glitches in ROS and some libraries built with the old version of Boost, but down the road I ran into serious runtime problems that forced me to revert to the system's Boost 1.54.

My second guess was to use a local version of Boost 1.64, compiled but not installed system-wide and only used by the project that needs it. I knew this is possible because the PID framework does this. Without switching to a software source distribution manager, let's see how to change only the CMakeCache.txt configuration to switch from a system-wide to a local Boost install.

Local changes to CMakeCache.txt

I'm assuming you already have your CMakeLists.txt file written and functional. For instance, you can follow these instructions to use Boost.Python with CMake. We won't make any change to the CMakeLists.txt, so that your local Boost install will only affect you and stay transparent for other project users.

To start with, go to your build directory and call ccmake ., or alternatively open CMakeCache.txt in your text editor.

Boost paths

If you use the JRL CMake modules to find Boost automatically, you will already have a number of Boost_* CMake variables. The first thing is to update them to your local path:

Boost_INCLUDE_DIR:PATH=/my/local/path/boost/1.64.0/include
Boost_LIBRARY_DIR:PATH=/my/local/path/boost/1.64.0/lib
Boost_PYTHON_LIBRARY_DEBUG:FILEPATH=/my/local/path/boost/1.64.0/lib/libboost_python.so
Boost_PYTHON_LIBRARY_RELEASE:FILEPATH=/my/local/path/boost/1.64.0/lib/libboost_python.so

If compilation fails with Boost headers not found, add them manually by:

CMAKE_CXX_FLAGS:STRING=-I/my/local/path/boost/1.64.0/include

Possibly the Boost_DIR variable will revert to Boost_DIR-NOTFOUND after you run CMake again. This is not a problem.

Update linker flags

While Boost.Python will be linked with its full path, some libraries like Boost.NumPy may still be linked by a mere -lboost_numpy, in which case the linker will fail to find the local shared object (.so) file. Let's add a -L linker flag to help it here:

CMAKE_SHARED_LINKER_FLAGS:STRING=-L/my/local/path/boost/1.64.0/lib

At this stage, your executable or library should compile and link. You can check that the files produced in your build folder (let's assume a .so here) links properly to your local Boost libraries:

$ ldd ./build/mylib.so
linux-vdso.so.1 =>  (0x0000...)
libboost_numpy.so.1.64.0 => /my/local/path/boost/1.64.0/lib/libboost_numpy.so.1.64.0 (0x0000...)
libboost_python.so.1.64.0 => /my/local/path/boost/1.64.0/lib/libboost_python.so.1.64.0 (0x0000...)

However, you may notice that it is not the case with your installed files:

$ ldd /install/path/mylib.so
    linux-vdso.so.1 =>  (0x00007ffc8f5c7000)
    libboost_numpy.so.1.64.0 => not found
    libboost_python.so.1.64.0 => not found
    ...

This is caused by the different behaviors of CMake between build and install.

A Tale of two RPATHs

RPATH is a list of directories that is directly written into your executable or shared object and tells the linker where to look for libraries. It has precedence over the LD_LIBRARY_PATH system-wide variable, as explained in this page on CMake RPATH handling.

A dirty solution to the "not found" problem would be to add the local Boost 1.64 libraries to LD_LIBRARY_PATH. But then, they would also be visible in other projects trying to link to Boost, which can be problematic. We will rather set RPATH to avoid this and keep things local.

The behavior of CMake with respect to RPATH is controlled by two parameters:

// If set, runtime paths are not added when installing shared libraries,
// but are added when building.
CMAKE_SKIP_INSTALL_RPATH:BOOL=OFF

// If set, runtime paths are not added when using shared libraries.
CMAKE_SKIP_RPATH:BOOL=OFF

In our case, we need both of these values to be off. Plus, we want the linker to behave for the install the way it does for the build, i.e. using full paths to our local Boost libraries. This is specified by the following parameter:

// Use same RPATH at install and build
CMAKE_INSTALL_RPATH_USE_LINK_PATH:BOOL=ON

Once this is set, run make install again. You should see something like:

$ ldd /install/path/mylib.so
linux-vdso.so.1 =>  (0x0000...)
libboost_numpy.so.1.64.0 => /my/local/path/boost/1.64.0/lib/libboost_numpy.so.1.64.0 (0x0000...)
libboost_python.so.1.64.0 => /my/local/path/boost/1.64.0/lib/libboost_python.so.1.64.0 (0x0000...)

On a concluding note

These instructions provide an ad hoc way of linking with local libraries without affecting the CMakeLists.txt of your project (used by other users who don't want to see your local paths on their side). While CMake is the de facto standard today, we are feeling its limitations in such scenarios. The long term solution here is to switch to broader, dependency-handling frameworks such as PID.

Credits: thanks to Kevin Chappellet for suggesting local changes to the CMakeCache.txt and to Robin Passama for walking me through the RPATH :)

Pages of this website are under the CC-BY 4.0 license.