使用setuptools和swig构建模块

时间:2018-05-08 17:46:59

标签: python python-3.x swig setuptools

我有一堆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作为单个调用,想要运行{{1首先,然后build_py。你能明白为什么会失败吗?我没有Python源代码。我的模块中的Python脚本将由SWIG生成,但SWIG在build_ext步骤之前不会运行。因此,我最终得到了同样的问题,即如何在一次调用中构建模块。

build_ext

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

澄清:

  1. 错误就是这个: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

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

3 个答案:

答案 0 :(得分:1)

由于基本思想(参见@ 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})

答案 1 :(得分:0)

问题在于,一旦您声明py_modulesdistutilsbuild_py中包含build作为子命令,并且由于{{1}的排序,它始终首先被执行在sub_commands类中列出。它根本不知道SWIG将在build中生成python模块包装器。我想你可以看到这是一个bug,因为我们希望build_ext能够处理生成的模块,但是meh。

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

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

build

答案 2 :(得分:0)

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

class BuildPy(build_py)也解决了生成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,
    },
    ...
)