Building Python packages with C++ extensions using pybind11 and scikit-build-core in an isolated uv environment can encounter find_package errors for pybind11. This is typically due to an explicit cmake.args configuration overriding scikit-build-core‘s automatic dependency discovery.

The Problem

When developing Python packages containing C++ extensions (e.g., using pybind11) with scikit-build-core and managing environments via uv workspaces, the build process may fail to locate pybind11. This often manifests as CMake errors during the configuration step, specifically Could not find a package configuration file provided by "pybind11". The isolated build environment created by uv is intended to be clean, and scikit-build-core relies on specific mechanisms to find build dependencies like pybind11 which can be disrupted by manual path specifications in pyproject.toml.

The Solution

The primary solution involves ensuring pybind11 is correctly listed in build-system.requires and removing any explicit cmake.args that attempt to manually specify pybind11‘s installation directory. scikit-build-core automatically integrates with Python packages specified in build-system.requires, ensuring CMake can find their configuration files.

For a pyproject.toml located at packages/py/Img2Num_Py/pyproject.toml in a monorepo setup (e.g., with source-dir = "../../"), the optimized configuration is:

# pyproject.toml
[build-system]
requires = ["scikit-build-core>=0.8.1", "pybind11>=2.11.1", "cmake>=3.25", "ninja>=1.11.1"]
build-backend = "scikit_build_core.build_backend"

[project]
name = "Img2Num_Py"
version = "0.0.1"
dependencies = [
    # "numpy", # Include other runtime dependencies as needed
]

[tool.scikit-build]
cmake.args = [
    "-DCMAKE_BUILD_TYPE=Release",
    # Remove or comment out any explicit -Dpybind11_DIR argument.
    # scikit-build-core handles pybind11 discovery automatically.
    # "-DBUILD_TESTING=OFF", # Uncomment if needed
]
source-dir = "../../" # Adjust this path to the root of your CMakeLists.txt
minimum-version = "0.8.1"

# Add other project metadata and tool configurations as required.

Why It Works

  • Automatic Dependency Discovery: When pybind11 is listed in build-system.requires, uv (or pip) installs it into the isolated build environment. scikit-build-core then automatically modifies CMake’s search paths (CMAKE_PREFIX_PATH) to include the Python site-packages directory of this build environment. This allows the find_package(pybind11 CONFIG REQUIRED) command in your CMakeLists.txt to correctly locate pybind11‘s CMake configuration files.
  • Elimination of Fragile Paths: Manually specifying pybind11_DIR via cmake.args with paths like ${PYTHON_PACKAGE_LOCATION}/pybind11/... is often unreliable. Environment variables like PYTHON_PACKAGE_LOCATION are not standard or consistently resolved within an isolated build context or by scikit-build-core itself, leading to incorrect paths and build failures. Relying on scikit-build-core‘s built-in discovery mechanism provides a more robust and portable solution.
  • Correct Tool Installation: Listing cmake and ninja in build-system.requires ensures that scikit-build-core has access to the necessary build tools, either through their Python wrappers (if available) or by ensuring the system’s executables are discoverable. The uv environment ensures all Python build dependencies are correctly isolated and available.

Reference