View on GitHub

Building Python bindings with CMake and Boost

This is a short explanation on how to build a boost python binding with CMake. You may or may not use JRL CMake macros or PID macros.

The lib to bind

Let’s say you have a lib called libMyLib.so you want to bind. The CMake project name is defined as MyLib.

Let’s bind the functions.

Bindings

The binding file must be .cpp file, let’s call it bindings.cpp located in a src folder. The library name for python is set as pyMyLib. Here is a snippet that gives the minimum needed code.

#include <boost/python.hpp>
// Include the headers of MyLib

BOOST_PYTHON_MODULE(pyMyLib)
{
    Py_Initialize();

    // Write the bindings here
}

If you want to use numpy with C++ (only available from boost 1.63), here is the code

#include <boost/python.hpp>
#include <boost/python/numpy.hpp>
// Include the headers of MyLib

namespace np = boost::python::numpy;

BOOST_PYTHON_MODULE(pyMyLib)
{
    Py_Initialize();
    np::initialize();

    // Write the bindings here
}

Don’t forget to write the __init__.py file in the same folder as the bindings. To be able to use __init__.py with both python 2.7 and 3 you have to add a . when using the keyword from.

from .pyMyLib import # Add class or functions
# ...

Raw CMake

Let’s now write the CMake that will perform the build and the installation.

First of all you need to find the Python package and Boost packages. The variable PY_VERSION should return either 2.7.x either 3.x. Here it is assumed that the boost version is 1.64.0 (change it if needed).

find_package(PythonLibs ${PY_VERSION} REQUIRED)
find_package(Boost 1.64.0 REQUIRED COMPONENTS system python)

Then you need to make the compiler aware of the header files, create the library and link.

# include directories
include_directories(${PROJECT_SOURCE_DIR}/src)
include_directories(${PYTHON_INCLUDE_DIRS})
include_directories(${Boost_INCLUDE_DIRS})

# create the lib
add_library(pyMyLib SHARED src/bindings.cpp)

# link
target_link_libraries(pyMyLib ${Boost_LIBRARIES} ${PROJECT_NAME})

It is very important that the library name (here pyMyLib) and the python module name (in BOOST_PYTHON_MODULE(pyMyLib)) are the same.

You now need to install the __init__.py and the lib.

# Copy the __init__.py file
configure_file(__init__.py ${CMAKE_CURRENT_BINARY_DIR}/src/__init__.py COPYONLY)

# Suppress prefix "lib" because Python does not allow this prefix
set_target_properties(pyMyLib PROPERTIES PREFIX "")

install(TARGETS pyMyLib __init__.py DESTINATION "${PYTHON_INSTALL_PATH}")

JRL CMake

This is pretty much the same as above. You have some macro you can use to facilitate the writings.

# find boost and python
set(BOOST_COMPONENTS system python)
SEARCH_FOR_BOOST()
FINDPYTHON()

# Compile and install python file
PYTHON_INSTALL_BUILD(pyMyLib __init__.py "${PYTHON_INSTALL_PATH}")

PID

It is a bit simpler for PID since it handle everything itself. In the global CMakeLists.txt

get_PID_Platform_Info(PYTHON PY_VERSION)
find_package(PythonLibs ${PY_VERSION} REQUIRED)
declare_PID_Package_Dependency(PACKAGE boost EXTERNAL VERSION 1.64.0)

In the CMakeLists.txt of the src folder

declare_PID_Component(
    MODULE_LIB
    NAME pyMyLib
    DIRECTORY pyMyLib
)

declare_PID_Component_Dependency(
    COMPONENT pyMyLib
    NATIVE MyLib
)

get_PID_Platform_Info(PYTHON PY_VERSION)

if(PY_VERSION VERSION_LESS 3.0)
    #using python2 to manage python wrappers
    declare_PID_Component_Dependency(
        COMPONENT pyMyLib 
        EXTERNAL boost-python 
        PACKAGE boost
    )
else()
    #using python3 to manage python wrappers
    declare_PID_Component_Dependency(
        COMPONENT pyMyLib 
        EXTERNAL boost-python3 
        PACKAGE boost
    )
endif()