Python 2.7 使用 CMake 和 SetupTools 的 C++ 扩展?

Python 2.7 C++ extensions with CMake and SetupTools?

概览

我有一个提供 Python 2.7 包装器的 C++ 项目。我正在使用 CMake 构建该项目,我想要一个 setup.py 文件,以便我可以轻松地将此项目 pip install git+https://mygitrepo/my_project 放入 virtualenv 中。我已经成功地达到了可以使用 pip 来构建我的项目的地步,但是我不知道如何在我的 setup.py 文件或我的 CMakeLists.txt 中指定我的二进制文件应该安装在哪里。

详情

当我使用 CMake(例如 mkdir /path/to/my_project/build && cd /path/to/my_project/build && cmake .. && make)构建我的项目时,一切都正确构建,我最终得到一个 libmy_project.so 和一个 pymy_project.so 文件,我可以成功导入和使用在 Python.

当我在 virtualenv 中使用 pip 构建项目时(例如 pip install /path/to/my_project -v),我可以看到 CMake 和我的编译器正在按预期进行编译。然而,它们建立在一个临时目录中并立即被丢弃。 如何告诉我的 setup.py 要保留哪些文件,以及将它们放在哪里?

相关文件

我的 CMakeLists.txt 如下所示:

cmake_minimum_required (VERSION 3.0.0)
project (my_project)

# Find SomePackage
find_package(SomePackage REQUIRED COMPONENTS cool_unit great_unit)
include_directories(${SomePackage_INCLUDE_DIR})

# Find Python
find_package(PythonLibs 2.7 REQUIRED)
include_directories(${PYTHON_INCLUDE_DIR})

# Build libmy_project
add_library(my_project SHARED src/MyProject.cpp)
target_link_libraries(my_project ${SomePackage_LIBRARIES} ${PYTHON_LIBRARIES})

# Build py_my_project
add_library(py_my_project SHARED src/python/pyMyProject.cpp)
set_target_properties(py_my_project PROPERTIES PREFIX "")
target_link_libraries(py_my_project ${SomePackage_LIBRARIES} 
                      ${PYTHON_LIBRARIES} my_project)

file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/__init__.py "__all__ = ['py_my_project']")

我的 setup.py 文件如下所示:

import os
import subprocess

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

class CMakeExtension(Extension):
    def __init__(self, name, sourcedir=''):
        Extension.__init__(self, name, sources=[])
        self.sourcedir = os.path.abspath(sourcedir)

class CMakeBuild(build_ext):
    def run(self):
        for ext in self.extensions:
            self.build_extension(ext)

    def build_extension(self, ext):
        if not os.path.exists(self.build_temp):
            os.makedirs(self.build_temp)
        subprocess.check_call(['cmake', ext.sourcedir], cwd=self.build_temp)
        subprocess.check_call(['cmake', '--build', '.'], cwd=self.build_temp)

setup(
    name='my_project',
    version='0.0.1',
    author='Me',
    author_email='rcv@whosebug.com',
    description='A really cool and easy to install library',
    long_description='',
    ext_modules=[CMakeExtension('.')],
    cmdclass=dict(build_ext=CMakeBuild),
    zip_safe=False,
)

我如何告诉我的 setup.py 哪些文件要保留,以及将它们放在哪里?”的答案似乎是 self.get_ext_fullpath(ext.name)this solution as pointed out by abarnert and hoefling 中所见。此方法 returns 一个临时根目录,您可以将构建输出放入其中。然后 Pip 似乎会扫描该目录,并在构建完成后将所有内容复制到您的真实环境中。

不幸的是,Linux 动态链接器无法为我的扩展找到我的依赖项(libmy_project.so),因为 virtualenv 没有设置 LD_LIBRARY_PATH 或任何东西,所以我结束了只是将所有编译单元滚动到我的扩展中。完整的解决方案如下:

setup.py

import os
import subprocess

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

class CMakeExtension(Extension):
    def __init__(self, name, sourcedir=''):
        Extension.__init__(self, name, sources=[])
        self.sourcedir = os.path.abspath(sourcedir)

class CMakeBuild(build_ext):
    def run(self):
        for ext in self.extensions:
            self.build_extension(ext)

    def build_extension(self, ext):
        if not os.path.exists(self.build_temp):
            os.makedirs(self.build_temp)

        extdir = self.get_ext_fullpath(ext.name)
        if not os.path.exists(extdir):
            os.makedirs(extdir)

        # This is the temp directory where your build output should go
        install_prefix = os.path.abspath(os.path.dirname(extdir))
        cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={}'.format(install_prefix)]

        subprocess.check_call(['cmake', ext.sourcedir, cmake_args], cwd=self.build_temp)
        subprocess.check_call(['cmake', '--build', '.'], cwd=self.build_temp)

setup(
    name='my_project',
    version='0.0.1',
    author='Me',
    author_email='rcv@whosebug.com',
    description='A really cool and easy to install library',
    long_description='',
    ext_modules=[CMakeExtension('.')],
    cmdclass=dict(build_ext=CMakeBuild),
    zip_safe=False,
    install_requires=[
        'cmake',
    ]
)

CMakeLists.txt

cmake_minimum_required (VERSION 3.0.0)
project (my_project)

# Find SomePackage
find_package(SomePackage REQUIRED COMPONENTS cool_unit great_unit)
include_directories(${SomePackage_INCLUDE_DIR})

# Find Python
find_package(PythonLibs 2.7 REQUIRED)
include_directories(${PYTHON_INCLUDE_DIR})

# Build py_my_project
add_library(py_my_project SHARED src/python/pyMyProject.cpp src/MyProject.cpp)
set_target_properties(py_my_project PROPERTIES PREFIX "")
target_link_libraries(py_my_project ${SomePackage_LIBRARIES} 
                      ${PYTHON_LIBRARIES})

file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/__init__.py "__all__ = ['py_my_project']")