使用 CMake 构建 Python C/C++ 扩展

使用 CMake 构建 Python C/C++ 扩展

💡 原文中文,约6100字,阅读约需15分钟。
📝

内容提要

文章介绍用CMake构建Python C/C++扩展的两种方案:一是继承distutils的Extension并重写build_ext,在构建时调用CMake,适合pybind11项目;二是使用scikit-build,它作为setuptools与CMake的胶水,提供PythonExtensions等CMake函数,可通过pyproject.toml声明依赖,自带CMake和Ninja的wheel而无需全局安装,支持Cython和pybind11,便于管理第三方C/C++库依赖。

🔎

延伸解读

两种方案的选择依据

文章对比了手工继承 distutils 与使用 scikit-build 两种方案。前者需要自己重写 build_ext 并调用 CMake,适合已使用 pybind11 的项目,因为 pybind11 提供了 pybind11_add_module 等 CMake 函数简化配置;后者作为 setuptools 与 CMake 的胶水,提供 PythonExtensions 等函数,更适合需要管理第三方 C/C++ 库依赖或使用 Cython 的项目。

scikit-build 的依赖管理优势

scikit-build 通过 pyproject.toml 声明构建依赖,并自带 CMake 和 Ninja 的 wheel 包,无需在系统全局安装。这降低了环境配置的复杂度,尤其适合持续集成或跨平台构建场景。文章还提到它支持 Cython 和 pybind11,能方便地集成第三方 C/C++ 库,如 abseil-cpp。

手工方案的局限与适用场景

方案一需要手动处理 CMake 调用、输出目录、编译配置等细节,代码量较大。文章指出,如果不使用 pybind11,配置会变得繁琐,并以 Apache Arrow 的 CMakeLists.txt 为例说明复杂度。因此,该方案更适合已采用 pybind11 且依赖简单的项目,否则 scikit-build 可能是更省心的选择。

❓

Q&A

用CMake构建Python C/C++扩展有哪些可选方案?

文章介绍了两种方案:一是继承distutils的Extension并重写build_ext,在构建时调用CMake;二是使用scikit-build,它作为setuptools与CMake的胶水,提供PythonExtensions等CMake函数,可通过pyproject.toml声明依赖,自带CMake和Ninja的wheel而无需全局安装。

为什么需要CMake来构建Python扩展?

当项目需要用到C/C++第三方库(如abseil-cpp)并需要将三方库源码和项目源码一起编译时,使用distutils的Extension会遇到C/C++依赖管理问题,而CMake是常用的C/C++依赖管理工具,因此用CMake来构建扩展可以更好地管理依赖。

方案一(继承distutils Extension)具体如何实现?

通过继承distutils的Extension创建CMakeExtension类,并重写setuptools的build_ext命令类(CMakeBuild),在run方法中检查CMake版本,然后对每个扩展调用build_extension方法,该方法使用subprocess调用cmake命令配置和构建项目,并设置输出目录等参数。

方案一适合什么样的项目?

方案一比较适合pybind11的项目,因为pybind11提供了很多CMake模块(如找到Python.h、libpython等),并且提供了pybind11_add_module函数来简化Python扩展的定义。如果不使用pybind11,则需要手动处理更多配置,比较麻烦。

scikit-build是什么?它有什么特点?

scikit-build是一个增强的Python C/C++/Fortran/Cython扩展构建系统生成器,本质上是setuptools和CMake的胶水。它提供了PythonExtensions等CMake函数,可通过pyproject.toml声明构建依赖,并且打包了CMake和Ninja的二进制wheel,因此无需在系统上全局安装CMake/Ninja。它还支持Cython和pybind11。

使用scikit-build时,setup.py和CMakeLists.txt需要怎么写?

setup.py中从skbuild导入setup替代setuptools的setup,其他参数类似。CMakeLists.txt中需要使用find_package(PythonExtensions REQUIRED)来加载scikit-build提供的CMake模块,然后使用add_library定义扩展模块,并调用python_extension_module函数,最后通过install(TARGETS ... LIBRARY DESTINATION ...)将构建好的动态库复制到Python包目录中。

🏷️

标签

➡️

继续阅读