简体   繁体   English

使用 distutils 分发预编译的 python 扩展模块

[英]Distribute pre-compiled python extension module with distutils

Quick one today: I'm learning the in's and out's of Pythons distutils library, and I would like to include a python extension module (.pyd) with my package.今天快速介绍一下:我正在学习 Python 的 distutils 库的来龙去脉,我想在我的包中包含一个 python 扩展模块 (.pyd)。 I know of course that the recommended way is to have distutils compile the extension at the time the package is created, but this is a fairly complex extension spanning many source files and referencing several external libs so it's going to take some significant playing to get everything working right.我当然知道推荐的方法是在创建包时让 distutils 编译扩展,但这是一个相当复杂的扩展,跨越许多源文件并引用多个外部库,因此需要一些重要的操作才能获得所有内容工作正常。

In the meantime I have a known working build of the extension coming out of Visual Studio, and would like to use it in the installer as a temporary solution to allow me to focus on other issues.与此同时,我有一个来自 Visual Studio 的已知扩展工作版本,并希望在安装程序中使用它作为临时解决方案,以便我可以专注于其他问题。 I can't specify it as a module, however, since those apparently must have an explicit .py extension.但是,我无法将其指定为模块,因为那些显然必须具有显式的 .py 扩展名。 How could I indicate in my setup.py that I want to include a pre-compiled extension module?我怎么能在我的 setup.py 中表明我想要包含一个预编译的扩展模块?

(Python 3.1, if it matters) (Python 3.1,如果重要的话)

I solved this by overriding Extension.build_extension: 我通过重写Extension.build_extension解决了这个问题:

setup_args = { ... }
if platform.system() == 'Windows':
    class my_build_ext(build_ext):
        def build_extension(self, ext):
            ''' Copies the already-compiled pyd
            '''
            import shutil
            import os.path
            try:
                os.makedirs(os.path.dirname(self.get_ext_fullpath(ext.name)))
            except WindowsError, e:
                if e.winerror != 183: # already exists
                    raise


            shutil.copyfile(os.path.join(this_dir, r'..\..\bin\Python%d%d\my.pyd' % sys.version_info[0:2]), self.get_ext_fullpath(ext.name))

    setup_args['cmdclass'] = {'build_ext': my_build_ext }

setup(**setup_args)

I ran into this same issue while building an extension library using Python 3.7, CMake 3.15.3 and Swig 4.0.1 with Visual Studio 2017. The build system generates three files: mymodule.py, _mymodule.lib and _mymodule.pyd. 在使用Python 3.7,CMake 3.15.3和Swig 4.0.1和Visual Studio 2017构建扩展库时,我遇到了同样的问题。构建系统生成三个文件:mymodule.py,_mymodule.lib和_mymodule.pyd。 After a bunch of trial and error, I found the following combination to work: 经过一堆反复的试验,我发现以下组合可以工作:

  1. Create a 'setup.cfg' file that specifies that you are installing a package; 创建一个“ setup.cfg”文件,指定您正在安装软件包; eg: 例如:
    [metadata]
    name = mymodule
    version = 1.0

    [options]
    include_package_data = True
    package_dir=
        =src
    packages=mymodule
    python_requires '>=3.7'

    [options.package_data]
    * = *.pyd
  1. Change CMake to create a distribution directory structure as follows: 更改CMake以创建分发目录结构,如下所示:
    setup.py
    setup.cfg
    src/
        mymodule/
                 __init__.py
                 _mymodule.pyd
  1. The 'setup.py' file is then trivial: 然后,“ setup.py”文件很简单:
    setup()

This requires having CMake rename the 'mymodule.py' output file to ' init .py'. 这需要让CMake将“ mymodule.py”输出文件重命名为“ init .py”。 I did this with the 'install' command in CMake: 我是通过CMake中的“安装”命令完成此操作的:


    install (TARGETS ${SWIG_MODULE_${PROJECT_NAME}_REAL_NAME} DESTINATION "${CMAKE_BINARY_DIR}/dist/src/${PROJECT_NAME}")
    install (FILES 
            setup.py
            setup.cfg
            DESTINATION "${CMAKE_BINARY_DIR}/dist"
    )

    install (FILES
            ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.py
            DESTINATION "${CMAKE_BINARY_DIR}/dist/src/${PROJECT_NAME}"
            RENAME "__init__.py"

I believe that the key secret to having this work was to restructure the build output as a Python package rather than trying to have setup use the default build output of as a Python script. 我相信进行这项工作的关键秘诀是将构建输出重组为Python软件包,而不是尝试使用默认的build输出作为Python脚本进行安装。

I had the same issue.我遇到过同样的问题。 This is what has worked out for me.这就是对我有用的方法。 I have changed the name of the module, just for the sake of a simple example.我已经更改了模块的名称,只是为了一个简单的例子。

My setup is: Visual Studio 2017 Project which builds an extension with a final file name myextension.pyd我的设置是: Visual Studio 2017 项目,它使用最终文件名 myextension.pyd 构建扩展

I then locally created the stubs for this module using stubgen from mypy module.然后我使用 mypy 模块中的 stubgen 在本地创建了这个模块的存根。

This is my file tree这是我的文件树

myextension/__init__.pyi
myextension/submodule.pyi
setup.py
myextension.pyd

And this is the contents of setup.py这是setup.py的内容

from setuptools import setup, Distribution


class BinaryDistribution(Distribution):
    def has_ext_modules(foo):
        return True


setup(
    name='myextension',
    version='1.0.0',
    description='myextension Wrapper',
    packages=['', 'myextension'],
    package_data={
        'myextension': ['*.pyi'],
        '': ['myextension.pyd'],
    },
    distclass=BinaryDistribution
)

After running pip wheel .运行pip wheel . I get a pretty nice wheel containing the extensions, along with the required stubs.我得到了一个非常漂亮的轮子,其中包含扩展以及所需的存根。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM