【问题标题】:Building a module with setuptools and swig使用 setuptools 和 swig 构建模块
【发布时间】:2018-05-08 17:46:58
【问题描述】:

我有一堆 SWIG 接口(foo.i、bar.i 等)。我想使用 setuptools 将它们构建到我的平台(MS Windows)的 Python(3.6.4)模块中。该模块应包括 SWIG 生成的 Python 文件 (*.py)、二进制扩展 (*.pyd) 和编译的缓存 (*.pyc)。我的 setup.py 基本上是这样的:

from setuptools import setup, Extension
from pathlib import Path
paths=list(Path('.').glob('*.i'))
py=[path.stem for path in paths]
ext=[Extension('_' + path.stem, [str(path)]) for path in paths]
setup(py_modules=py, ext_modules=ext)

现在我通过以下步骤构建它:

python setup.py build_ext -I..\include --swig-opts="-I..\include -c++" -b pkg
python setup.py build_py -c -d pkg
echo. > pkg\__init__.py

使用这些步骤,我得到了我想要的,在pkg 目录下。

我的问题是:有没有办法使用setup.py 的单个调用来获得这种效果,例如setup.py build?我认为构建应该调用 build_ext,但是我看不到如何传递,例如 swig-opts 选项。

更新

通过 SWIG 选项已解决 (h/t @hoefling)。解决方案如下所示:

ext=[Extension(name='_' + path.stem,
            sources=[str(path)],
            swig_opts=['-I../include', '-c++'],
            include_dirs=['../include'])
            for path in paths]

然而,剥掉那层洋葱后,我现在可以看到下面的层,即:setup.py build 作为单个调用,想先运行build_py,然后是build_ext。你能明白为什么这会失败吗?我没有 Python 资源。我的模块中的 Python 脚本将由 SWIG 生成,但 SWIG 直到 build_ext 步骤才会运行。因此,我最终遇到了同样的问题,即如何在一次调用中构建模块。

C:\some\path> python setup.py build
running build
running build_py
file foo.py (for module foo) not found
file foo.py (for module foo) not found
running build_ext
building '_foo' extension
swigging foo.i to foo_wrap.cpp
swig.exe -python -I../include -c++ -o foo_wrap.cpp foo.i
creating build
creating build\temp.win-amd64-3.6
creating build\temp.win-amd64-3.6\Release
cl.exe /c /nologo /Ox /W3 /GL /DNDEBUG /MD -I../include /EHsc /Tpfoo_wrap.cpp /Fobuild\temp.win-amd64-3.6\Release\foo_wrap.obj foo_wrap.cpp
Creating build\wib.win-amd64-3.6
link.exe /nologo /INCREMENTAL:NO /LTCG /DLL /MANIFEST:EMBED,ID=2 /MANIFESTUAC:NO /EXPORT:PyInit__foo build\temp.win-amd64-3.6\Release\foo_wrap.obj /IMPLIB:build\temp.win-amd64-3.6\Release\_foo.cp36-win_amd64.lib
Creating library build\temp.win-amd64-3.6\Release\_foo.cp36-win_amd64.lib and object build\temp.win-amd64-3.6\Release\_foo.cp36-win_amd64.exp
Generating code
Finished generating code

完成后,foo.py 存在于 foo.i 旁边的当前工作目录中,而 _foo.cp36-win_amd64.pyd 存在于 build\lib.win-amd64-3.6 下。 (仅供参考,为了保护专有信息,此记录略有编辑,例如,我没有显示所有命令回显的所有完整路径。而且我没有在源代码中包含 bar.i。)

澄清一下:

  1. 错误是这个:File foo.py (for module foo) not found

  2. 虽然所有必需的文件都存在于输出中,除了 foo.cp36-win_amd64.pyc 我可能没有,我必须手动将 foo.py 和 _foo.cp36-win_amd64.pyd 复制到一些新的目录来制作一个干净的 Python 模块。

