Skip to content

Latest commit

 

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Pybind11 Learning Project

This project demonstrates how to manually add pybind11 as a Git submodule to expose a simple C++ class and function to Python.

While building the project was straightforward, configuring the IDE was challenging. There are two parts covered in this document:

  • Part 1: Set up and run a Python script to test the imported C++ module.
  • Part 2: Troubleshoot IntelliSense and Pylance dependency issues.

Table of Contents

Part 1:

Setup

Take a look at CMakeLists.txt:

add_subdirectory(extern/pybind11)

This line tells CMake where to find the pybind11 dependency.

  1. Create and activate a virtual environment
python3 -m venv .venv
source .venv/bin/activate
  1. Initialize the repository and add pybind11

Add pybind11 as a Git submodule under extern/

git init
git submodule add -b stable https://github.com/pybind/pybind11 extern/pybind11
git submodule update --init --recursive
  1. Configure and Build
cmake -S . -B build
cmake --build build

After building the project, a .so file should be generated under the build directory.

Run

The test script is located under script/. Run the script with command:

python3 ./script/test*.py

Part 2:

Part 2 walks through the issues in the order they came up while building the project, explaining why each one occurred and how to solve it.

1. C++ Compilation vs. IntelliSense

Even if the project builds successfully with CMake, VS Code may still show red squiggles in the C++ source files. That's because the compiler and IntelliSense are separate tools — IntelliSense needs its own copy of the compiler settings, and doesn't get them automatically. The fix is to have CMake generate that information and feed it to IntelliSense.

Generate compile_commands.json

Add the following to CMakeLists.txt:

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

Then configure the project:

cmake -S . -B build

CMake will generate:

./build/compile_commands.json

The compilation database contains information such as:

-I <include directory>
-D <preprocessor definition>
-std=<C++ standard>
compiler flags
...

This gives IntelliSense the exact compile command for each file, instead of manually duplicating the config.

Configure VS Code IntelliSense

On VS Code (macOS example), create .vscode/c_cpp_properties.json pointing the C/C++ extension at the compilation database:

{
    "configurations": [
        {
            "name": "Mac",
            "compileCommands": "${workspaceFolder}/build/compile_commands.json"
        }
    ],
    "version": 4
}

This keeps CMake as the single source of truth, so there's no need to manually duplicate dependency paths like:

"includePath": [
    "${workspaceFolder}/extern/pybind11",
    "${workspaceFolder}/any/include/path"
]

If IntelliSense still shows errors

If red squiggles remain after configuring compile_commands.json, try the following.

1. Clean and reconfigure the project
rm -rf build
cmake -S . -B build
2. Reload the VS Code window
Cmd + Shift + P
→ Developer: Reload Window

2. Python Import and Pylance

Similarly on the Python side: the compiled module (.so) lives in build/, so the interpreter needs to be told where to find it:

import sys
import os

# Add the build directory to Python's module search path.
sys.path.append(os.path.abspath("./build"))

import cpp_modules as cpp

This works at runtime, but Pylance may still flag import cpp_modules as cpp with a red squiggle — it performs static analysis and doesn't automatically know where the module is located.

Configure Pylance

Create .vscode/settings.json:

{
    "python.analysis.extraPaths": [
        "${workspaceFolder}/build"
    ]
}

This solves module location, but not content — typing cpp. still won't autocomplete, since Pylance can find the .so file but doesn't know its interface.

Generate Python stubs with pybind11-stubgen

pybind11-stubgen inspects the compiled module and generates a .pyi stub describing its interface. With the virtual environment active, install it:

pip install pybind11-stubgen

Run:

PYTHONPATH=./build pybind11-stubgen cpp_modules --output-dir ./build

This generates cpp_modules.pyi in build/: PYTHONPATH=./build lets pybind11-stubgen import the compiled module, and --output-dir ./build places the stub next to it.

How the two pieces work together

settings.json tells Pylance where to look; the .pyi stub tells it what's inside. Miss extraPaths and Pylance won't find the module or stub at all; miss the .pyi and it'll find the module but not its classes, functions, or signatures.

Important: regenerate the stub when bindings change

The .pyi file is a snapshot, not live introspection — whenever a binding changes (e.g. a .def(...) edit), rebuild and regenerate it, or the stub goes stale:

cmake --build build

PYTHONPATH=./build pybind11-stubgen cpp_modules --output-dir ./build

Summary

The project uses three different tools for three different purposes:

Tool Purpose
CMake Configure and build the C++ project
IntelliSense Analyze C++ code and provide completion
Pylance Analyze Python code and provide completion

For C++:

CMakeLists.txt
      │
      ▼
     CMake
      │
      ├──► build system ──► compiler ──► actual program
      │
      └──► compile_commands.json
                       │
                       ▼
                  IntelliSense

For Python:

                     Pylance
                        │
              ┌─────────┴─────────┐
              │                   │
        extraPaths            .pyi stub
              │                   │
              ▼                   ▼
       Where is the         What does the
       module located?      module contain?
              │                   │
              └─────────┬─────────┘
                        ▼
                 Code completion

The main idea is to avoid manually duplicating configuration wherever possible:

Let CMake define how the project is built, and let the language servers consume the information they need from the build artifacts.

About

a quick pybind11 project with examples of exposing a class and a function to Python; together with troubleshooting of dependencies issues

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages