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

基于pip构建CMake扩展:C++/Python项目路径适配问题求助

解决C++/Python混合项目中setup.py兼容pip命令的问题

这个问题我之前做C++/Python混合项目时也碰到过——pip执行时会把foo/python/目录复制到临时环境,导致setup.py找不到顶层的C++源码和根目录的CMakeLists.txt。核心思路是不要依赖当前工作目录,而是通过setup.py自身的文件路径回溯定位项目根目录,这样不管是直接运行python setup.py还是用pip命令,都能正确关联到顶层代码。

具体实现步骤

1. 在setup.py中准确定位项目根目录

在setup.py开头,通过__file__(当前setup.py的绝对路径)向上回溯两级(因为setup.py在foo/python/下),就能拿到项目根目录foo/:

import os
import sys
from setuptools import setup, Extension
from setuptools.command.build_ext import build_ext

# 从foo/python/setup.py向上两级,定位到项目根目录foo/
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))

# 可选:添加断言验证根目录是否存在CMakeLists.txt,方便调试
assert os.path.exists(os.path.join(PROJECT_ROOT, "CMakeLists.txt")), "无法找到项目根目录的CMakeLists.txt"

2. 修改CMakeExtension和CMakeBuild逻辑

调整CMake相关的类,让cmake指向项目根目录的CMakeLists.txt,同时指定合理的构建目录(避免pip临时目录的干扰):

class CMakeExtension(Extension):
    def __init__(self, name):
        Extension.__init__(self, name, sources=[])
        # 指向项目根目录下的python/source/(存放pybind11封装代码)
        self.sourcedir = os.path.join(PROJECT_ROOT, "python", "source")

class CMakeBuild(build_ext):
    def build_extension(self, ext):
        # 获取最终扩展模块的输出目录(pip会自动处理这个路径)
        extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name)))
        
        # 把CMake构建目录放在项目根目录下,方便复用缓存,也避免临时目录被清理
        cmake_build_dir = os.path.join(PROJECT_ROOT, "build", f"cmake-build-{sys.platform}")
        os.makedirs(cmake_build_dir, exist_ok=True)

        # CMake配置参数:指定项目根目录作为源目录,传递Python可执行路径等
        cmake_args = [
            f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={extdir}",
            f"-DPYTHON_EXECUTABLE={sys.executable}",
            PROJECT_ROOT  # 关键:告诉CMake从项目根目录的CMakeLists.txt开始构建
        ]

        # 执行CMake配置和构建
        self.spawn(["cmake", "-B", cmake_build_dir, *cmake_args])
        self.spawn(["cmake", "--build", cmake_build_dir, "--config", "Release"])

3. 调整setup()函数参数

确保包路径和扩展模块的配置正确:

setup(
    name="foo",
    version="0.1.0",
    packages=["foo"],
    package_dir={"": "foo"},  # 指定包的根目录是foo/python/foo/
    ext_modules=[CMakeExtension("_foo_cpp")],
    cmdclass={"build_ext": CMakeBuild},
    zip_safe=False,  # 扩展模块是共享库,不能用zip压缩
)

关键细节说明

  • 路径可靠性:用__file__定位根目录,不管是直接在foo/python/运行python setup.py,还是pip把该目录复制到临时环境,都能准确找到顶层C++代码。
  • 构建目录选择:把CMake构建目录放在项目根目录下(如foo/build/),既可以复用编译缓存,又避免了pip临时目录被自动清理导致的重复编译。
  • 兼容性验证:
    • 直接运行:在foo/python/执行python setup.py install,正常构建安装。
    • pip命令:在foo/python/执行pip wheel -w wheelhouse --no-deps .,可以正确生成包含C++扩展的wheel包。

额外建议

  • 可以在根目录的CMakeLists.txt中添加条件编译逻辑,比如通过PYTHON_EXECUTABLE变量判断是否启用pybind11模块的构建。
  • 不需要额外修改MANIFEST.in,因为我们是通过CMake直接访问根目录的C++代码,pip的临时目录复制机制不影响CMake的路径查找。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 08:05:56