Skip to content

Latest commit

 

History

History
564 lines (390 loc) · 18.9 KB

File metadata and controls

564 lines (390 loc) · 18.9 KB

🔝 Retour au Sommaire

43.2.1 — Installation et configuration

Module 15 : Interopérabilité · Niveau Expert


Introduction

pybind11 est une bibliothèque header-only : elle ne nécessite pas de compilation préalable ni de linkage avec un binaire pybind11. La seule dépendance externe est l'interpréteur Python et ses headers de développement. L'installation consiste donc essentiellement à rendre les headers pybind11 disponibles pour le compilateur C++ et à configurer CMake pour qu'il localise correctement Python.

Cette simplicité apparente masque cependant plusieurs points de friction courants : incompatibilité entre la version Python du système et celle d'un environnement virtuel, headers de développement manquants, mauvaise détection par CMake, ou mélange entre plusieurs installations Python. Cette section détaille chaque méthode d'installation et les pièges associés.


43.2.1.1 — Prérequis système sur Ubuntu

Headers de développement Python

pybind11 a besoin des headers CPython (Python.h) pour compiler les modules d'extension. Sur Ubuntu, ces headers ne sont pas installés par défaut — seul l'interpréteur l'est.

# Installer les headers de développement pour Python 3
sudo apt update  
sudo apt install python3-dev  

# Vérifier que Python.h est accessible
python3 -c "import sysconfig; print(sysconfig.get_path('include'))"
# /usr/include/python3.12  (le chemin exact dépend de la version)

ls $(python3 -c "import sysconfig; print(sysconfig.get_path('include'))")/Python.h
# Doit exister

Sans ce paquet, la compilation échouera avec une erreur du type fatal error: Python.h: No such file or directory.

Compilateur et outils de build

# Si ce n'est pas déjà fait (cf. chapitre 2)
sudo apt install build-essential cmake ninja-build

# Vérifier les versions
g++ --version      # GCC 15+ recommandé  
cmake --version    # CMake 3.20+ requis pour FindPython, 3.24+ recommandé  
python3 --version  # Python 3.8+ requis, 3.10+ recommandé  

