You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用setuptools打包C++扩展并包含stub.pyi文件

问题

项目文件结构:

project/
├─ cpp_src/
│  ├─ src/
│  │  ├─ cpp source files
│  ├─ test/
│  │  ├─ cpp test files
│  ├─ CMakeLists.txt
│  ├─ stub.pyi
├─ python_src/
│  ├─ ...
├─ build.py

使用setuptools配合自定义build_ext命令编译打包cpp_src中的C++扩展,但无法将stub.pyi文件包含进包内。允许调整文件结构(比如在cpp_src中新增setup.py),同时使用Poetry管理虚拟环境,也可更换更简便的构建系统。

简化后的build.py代码:

import subprocess
from pathlib import Path
from setuptools import Extension, setup
from setuptools.command.build_ext import build_ext

class CMakeBuild(build_ext):
    def build_extension(self, ext: Extension) -> None:
        # Determine where the extension should be transferred to after it has been
        # compiled
        current_dir = Path.cwd()
        build_dir = current_dir.joinpath(self.get_ext_fullpath(ext.name)).parent

        # Determine the profile to build the CMake extension with
        profile = "Release"

        # Make sure the build directory exists
        build_temp = Path(self.build_temp).joinpath(ext.name)
        if not build_temp.exists():
            build_temp.mkdir(parents=True)

        # Compile and build the CMake extension
        subprocess.run(
            [
                "cmake",
                current_dir.joinpath(ext.sources[0]),
                f"-DDO_TESTS=false",
                f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{profile.upper()}={build_dir}",
            ],
            cwd=build_temp,
            check=True,
        )
        subprocess.run(
            ["cmake", "--build", ".", f"--config {profile}"], cwd=build_temp, check=True
        )


def main():
    setup(
        name="hades_extensions",
        script_args=["bdist_wheel"],
        ext_modules=[Extension("hades_extensions", ["cpp_src"])],
        cmdclass={"build_ext": CMakeBuild},
    )

if __name__ == "__main__":
    main()

需求:修改setuptools命令以包含stub.pyi文件。

解决方案

方法一:修改现有build.py,添加package_data配置

Setuptools的setup函数支持通过package_data指定要包含的额外文件。由于扩展模块名为hades_extensions,需要把stub.pyi重命名为hades_extensions.pyi,让Python能正确关联到扩展模块。

修改main()函数中的setup调用:

def main():
    setup(
        name="hades_extensions",
        script_args=["bdist_wheel"],
        ext_modules=[Extension("hades_extensions", ["cpp_src"])],
        cmdclass={"build_ext": CMakeBuild},
        # 配置要包含的存根文件
        package_data={
            "hades_extensions": ["hades_extensions.pyi"],
        },
        # 声明包目录(即使没有__init__.py)
        packages=["hades_extensions"],
    )

注意:需要在cpp_src目录下创建空的__init__.py,或者直接通过packages参数声明hades_extensions为包,这样setuptools才能识别并处理package_data配置。

方法二:在cpp_src目录下新增setup.py

如果允许调整文件结构,可以把构建逻辑和文件配置放在cpp_src目录下的setup.py中,结构更清晰:

cpp_src/setup.py:

import subprocess
from pathlib import Path
from setuptools import Extension, setup
from setuptools.command.build_ext import build_ext

class CMakeBuild(build_ext):
    def build_extension(self, ext: Extension) -> None:
        current_dir = Path(__file__).parent
        build_dir = Path(self.get_ext_fullpath(ext.name)).parent

        profile = "Release"
        build_temp = Path(self.build_temp).joinpath(ext.name)
        if not build_temp.exists():
            build_temp.mkdir(parents=True)

        subprocess.run(
            [
                "cmake",
                current_dir,
                f"-DDO_TESTS=false",
                f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{profile.upper()}={build_dir}",
            ],
            cwd=build_temp,
            check=True,
        )
        subprocess.run(
            ["cmake", "--build", ".", f"--config {profile}"], cwd=build_temp, check=True
        )

setup(
    name="hades_extensions",
    ext_modules=[Extension("hades_extensions", ["."])],
    cmdclass={"build_ext": CMakeBuild},
    package_data={
        "hades_extensions": ["hades_extensions.pyi"],
    },
    packages=["hades_extensions"],
)

将cpp_src下的stub.pyi重命名为hades_extensions.pyi,然后执行python cpp_src/setup.py bdist_wheel即可完成打包。

方法三:结合Poetry简化构建

既然用Poetry管理虚拟环境,可以直接通过Poetry配置构建流程:

  1. 项目根目录创建pyproject.toml(若未存在),添加以下配置:
[tool.poetry]
name = "hades_extensions"
version = "0.1.0"
description = ""
authors = []

[tool.poetry.dependencies]
python = "^3.8"

[tool.poetry.build]
script = "build.py"

[build-system]
requires = ["poetry-core>=1.0.0", "setuptools"]
build-backend = "poetry.core.masonry.api"
  1. 修改build.py,添加package_data配置(同方法一),同时在CMakeBuild的build_extension方法末尾添加存根文件复制逻辑:
def build_extension(self, ext: Extension) -> None:
    # ... 原有编译逻辑
    # 复制stub文件到扩展目录
    stub_src = Path(__file__).parent / "cpp_src" / "stub.pyi"
    stub_dst = build_dir / "hades_extensions.pyi"
    stub_dst.write_text(stub_src.read_text())

执行poetry build即可完成打包,stub文件会被正确包含。

内容的提问来源于stack exchange,提问作者Aspect11

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 06:12:53