【问题讨论】:

  • build_ext 支持开箱即用的 SWIG,因此 Extension(name, files, swig_opts=['-I../include', '-c++']) 应该构建得很好。
  • @hoefling 这对于传递 SWIG 选项是正确的,谢谢。
  • 至于其他错误 - 您可以发布完整的错误消息吗?只需将文本从终端复制并粘贴到您的问题中即可。
  • 啊,我现在明白问题所在了。

标签: python python-3.x swig setuptools


【解决方案1】:

问题是,一旦你声明了py_modulesdistutilsbuild 中包含build_py 作为子命令,并且由于sub_commands 列表在build 类中的排序,它总是首先执行。它根本不知道 SWIG 稍后会在 build_ext 中生成 python 模块包装器。我想您可以将此视为一个错误,因为人们希望 distutils 能够处理生成的模块,但是嗯。

由于您只有 SWIG 接口,并且您的所有 python 模块都是生成的包装器(因此在您的情况下,build_ext 不依赖于build_py),您可以更改build 中子命令的顺序@所以build_ext先执行,后执行。这将确保在执行build_py 之前生成python 模块。

基本上,您需要覆盖build 命令类并更改sub_commands 列表中元素的顺序。有很多方法可以在 python 中重新排序列表;以下是我的建议。为了重新排序列表,我使用了来自itertools recipes 的列表条件分区配方:

import itertools
from setuptools import setup, Extension
from distutils.command.build import build as build_orig


def partition(pred, iterable):
    t1, t2 = itertools.tee(iterable)
    return itertools.filterfalse(pred, t1), filter(pred, t2)


class build(build_orig):

    def finalize_options(self):
        super().finalize_options()
        condition = lambda el: el[0] == 'build_ext'
        rest, sub_build_ext = partition(condition, self.sub_commands)
        self.sub_commands[:] = list(sub_build_ext) + list(rest)


setup(
    ...,
    cmdclass={'build': build},
)

【讨论】:

  • 做这项工作,并解释如何做。
【解决方案2】:

由于基本思想(参见@hoefling 的回答)是将构建命令子类化以设置其 sub_commands 属性,因此这是可行的:

from setuptools import setup, Extension
from distutils.command.build import build

class build_alt_order(build):
  def __init__(self, *args):
    super().__init__(*args)
    self.sub_commands = [('build_ext', build.has_ext_modules),
                         ('build_py', build.has_pure_modules)]

setup(py_modules=['foo.py', ...],
      ext_modules=[Extension('_foo', swig_opts=...), ...],
      cmdclass={'build':build_alt_order})

【讨论】:

【解决方案3】:

有一个演示项目 swig-python-demo 与 SWIG 集成在 setup.py 中。 里面有2个扩展。 一个 (example.c) 由 C 编写,另一个由带有 STL 的 C++ (stl_example.cpp) 编写。

生成的py文件丢失的问题class BuildPy(build_py)也解决了。

EXAMPLE_EXT = Extension(
    name='_example',
    sources=[
        'src/example/example.c',
        'src/example/example.i',
    ],
)

STD_EXT = Extension(
    name='_stl_example',
    swig_opts=['-c++'],
    sources=[
        'src/example/stl_example.cpp',
        'src/example/stl_example.i',
    ],
    include_dirs=[
        'src/example',
    ],
    extra_compile_args=[  # The g++ (4.8) in Travis needs this
        '-std=c++11',
    ]
)


# Build extensions before python modules,
# or the generated SWIG python files will be missing.
class BuildPy(build_py):
    def run(self):
        self.run_command('build_ext')
        super(build_py, self).run()


setup(
    name='swig-example-demo',
    description='A Python demo for SWIG',
    ...
    ext_modules=[EXAMPLE_EXT, STD_EXT],
    cmdclass={
        'build_py': BuildPy,
    },
    ...
)

【讨论】:

    猜你喜欢
    • 2020-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-27
    • 2011-02-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多