🔝 Retour au Sommaire
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.
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 existerSans ce paquet, la compilation échouera avec une erreur du type fatal error: Python.h: No such file or directory.
# 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é # S'assurer que pip est installé et à jour
sudo apt install python3-pip
python3 -m pip install --upgrade pip Quatre méthodes sont courantes. Le choix dépend du contexte du projet.
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.
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/pybind11Dans 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.
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.cmakeDans 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.
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).
| 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 |
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.
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étierpybind11_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.
# 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)# 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# 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))"# Avec pytest (recommandé)
PYTHONPATH=build python3 -m pytest tests/
# Ou directement
PYTHONPATH=build python3 tests/test_bindings.pyTravailler 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.
# 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")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=ONLYLa variable Python3_FIND_VIRTUALENV=ONLY (CMake 3.18+) indique à CMake de ne chercher que dans l'environnement virtuel actif.
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.
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.
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-isolationAprè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.soPour 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.
Pour les pipelines CI/CD et le déploiement, la compilation dans un conteneur Docker garantit la reproductibilité.
# ── 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.
Les headers de développement Python ne sont pas installés.
sudo apt install python3-devCMake 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.12Le .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']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 undefinedLe .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 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.
| É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.