Why should I use scikit-build ?¶
Scikit-build is a replacement for setuptools.Extension that builds extension modules with CMake, providing:
better support for additional compilers and build systems
first-class cross-compilation support
location of dependencies and their associated build requirements
Scikit-build is a lightweight wrapper around the setuptools plugin of
scikit-build-core. Use it when
you have an existing setup.py-based project or need setuptools features;
for new projects, use scikit-build-core directly.
Migrating from scikit-build 0.x¶
Starting with scikit-build 1.0, the classic scikit-build backend was replaced
by the setuptools plugin provided by scikit-build-core. skbuild.setup() is now a
thin wrapper around scikit_build_core.setuptools.wrapper.setup(). Most
projects keep working unchanged:
from skbuild import setupwith thecmake_args,cmake_source_dir,cmake_install_dir,cmake_install_targetandcmake_process_manifest_hookkeyword arguments.The CMake modules shipped with scikit-build, like
find_package(PythonExtensions),find_package(Cython),find_package(NumPy)andfind_package(F2PY). They are now injected intoCMAKE_MODULE_PATHusing scikit-build-core’scmake.moduleentry-point group.The
SKBUILDCMake variable is still set, now to2instead ofTRUE(still truthy).The standard setuptools commands:
build,bdist_wheel,sdist,installandbuild_ext --inplace.The classic
build-systemtable:requires = ["setuptools", "scikit-build", "cmake", "ninja"]withbuild-backend = "setuptools.build_meta". The recommended backend is nowscikit_build_core.setuptools.build_meta, which addscmakeandninjaonly when needed and supports config-settings; see Example of setup.py, CMakeLists.txt and pyproject.toml.
The following changes are breaking:
All scikit-build-specific command line options were removed:
--build-type,-G/--generator,-j N,--cmake-executable,--skip-generator-test,--hide-listing,--force-cmake,--skip-cmake,--install-target, as well as thesetup.py <setuptools args> -- <cmake args> -- <build tool args>triple-section syntax. Use theCMAKE_ARGSandCMAKE_GENERATORenvironment variables, thecmake_argskeyword ofsetup(), or scikit-build-core’s[tool.scikit-build]settings inpyproject.toml(or the equivalentSKBUILD_*environment variables or config-settings) instead. See the scikit-build-core configuration documentation.cmake_with_sdist=Truenow raises an error.cmake_languagesandcmake_minimum_required_versionare accepted but ignored with a warning; the minimum CMake version is configured with thecmake.versionsetting in the[tool.scikit-build]table ofpyproject.toml.The internal Python API is gone; only
skbuild.setupandskbuild.exceptions.SKBuildErrorremain public.skbuild.command.*andskbuild.platform_specificswere removed.skbuild.cmaker,skbuild.constants,skbuild.utilsandskbuild.setuptools_wrapremain importable as deprecated shims that warn on import:skbuild.cmakerkeeps onlyget_cmake_version(),skbuild.constantskeeps onlyCMAKE_INSTALL_DIR()andskbuild_plat_name(), and the last two expose nothing.skbuild.exceptions.SKBuildErroris now an alias of setuptools’SetupError, so it is no longer aRuntimeError; itsSKBuildInvalidFileInstallationErrorandSKBuildGeneratorNotFoundErrorsubclasses were removed.The
_skbuild/<platform>-<pyversion>/build directory is gone; the standard setuptoolsbuild/directories are used instead (CMake builds in an_skbuilddirectory underbuild/temp.*).sdists no longer automatically generate their file manifest from git; provide a
MANIFEST.in(or usesetuptools-scm) like any other setuptools project.Editable installs (
pip install -e .) require settingeditable.mode = "inplace"in the[tool.scikit-build]table ofpyproject.toml. A plainsetup.py build_ext --inplacestill works without configuration.Generators are no longer discovered by probing for Visual Studio and running a language test; CMake’s own default generator selection applies. Set the
CMAKE_GENERATORenvironment variable to override it. See C Runtime, Compiler and Build System Generator.scikit-build now depends on
scikit-build-core[setuptools]at run time; thedistro,wheelandtomlidependencies were dropped.
Basic Usage¶
Example of setup.py, CMakeLists.txt and pyproject.toml¶
The full example code is Here
Make a folder named my_project as your project root folder, and place the
following in your project’s setup.py file:
from skbuild import setup # This line replaces 'from setuptools import setup'
setup(
name="hello-cpp",
version="1.2.3",
description="a minimal example package (cpp version)",
author='The scikit-build team',
license="MIT",
packages=['hello'],
python_requires=">=3.10",
)
Your project now uses scikit-build instead of setuptools.
Next, add a CMakeLists.txt to describe how to build your extension. In the following example,
a C++ extension named _hello is built:
cmake_minimum_required(VERSION 3.18...3.22)
project(hello)
find_package(PythonExtensions REQUIRED)
add_library(_hello MODULE hello/_hello.cxx)
python_extension_module(_hello)
install(TARGETS _hello LIBRARY DESTINATION hello)
Then, add a pyproject.toml declaring the build backend:
[build-system]
requires = ["scikit-build>=1"]
build-backend = "scikit_build_core.setuptools.build_meta"
The scikit_build_core.setuptools.build_meta backend adds cmake (and
ninja) to the build requirements only when a suitable version is not
already available, and supports config-settings (see
Command line options). If you don’t want those, you can use the
standard setuptools backend here; then list cmake (and ninja) in
requires yourself.
Make a hello folder inside my_project folder and place _hello.cxx and __init__.py inside hello folder.
Now everything is ready; go to my_project’s parent folder and type the following command to install your extension:
pip install my_project/.
If you want to see the detail of installation:
pip install my_project/. -v
Try your new extension:
$ python
Python 3.10.4 (main, Jun 29 2022, 12:14:53) [GCC 11.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import hello
>>> hello.hello("scikit-build")
Hello, scikit-build!
>>>
Note
By default, scikit-build looks in the project top-level directory for a
file named CMakeLists.txt. It will then invoke the cmake
executable, using CMake’s default generator unless
the CMAKE_GENERATOR environment variable is set.
Setup options¶
setuptools options¶
Changed in version 1.0: scikit-build no longer intercepts or rewrites setuptools options.
Standard setuptools options are handled by setuptools itself and can be
declared in setup.py, setup.cfg, or the [project] table of
pyproject.toml as usual. See the setuptools documentation.
scikit-build options¶
Scikit-build augments the setup() function with the following options:
cmake_args: List of CMake options.
For example:
setup(
[...]
cmake_args=['-DSOME_FEATURE:BOOL=OFF']
[...]
)
cmake_install_dir: relative directory where the CMake artifacts are installed. By default, it is set to an empty string.cmake_source_dir: Relative directory containing the projectCMakeLists.txt. By default, it is set to the top-level directory wheresetup.pyis found.cmake_install_target: Name of the target to “build” for installing the artifacts into the wheel. By default, this option is set toinstall, which is always provided by CMake and runscmake --install. Any other value is installed by building that target withcmake --build --target, which can be used to only install certain components.
For example:
install(TARGETS foo COMPONENT runtime)
add_custom_target(foo-install-runtime
${CMAKE_COMMAND}
-DCMAKE_INSTALL_COMPONENT=runtime
-P "${PROJECT_BINARY_DIR}/cmake_install.cmake"
DEPENDS foo
)
cmake_process_manifest_hook: Python function consuming the list of files to be installed produced by cmake. For example,cmake_process_manifest_hookcan be used to exclude static libraries from the built wheel.
For example:
def exclude_static_libraries(cmake_manifest):
return list(filter(lambda name: not (name.endswith('.a')), cmake_manifest))
setup(
[...]
cmake_process_manifest_hook=exclude_static_libraries
[...]
)
These options are described in more detail in the scikit-build-core
setuptools plugin documentation.
All other scikit-build-core settings can be set in the [tool.scikit-build]
table of pyproject.toml; see the scikit-build-core configuration
documentation.
Changed in version 1.0: The cmake_with_sdist option now raises an error if set to True,
and the cmake_languages and cmake_minimum_required_version options
are ignored with a warning. See Migrating from scikit-build 0.x.
Command line options¶
Changed in version 1.0: The scikit-build-specific command line options and the
setup.py <setuptools args> -- <cmake args> -- <build tool args>
syntax were removed. See Migrating from scikit-build 0.x.
Build with a standard frontend like pip, build or uv. When using
the scikit_build_core.setuptools.build_meta backend (see
Example of setup.py, CMakeLists.txt and pyproject.toml), any scikit-build-core setting can be passed as a
config-setting:
pip install . -C cmake.build-type=Debug
python -m build -C cmake.args="-DSOME_FEATURE:BOOL=OFF"
CMake options can also be passed using the CMAKE_ARGS environment
variable, the cmake_args keyword of setup(), or scikit-build-core’s
[tool.scikit-build] settings in pyproject.toml.
Advanced Usage¶
How to test if scikit-build is driving the compilation ?¶
To support the case of code base being built as both a standalone project
and a python wheel, it is possible to test for the variable SKBUILD:
if(SKBUILD)
message(STATUS "The project is built using scikit-build")
endif()
Changed in version 1.0: The SKBUILD variable is now set to 2 instead of TRUE. Both
values are truthy in CMake.
Parallel builds¶
The build runs through cmake --build; with the Ninja generator it is
parallel by default. Set the CMAKE_BUILD_PARALLEL_LEVEL
environment variable to control the number of parallel jobs:
CMAKE_BUILD_PARALLEL_LEVEL=3 pip install .
Support for isolated build¶
Build frontends like pip and build install the
build-system.requires entries from pyproject.toml into an isolated
environment before building. scikit-build supports isolated builds, as well
as builds with isolation disabled (pip install --no-build-isolation).
Editable installs¶
Changed in version 1.0: Editable installs previously worked without extra configuration.
In-place builds (python setup.py build_ext --inplace) work without extra
configuration. Editable installs (pip install -e .) require opting in to
scikit-build-core’s “inplace” editable mode in pyproject.toml:
[tool.scikit-build]
editable.mode = "inplace"
For details, including setuptools’ strict editable mode, see the scikit-build-core setuptools plugin documentation.
Optimized incremental build¶
To optimize the developer workflow, the CMake build directory is kept inside
the standard setuptools build/ directory (an _skbuild directory under
build/temp.*) and reused across builds.
If a file is added to the CMake build system by updating one of the
CMakeLists.txt files, the generated build-system will automatically
detect the change and reconfigure the project the next time a build is run.
Environment variable configuration¶
Scikit-build support environment variables to configure some options. These are:
CMAKE_ARGSThis will add configuration options when configuring CMake.
CMAKE_GENERATORThis selects the CMake generator to use. See C Runtime, Compiler and Build System Generator.
SKBUILD_CONFIGURE_OPTIONSExtra arguments appended when configuring CMake, like
CMAKE_ARGS. (Deprecated)SKBUILD_BUILD_OPTIONSExtra arguments forwarded to
cmake --build. (Deprecated)
Both SKBUILD_*_OPTIONS variables are split following shell quoting rules
and only honored when building through skbuild.setup().
In addition, every scikit-build-core setting can be set using a corresponding
SKBUILD_* environment variable. See the scikit-build-core configuration
documentation.
Cross-compilation¶
Cross-compilation works through the standard CMake mechanisms (CMake toolchains). See the scikit-build-core cross-compilation guide; tools like cibuildwheel handle the common macOS and Windows cases automatically.