pip (pour certaines méthodes d'installation)

# S'assurer que pip est installé et à jour
sudo apt install python3-pip  
python3 -m pip install --upgrade pip  

43.2.1.2 — Méthodes d'installation de pybind11

Quatre méthodes sont courantes. Le choix dépend du contexte du projet.

Méthode 1 : CMake FetchContent (recommandée pour les projets CMake)

C'est la méthode la plus propre pour un projet CMake. pybind11 est téléchargé et intégré automatiquement au moment de la configuration, sans installation préalable et sans polluer le système.

cmake_minimum_required(VERSION 3.24)  
project(myproject LANGUAGES CXX)  

include(FetchContent)

FetchContent_Declare(
    pybind11
    GIT_REPOSITORY https://github.com/pybind/pybind11.git
    GIT_TAG        v2.13.6   # Dernière version stable — vérifier sur GitHub
    GIT_SHALLOW    TRUE
)
FetchContent_MakeAvailable(pybind11)

Avantages : aucune dépendance externe, version épinglée et reproductible, fonctionne immédiatement dans tout environnement CI/CD.

Inconvénient : le premier cmake --preset ou cmake -B build télécharge le dépôt (quelques secondes). Ensuite, il est mis en cache dans le répertoire de build.

Méthode 2 : Installation via pip

pybind11 est disponible comme paquet Python. L'installation via pip rend les headers disponibles et fournit un helper CMake pour les localiser.

# Installation dans l'environnement Python actif
python3 -m pip install pybind11

# Vérifier l'installation
python3 -m pybind11 --cmakedir
# /home/user/.local/lib/python3.12/site-packages/pybind11/share/cmake/pybind11

Dans CMake, on utilise ensuite find_package pour localiser pybind11 via le chemin que pip a installé :

cmake_minimum_required(VERSION 3.24)  
project(myproject LANGUAGES CXX)  

# Trouver pybind11 installé par pip
find_package(pybind11 REQUIRED)

Si CMake ne trouve pas pybind11 automatiquement, on peut lui indiquer le chemin :

cmake -B build -DCMAKE_PREFIX_PATH=$(python3 -m pybind11 --cmakedir)

Avantage : installation rapide en une commande, partage avec l'écosystème Python.

Inconvénient : la version dépend de l'environnement Python actif. Si le projet est compilé dans un contexte différent (CI, collègue, conteneur Docker), la version peut varier.

Méthode 3 : Paquet système apt

Ubuntu fournit un paquet pybind11 dans ses dépôts :

sudo apt install pybind11-dev

# Vérifier
dpkg -L pybind11-dev | grep cmake
# /usr/share/cmake/pybind11/pybind11Config.cmake

Dans CMake :

find_package(pybind11 REQUIRED)

Avantage : aucune configuration supplémentaire, find_package fonctionne directement.

Inconvénient majeur : la version packagée par Ubuntu est souvent en retard de plusieurs versions mineures sur la dernière release. Pour Ubuntu 24.04 LTS, le paquet fournit une version antérieure à la dernière stable. Pour des projets qui exploitent les fonctionnalités récentes de pybind11, préférer FetchContent ou pip.

Méthode 4 : Sous-module Git

Pour les projets qui versionnent leurs dépendances :

git submodule add https://github.com/pybind/pybind11.git extern/pybind11  
git submodule update --init  
add_subdirectory(extern/pybind11)

Avantage : contrôle total de la version, fonctionne hors-ligne après le clone initial.

Inconvénient : alourdit le dépôt Git et nécessite que les contributeurs initialisent les sous-modules (git submodule update --init).

Résumé comparatif

Méthode Version contrôlée Zéro config système Reproductible CI Recommandée
FetchContent Oui (tag Git) Oui Oui Oui
pip Via pip freeze Non (dépend de l'env) Si env verrouillé Oui
apt Non (fixée par Ubuntu) Oui Partielle Non
Sous-module Git Oui (commit) Oui Oui Selon contexte

43.2.1.3 — Configuration CMake complète

Structure de projet recommandée

myproject/
├── CMakeLists.txt              # Racine du projet
├── CMakePresets.json            # Presets de configuration (optionnel)
├── src/
│   └── mylib/
│       ├── processor.h          # Header C++ (code métier)
│       └── processor.cpp        # Implémentation C++ (code métier)
├── python/
│   └── bindings.cpp             # Code de binding pybind11 (séparé)
└── tests/
    └── test_bindings.py         # Tests Python

Le point architectural important est la séparation entre le code métier et le code de binding. La bibliothèque C++ (processor.h / processor.cpp) ne contient aucune référence à pybind11 ni à Python. Le fichier bindings.cpp est la seule couche qui dépend de pybind11. Cette séparation permet de compiler et tester la bibliothèque C++ indépendamment de Python.

CMakeLists.txt complet

cmake_minimum_required(VERSION 3.24)  
project(myproject VERSION 1.0 LANGUAGES CXX)  

# ─── Standard C++ ─────────────────────────────────────────────
set(CMAKE_CXX_STANDARD 20)  
set(CMAKE_CXX_STANDARD_REQUIRED ON)  
set(CMAKE_CXX_EXTENSIONS OFF)  

# ─── Bibliothèque C++ pure (indépendante de Python) ──────────
add_library(mylib
    src/mylib/processor.cpp
)
target_include_directories(mylib PUBLIC src/)

# ─── pybind11 (via FetchContent) ─────────────────────────────
include(FetchContent)  
FetchContent_Declare(  
    pybind11
    GIT_REPOSITORY https://github.com/pybind/pybind11.git
    GIT_TAG        v2.13.6
    GIT_SHALLOW    TRUE
)
FetchContent_MakeAvailable(pybind11)

# ─── Module Python ────────────────────────────────────────────
pybind11_add_module(mymodule                # Nom du module Python
    python/bindings.cpp                     # Code de binding uniquement
)
target_link_libraries(mymodule PRIVATE mylib) # Lien avec le code métier

La fonction pybind11_add_module

pybind11_add_module est une fonction CMake fournie par pybind11 qui remplace add_library(... MODULE ...). Elle configure automatiquement :

  • Le type de target : MODULE (bibliothèque chargeable dynamiquement, pas linkable).
  • Le suffixe du fichier : .cpython-312-x86_64-linux-gnu.so (encodage de la version Python et de la plateforme, selon la convention PEP 3149).
  • Les flags de compilation : optimisation de la taille des bindings (-fvisibility=hidden), stripping des symboles inutiles.
  • Les includes Python et pybind11.
  • Le linkage avec libpython (si nécessaire selon la plateforme).

On n'a pas besoin de configurer manuellement ces détails — c'est un gain de temps considérable et une source d'erreurs en moins.

Options utiles de pybind11_add_module

# Bindings avec optimisation de taille (défaut)
pybind11_add_module(mymodule python/bindings.cpp)

# Bindings sans l'optimisation de taille (utile pour le débogage)
pybind11_add_module(mymodule NO_EXTRAS python/bindings.cpp)

# Bindings avec support des exceptions désactivé (rare)
pybind11_add_module(mymodule THIN_LTO python/bindings.cpp)

43.2.1.4 — Compilation et test rapide

Build avec Ninja (recommandé)

# Configuration
cmake -B build -G Ninja

# Compilation
cmake --build build

# Vérifier le module produit
ls build/*.so
# build/mymodule.cpython-312-x86_64-linux-gnu.so

Vérification immédiate depuis Python

# Ajouter le répertoire de build au PYTHONPATH et tester
cd build  
python3 -c "import mymodule; print('OK')"  

Ou de manière plus portable :

PYTHONPATH=build python3 -c "import mymodule; print(dir(mymodule))"

Lancer les tests Python

# Avec pytest (recommandé)
PYTHONPATH=build python3 -m pytest tests/

# Ou directement
PYTHONPATH=build python3 tests/test_bindings.py

43.2.1.5 — Environnements virtuels Python

Pourquoi utiliser un venv

Travailler dans un environnement virtuel Python (venv) isole les dépendances du projet de l'installation système. C'est particulièrement important pour pybind11 : le module compilé est lié à une version spécifique de Python (version majeure + mineure). Un module compilé pour Python 3.12 ne sera pas chargeable par Python 3.11.

Configuration avec venv

# Créer un environnement virtuel
python3 -m venv .venv

# Activer l'environnement
source .venv/bin/activate

# Installer pybind11 (si méthode pip)
pip install pybind11

# Vérifier que CMake détecte le bon Python
cmake -B build -G Ninja
# -- Found Python3: /path/to/project/.venv/bin/python3 (found version "3.12.x")

Le piège classique : mauvaise version de Python détectée

CMake utilise le module FindPython3 (ou FindPython) pour localiser l'interpréteur et les headers. Si un venv est actif, CMake devrait le détecter automatiquement. Mais dans certains cas (CI, IDE, scripts), le mauvais Python est trouvé.

Pour forcer CMake à utiliser un Python spécifique :

cmake -B build -G Ninja \
    -DPython3_EXECUTABLE=$(which python3) \
    -DPython3_FIND_VIRTUALENV=ONLY

La variable Python3_FIND_VIRTUALENV=ONLY (CMake 3.18+) indique à CMake de ne chercher que dans l'environnement virtuel actif.

Vérification de cohérence

Après la configuration, vérifier dans la sortie CMake que les chemins sont cohérents :

-- Found Python3: /home/user/project/.venv/bin/python3
-- Found Python3 includes: /usr/include/python3.12
-- Found pybind11: /home/user/project/build/_deps/pybind11-src

Le python3 doit pointer vers le venv, et les includes vers les headers de la même version majeure.mineure.


43.2.1.6 — Installation du module dans l'environnement Python

Compiler le module avec CMake produit un .so dans le répertoire de build. Pour l'utiliser sans manipuler PYTHONPATH, on peut l'installer proprement dans l'environnement Python.

Méthode 1 : scikit-build-core (recommandée en 2026)

scikit-build-core est le successeur de scikit-build. Il fournit un backend de build PEP 517 qui intègre CMake dans le workflow standard de packaging Python (pip install .).

pyproject.toml :

[build-system]
requires = ["scikit-build-core>=0.10", "pybind11>=2.13"]  
build-backend = "scikit_build_core.build"  

[project]
name = "mymodule"  
version = "1.0.0"  
requires-python = ">=3.8"  

[tool.scikit-build]
cmake.build-type = "Release"
# Installation dans le venv actif (compile et installe)
pip install .

# Ou en mode développement (symlink, recompile à chaque changement)
pip install -e . --no-build-isolation

Après installation, le module est importable depuis n'importe quel répertoire :

python3 -c "import mymodule; print(mymodule.__file__)"
# /home/user/project/.venv/lib/python3.12/site-packages/mymodule.cpython-312-x86_64-linux-gnu.so

Méthode 2 : setup.py avec pybind11.setup_helpers

Pour les projets qui utilisent encore setup.py :

# setup.py
from setuptools import setup  
from pybind11.setup_helpers import Pybind11Extension, build_ext  

ext_modules = [
    Pybind11Extension(
        "mymodule",
        ["python/bindings.cpp", "src/mylib/processor.cpp"],
        include_dirs=["src/"],
        cxx_std=20,
    ),
]

setup(
    name="mymodule",
    version="1.0.0",
    ext_modules=ext_modules,
    cmdclass={"build_ext": build_ext},
)
pip install .

Cette méthode fonctionne mais est moins flexible que scikit-build-core pour les projets CMake complexes (dépendances multiples, sous-projets, options de compilation avancées). Elle convient aux projets simples avec peu de fichiers source.


43.2.1.7 — Configuration dans un environnement Docker

Pour les pipelines CI/CD et le déploiement, la compilation dans un conteneur Docker garantit la reproductibilité.

Dockerfile multi-stage

# ── Stage 1 : Build ──────────────────────────────────────────
FROM ubuntu:24.04 AS builder

RUN apt-get update && apt-get install -y --no-install-recommends \
    g++ cmake ninja-build python3-dev python3-pip python3-venv git \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app  
COPY . .  

RUN cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \
    && cmake --build build

# ── Stage 2 : Runtime ────────────────────────────────────────
FROM ubuntu:24.04

RUN apt-get update && apt-get install -y --no-install-recommends \
    python3 libstdc++6 \
    && rm -rf /var/lib/apt/lists/*

COPY --from=builder /app/build/*.so /usr/local/lib/python3/dist-packages/

# Vérification
RUN python3 -c "import mymodule; print('Module loaded successfully')"

Le stage de build contient tous les outils de compilation. Le stage runtime ne contient que Python et les bibliothèques partagées nécessaires — pas de compilateur, pas de headers, pas de pybind11.


43.2.1.8 — Diagnostic des problèmes courants

Python.h: No such file or directory

Les headers de développement Python ne sont pas installés.

sudo apt install python3-dev

Could NOT find Python3 (CMake)

CMake ne trouve pas l'interpréteur ou les headers Python.

# Vérifier ce que CMake détecte
cmake -B build -G Ninja --log-level=DEBUG 2>&1 | grep -i python

# Forcer le chemin
cmake -B build -G Ninja -DPython3_EXECUTABLE=/usr/bin/python3.12

ModuleNotFoundError: No module named 'mymodule'

Le .so compilé n'est pas dans le PYTHONPATH ou dans un répertoire de site-packages.

# Vérifier que le fichier existe
find build/ -name "*.so" -print

# Vérifier le nom exact attendu par Python
python3 -c "import importlib.machinery; print(importlib.machinery.EXTENSION_SUFFIXES)"
# ['.cpython-312-x86_64-linux-gnu.so', '.abi3.so', '.so']

ImportError: undefined symbol: ...

Un symbole référencé dans le binding n'a pas été lié. Cause habituelle : target_link_libraries(mymodule PRIVATE mylib) est manquant dans CMake.

# Inspecter les symboles manquants
ldd -r build/mymodule*.so 2>&1 | grep undefined

Module compilé pour la mauvaise version de Python

Le .so inclut la version Python dans son nom (cpython-312). Si Python 3.11 essaie de charger un module compilé pour 3.12, il ne le trouvera pas (le nom ne correspond pas).

# Vérifier la version Python utilisée pour la compilation (dans les logs CMake)
grep "Found Python3" build/CMakeCache.txt

# Recompiler si nécessaire avec le bon Python
cmake -B build -G Ninja -DPython3_EXECUTABLE=$(which python3)  
cmake --build build --clean-first  

Temps de compilation longs

pybind11 repose sur des templates lourds. Chaque fichier de binding instancie de nombreux templates de conversion. Pour les projets avec beaucoup de bindings :

# Séparer les bindings en plusieurs fichiers .cpp
pybind11_add_module(mymodule
    python/bindings_core.cpp
    python/bindings_io.cpp
    python/bindings_math.cpp
)

Chaque fichier .cpp peut définir une sous-fonction appelée depuis le PYBIND11_MODULE principal :

// bindings_core.cpp
#include <pybind11/pybind11.h>
namespace py = pybind11;

void bind_core(py::module_& m);      // Déclaré ici  
void bind_io(py::module_& m);        // Implémenté dans bindings_io.cpp  
void bind_math(py::module_& m);      // Implémenté dans bindings_math.cpp  

PYBIND11_MODULE(mymodule, m) {
    bind_core(m);
    bind_io(m);
    bind_math(m);
}

Cela permet la compilation parallèle (Ninja compile les trois fichiers simultanément) et la compilation incrémentale (modifier bindings_math.cpp ne recompile pas les autres).

Activer ccache (section 2.3) réduit encore significativement les temps de recompilation.


Résumé

Étape Commande / Action
Headers Python sudo apt install python3-dev
pybind11 (FetchContent) FetchContent_Declare(pybind11 ...) dans CMake
Target du module pybind11_add_module(mymodule ...)
Lien avec le code métier target_link_libraries(mymodule PRIVATE mylib)
Build cmake -B build -G Ninja && cmake --build build
Test rapide PYTHONPATH=build python3 -c "import mymodule"
Installation propre pip install . avec scikit-build-core

L'installation et la configuration de pybind11 se résument à deux lignes dans CMake (FetchContent + pybind11_add_module) et une dépendance système (python3-dev). Les complications apparaissent aux marges — environnements virtuels, CI Docker, versions multiples de Python — et sont résolues par les techniques de diagnostic décrites ci-dessus.

📎 La section suivante (43.2.2) entre dans le vif du sujet : la syntaxe complète des bindings pour exposer fonctions, classes, propriétés et hiérarchies d'héritage C++ à Python.

⏭️ Exposition de fonctions et classes