View on GitHub

Compiler des binding Python avec CMake et Boost

Ceci une courte notice pour expliquer comment compiler des binding python avec Boost et CMake. Il possible (ou non) d’utiliser les macros du CMake du JRL ou celles de PID.

La lib à binder

Imaginons qu’on veuille binder une librairie appelée libMyLib.so. Le nom du projet CMake est alors MyLib.

Commençons par binder les fonctions.

Bindings

Le fichier pour binder doit être un fichier .cpp, il est appelé ici bindings.cpp et est situé dans le dossier src. On choisit comme nom pour la librairie python pyMyLib Voici le code minimal à avoir

#include <boost/python.hpp>
// Inclure les headers de MyLib

BOOST_PYTHON_MODULE(pyMyLib)
{
    Py_Initialize();

    // Ecrire les bindings ici
}

Si il y a besoin d’utiliser Numpy avec C++ (à partir de Boost 1.63), quelques ajouts au code est nécessaire

#include <boost/python.hpp>
#include <boost/python/numpy.hpp>
// Inclure les headers de MyLib

namespace np = boost::python::numpy;

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

    // Ecrire les bindings ici
}

Il faut aussi écrire le fichier __init__.py dans le même dossier que les bindings. Pour pouvoir utiliser __init__.py avec les versions 2.7 et 3 de Python, il faut ajouter un . quand le mot-clé from est employé.

from .pyMyLib import # Ajouter les class ou fonctions
# ...

CMake basique

On peut maintenant écrire le CMake qui compilera et installera les bindings

Tout d’abord il faut rechercher les package Python et Boost. La variable PY_VERSION devrait valoir soit 2.7.x soit 3.x. On assume ici que la version de Boost désirée est 1.64.0 (à modifier si nécessaire).

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

Ensuite, il faut donner au compilateur des dossier des headers, créer la librairie et linker.

# ajout des headers
include_directories(${PROJECT_SOURCE_DIR}/src)
include_directories(${PYTHON_INCLUDE_DIRS})
include_directories(${Boost_INCLUDE_DIRS})

# creation de la lib
add_library(pyMyLib SHARED src/bindings.cpp)

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

Il est primordial que le nom de la lib (ici pyMyLib) et le nom du module Python (dans BOOST_PYTHON_MODULE(pyMyLib)) coïncident

Il faut maintenant installer __init__.py et la lib.

# Copie le fichier __init__.py
configure_file(__init__.py ${CMAKE_CURRENT_BINARY_DIR}/src/__init__.py COPYONLY)

# Supprime le prefix "lib" car Python ne permet pas ce prefix
set_target_properties(pyMyLib PROPERTIES PREFIX "")

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

JRL CMake

Ça ne change que très peu ici. Il est possible d’utiliser quelques macros.

# rechercher boost et python
set(BOOST_COMPONENTS system python)
SEARCH_FOR_BOOST()
FINDPYTHON()

# Compile et installe __init__.py
PYTHON_INSTALL_BUILD(pyMyLib __init__.py "${PYTHON_INSTALL_PATH}")

PID

PID s’occupe de presque tout faire, il faut juste lui dire ce dont il a besoin. Dans le CMakeLists.txt global

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

Dans le CMakeLists.txt du dossier src

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)
    # python2
    declare_PID_Component_Dependency(
        COMPONENT pyMyLib 
        EXTERNAL boost-python 
        PACKAGE boost
    )
else()
    # python3
    declare_PID_Component_Dependency(
        COMPONENT pyMyLib 
        EXTERNAL boost-python3 
        PACKAGE boost
    )
endif()