在setup.py中配置SWIG模块
2018-03-20 16:17:05 +08 字数:1883 标签: Python CSWIG的一个优点是,受到了distutils/setuptools的原生支持。
在setup.py
文件中,可以很方便地直接把*.i
文件作为源文件进行配置,而不用手写编译脚本。
本文通过一个Demo项目,来讲解如何在setup.py
中对SWIG进行配置。
Demo项目 ¶
为了解释SWIG模块的配置,必须要先说明其项目结构,这有些麻烦。 为此,孤特地写了一个Demo项目swig-python-demo,作为参考。
同时,这个项目也适合作为一个SWIG项目的初始化参考。
项目结构 ¶
脱离具体的项目结构,谈不了setup.py
配置。
以下是swig-python-demo的项目结构。
swig-python-demo
├── LICENSE
├── Makefile
├── setup.cfg
├── setup.py
├── src
│ └── example
│ ├── example.c
│ ├── example.h
│ ├── example.i
│ ├── __init__.py
│ ├── _meta.py
│ ├── stl_example.cpp
│ ├── stl_example.hpp
│ └── stl_example.i
└── tests
├── test_example.py
└── test_stl_example.py
它虽然只有一个example
模块,但额外包括了example.i
和stl_example.i
两个SWIG模块。
前者是官方C语言的样例,后者是孤特别提供的C++语言STL容器样例。
setup.py ¶
from setuptools import Extension, find_packages, setup
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',
]
)
setup(
...
packages=find_packages('src'),
package_dir={'': 'src'},
ext_modules=[EXAMPLE_EXT, STD_EXT],
...
)
EXAMPLE_EXT
和STD_EXT
分别是两个SWIG的Extension
。
当然,它们可以合并成一个,这里是为了分别展示C项目与C++项目。
在setup
函数中,ext_modules
参数是一个列表(实际上可以是一个Iterable),用来设置这个包所包含的Extension
。
注意:包名都以下划线_
开头,这是SWIG自动生成的模块所约定的。
例如,在example.i
中定义的模块名是example
,那么自动生成的Python文件就是example.py
,而SWIG的接口模块则是_example
,编译后的文件也是_example.*.so
。
生成的py文件丢失 ¶
在使用SWIG时,有一个隐藏的问题。
无论是用bdist
、bdist_wheel
还是bdist_rpm
,这类以编译产物打包的命令,都会丢失SWIG生成的py文件。
比如这里,会丢失example.py
和stl_example.py
两个文件。
之所以说『隐藏』,是因为在这个问题在本地开发时难以发现。 它只有在首次编译打包时,才会出现;如果在不删除这两个生成的py文件的前提下,再次编译、打包,就不会有这个问题。
根本原因在于,在执行setup
函数进行编译、打包时,build_ext
是在build_py
之后执行的。
由于SWIG项目的特殊性,这个默认的执行顺序会让自动生成的Python文件在Python部分打包后才生成,所以在程序包里丢失。
再次打包不会遇到问题,也是因为首次编译时已经执行了build_ext
。
这里提供一个简单的解决方案,让build_ext
在build_py
之前执行。
# 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(
...
cmdclass={
'build_py': BuildPy,
},
...
)
实际上,在Issue 7562: Custom order for the subcommands of build - Python tracker就描述了这个问题,而以下两个问题也记录了一些解决方案。 如果对此处提供的解决方案不满意,可以进一步参考它们。
- c++ - python distutils not include the SWIG generated module - Stack Overflow
- python - setup.py: run build_ext before anything else - Stack Overflow
pytest插件配置 ¶
在使用SWIG时,pytest的一些插件需要做额外配置,否则会有问题。
这些问题,主要都是由自动生成的Python文件——如本例中的example.py
、stl_example.py
——所导致的。
pytest-cov ¶
pytest-cov是用来计算测试覆盖率的插件。 它当然不支持C/C++的代码,但是却会自动计算自动生成的Python文件。 为了避免这个不是手写的文件影响结果,可以把它忽略掉。
[coverage:run]
omit = **/example.py
**/stl_example.py
pytest-pep8 ¶
pytest-pep8是自动检查Python代码是否符合PEP8规范。 当然,SWIG自动生成的Python文件,至少在SWIG的3.0版本,还不符合PEP8规范。 为了避免测试失败,必须忽略它。
pep8ignore = example.py ALL
stl_example.py ALL
pytest-flakes ¶
pytest-flakes是对Python代码做一些基本的语法与缺陷检查。 SWIG自动生成的Python文件……
flakes-ignore = example.py ALL
stl_example.py ALL
__init__.py UnusedImport
tests/* ALL
这里也忽略了所有的测试代码,因为pytest测试的某些写法不被这个插件所认可。
还有__init__.py
里的UnusedImport
类型,这是一种常见的误报。
后来,上述两个插件改为pytest-flake8。 并且,还会配上更多测试插件,详见swig-python-demo。
总结 ¶
更多的细节,包括.travis.yml
、.appveyor.yml
等CI配置,还是直接看swig-python-demo比较方便。