基于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
相关产品推荐
相关产品推荐

