现代化的 Python + CMake 构建系统: scikit-build-core

September 19, 2025

随着 PyTorch 的成功,越来越多的 C++ 项目使用 Python 作为用户交互的前端语言,通过 pybind11 等库连接 Python 前端接口与 C++ 高性能后端实现。 例如,NVIDIA 也在主推 cuteDSL 作为 CUTLASS 的 Python 接口。 本文主要介绍 scikit-build-core,一个利用 pyproject.toml 来定义 CMake 构建命令的工具,让开发者方便地在 Python 和 C++ 间协同开发。

Python package 构建流程: https://doi.org/10.25080/FMKR8387

Python package 构建流程: https://doi.org/10.25080/FMKR8387

pyproject.toml 中声明

[build-system]
requires = ["scikit-build-core"]
build-backend = "scikit_build_core.build"

开发者需要在 pyproject.toml 中声明使用 scikit-build-core 作为项目的构建后端,然后可以详细定义在构建项目的时候需要执行的 CMake 命令和参数:

[tool.scikit-build]
build-dir = "build"

cmake.version = ">=3.26.1"
ninja.version = ">=1.11"

cmake.source-dir = "."
cmake.args = ["-G Ninja"]
cmake.build-type = "RelWithDebInfo"

[tool.scikit-build.cmake.define]
SOME_DEFINE = "Foo"
SOME_OPTION = true
FOOD_GROUPS = [
    "Apple",
    "Lemon;Lime",
    "Banana",
    "Pineapple;Mango",
]

在 CMake 侧,开发者可以复用一些在 pyproject.toml 中定义的参数,避免重复定义:

cmake_minimum_required(VERSION 3.15)
project(
  ${SKBUILD_PROJECT_NAME}
  VERSION ${SKBUILD_PROJECT_VERSION}
  LANGUAGES C CXX)

构建和安装

运行 uv pip install .,可以在本地进行项目的构建,如果你需要将构建产物安装到 site-packages 目录中使得 Python 解释器可以 import,需要在 CMakeLists.txt 中用 install 声明哪些文件/目标需要被安装,scikit-build-core 会自动将 CMake 中的 CMAKE_INSTALL_PREFIX 参数设置为当前 Python 环境中的 site-packages/${SKBUILD_PROJECT_NAME} 目录。

同时,scikit-build-core 会自动将当前 Python 环境的 site-packages 目录添加到 CMAKE_PREFIX_PATH 中,因此开发者通过 uv pip install 等方式安装的 Python 包可以被 CMake 轻易地发现 (只要正确地使用 find_package(Python))。 举个例子,如果你需要将 __init__.pyi 等文件同编译好的二进制文件一起被 ship 到安装目录的话,你需要在 CMakeLists.txt 文件中通过 install 命令显式地进行指定。

sdist

sdist 是一种分发 Python 软件包源码 (source code) 的格式,有点类似 Debian 包 .deb 对应的 .dsc。 scikit-build-core 支持通过 pyproject.toml 指定构建 sdist 包,从而让用户能够下载 Python 包所需要的源码在本地进行构建。

sdist.include = []
sdist.exclude = []
sdist.reproducible = true
sdist.cmake = false

scikit-build-core 允许用户指定 includeexclude 等字段批量 mark 需要被包含到 sdist 包中的文件。 如果需要在打包前运行一下 cmake (如 dump 版本号等操作),则通过 sdist.cmake 选项指定。

wheel

当然,scikit-build-core 同样也支持构建二进制分发的格式 .whl:

wheel.exclude = ["**.pyx"]
wheel.py-api = "py3"  # "cp38"