scikit-build-core - CMake Build Backend
scikit-build-core is a modern, PEP 517/660 compliant build backend for Python projects that use CMake to build native extensions. It streamlines the integration of C, C++, or Fortran code with Python packaging, handling everything from project configuration to wheel and sdist generation. The current version is 0.12.2, with a fairly active release cadence, typically releasing minor bugfix or feature updates every few weeks.
Warnings
- breaking Python 3.7 support has been removed since version 0.11.0. Projects requiring Python 3.7 must pin `scikit-build-core<0.11.0`.
- gotcha The default `sdist.inclusion-mode` changed in 0.12.0 from 'classic' to a new mode that no longer traverses ignored directories unless files are explicitly allowed. This can affect which files are included in your source distributions (sdist).
- breaking PyPI no longer accepts non-normalized SDist names. `scikit-build-core` versions >=0.12.2 now always normalize SDist names, even if `minimum-version` is set to 0.4 or older. Projects with older `minimum-version` settings should still update their `minimum-version` to explicitly support this change and avoid future issues.
- gotcha Python 3.13.4 on Windows is known to have issues with certain build processes. `scikit-build-core` versions >=0.11.5 will warn about this specific Python version.
- gotcha `scikit-build-core` requires CMake and a compatible C/C++ compiler toolchain to be installed and available in the system's PATH. This is not managed by pip.
Install
-
pip install scikit-build-core
Quickstart
mkdir my_cmake_project
cd my_cmake_project
# pyproject.toml
cat <<EOF > pyproject.toml
[build-system]
requires = ["scikit-build-core>=0.12.0", "cmake>=3.15"]
build-backend = "scikit_build_core.build"
[project]
name = "my_package"
version = "0.1.0"
authors = [
{ name="Your Name", email="you@example.com" },
]
description = "A minimal example with scikit-build-core"
readme = "README.md"
requires-python = ">=3.8"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
[tool.scikit-build]
cmake.minimum-version = "3.15"
EOF
# CMakeLists.txt
cat <<EOF > CMakeLists.txt
cmake_minimum_required(VERSION 3.15...3.27)
project(my_package LANGUAGES CXX)
add_library(mylib SHARED mylib.cpp)
install(TARGETS mylib DESTINATION .)
EOF
# mylib.cpp
mkdir -p src
cat <<EOF > src/mylib.cpp
#include <iostream>
extern "C" void greet() {
std::cout << "Hello from C++!" << std::endl;
}
EOF
# my_package/__init__.py
mkdir -p my_package
cat <<EOF > my_package/__init__.py
import os
import sys
from pathlib import Path
# Find the built native library (e.g., _mylib.so, _mylib.pyd)
# This is a common pattern, but scikit-build-core can also inject helpers.
# For a proper package, you'd typically use `from . import _mylib`
# assuming the native library is packaged correctly.
def _load_native_library():
# This simple example assumes the library is in the package root after install
# In a real scenario, use proper packaging tools or scikit-build-core's
# built-in module finder to locate and load.
# For demonstration, we'll simulate direct loading.
try:
# On Linux/macOS, it's typically 'libmylib.so' or 'libmylib.dylib'
# On Windows, it's 'mylib.dll' or '_mylib.pyd'
if sys.platform == 'win32':
_mylib = Path(__file__).parent / 'mylib.dll' # Or '_mylib.pyd'
elif sys.platform == 'darwin':
_mylib = Path(__file__).parent / 'libmylib.dylib'
else:
_mylib = Path(__file__).parent / 'libmylib.so'
# This simple example doesn't actually load the C++ library
# for direct python binding calls, as that requires pybind11/nanobind.
# The greet() function below will just print a Python message.
print(f"[my_package] Simulating loading native library: {_mylib.name}")
except Exception as e:
print(f"[my_package] Could not load native library: {e}")
_load_native_library()
def greet():
print("Hello from Python via my_package!")
EOF
pip install build
python -m build
pip install dist/*.whl --force-reinstall
python -c "from my_package import greet; greet()"