Distutils: build multiple Python extension modules (written in Swig) that share a method
我有四个C ++文件:A.h,A.cpp,B.h,B.cpp和B.cpp中包含A.h
啊:
1 2 | #pragma once void A(); |
A.cpp:
1 2 3 4 | #include <iostream> void A() { std::cout <<"A" << std::endl; } |
B.h:
1 2 | #pragma once void B(); |
B.cpp:
1 2 3 4 5 6 | #include"A.h" #include <iostream> void B() { A(); std::cout <<"B" << std::endl; } |
现在,我编写了两个SWIG接口文件A.i和B.i
A.i:
1 2 3 4 5 | %module A %{ #include"A.h" %} %include"A.h" |
双:
1 2 3 4 5 | %module B %{ #include"B.h" %} %include"B.h" |
setup.py文件是:
1 2 3 4 5 6 7 8 9 | from distutils.core import setup, Extension A_ext = Extension("_A", ["A.i","A.cpp", ], swig_opts = ['-c++'], extra_compile_args = ['-g']) B_ext = Extension("_B", ["B.i","B.cpp", ], swig_opts = ['-c++'], extra_compile_args = ['-g']) setup( name ="test", version ="1.0", ext_modules = [ A_ext, B_ext ], py_modules = ["A","B" ] ) |
如果我在下面键入命令,它将显示" A"。
1 | python -c 'import A; A.A()' |
如果我在下面键入命令,则会出现分段错误:
1 | python -c 'import B; B.B()' |
如何使此命令正确运行? 由于我不想多次编译B.cpp,除了下面的方法有什么办法吗?
1 | B_ext = Extension("_B", ["B.i","A.cpp","B.cpp", ], swig_opts = ['-c++'], extra_compile_args = ['-g']) |
为了清楚起见,我对您的文件做了一些更改。
啊:
1 2 3 4 | #pragma once void funcA(); |
a.cpp:
1 2 3 4 5 6 | #include <iostream> void funcA() { std::cout << __FILE__ <<"" << __LINE__ << "" << __FUNCTION__ << std::endl; } |
a.i:
1 2 3 4 5 | %module a %{ #include"a.h" %} %include"a.h" |
b.h:
1 2 3 4 | #pragma once void funcB(); |
b.cpp:
1 2 3 4 5 6 7 8 | #include"a.h" #include <iostream> void funcB() { std::cout << __FILE__ <<"" << __LINE__ << "" << __FUNCTION__ << std::endl; funcA(); } |
双:
1 2 3 4 5 | %module b %{ #include"b.h" %} %include"b.h" |
setup.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | from distutils.core import setup from distutils.extension import Extension a ="a" b ="b" ext_a = Extension("_" + a, [a +".i", a +".cpp"], swig_opts=("-c++",), extra_compile_args=["-g"]) ext_b = Extension("_" + b, [b +".i", b +".cpp"], swig_opts=("-c++",), extra_compile_args=["-g"]) setup( name="test", version="1.0", ext_modules=[ext_a, ext_b], py_modules=[a, b] ) |
调用
- 从这里开始的一切都发生在C(或C ++)中
- 当前的funcB与b.cpp中的funcB不同:它由swig生成,名称为_wrap_funcB
- 上一个项目符号也适用于funcA和a.cpp
问题是步骤4中的代码。不在模块_b中,它将在运行时失败。但是事情有点奇怪:调用funcB时不会出现故障(核心转储),而是在模块(b-> _b)导入时出现(我认为这是由于Swig幕后的魔法所致),如下所示。
输出:
1
2
3
4
5
6
7
8
9
10
11
12
13 [cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q050938128]> ls
a.cpp a.h a.i b.cpp b.h b.i setup.py
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q050938128]> python3 setup.py build > /dev/null 2>&1
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q050938128]> echo $?
0
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q050938128]> ls
a.cpp a.h a.i a.py a_wrap.cpp b.cpp b.h b.i b.py build b_wrap.cpp setup.py
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q050938128]> ls ./build/lib.linux-x86_64-3.5
_a.cpython-35m-x86_64-linux-gnu.so _b.cpython-35m-x86_64-linux-gnu.so
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q050938128]> PYTHONPATH=${PYTHONPATH}:./build/lib.linux-x86_64-3.5 python3 -c"import _a;_a.funcA()"
a.cpp 6 funcA
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q050938128]> PYTHONPATH=${PYTHONPATH}:./build/lib.linux-x86_64-3.5 python3 -c"import _b"
Segmentation fault (core dumped)
为了解决它,可以:
显然,对于选项3。是完美的候选人。但是distutils([Python 3]:API参考)没有提供所需的功能OOTB(显然,构建扩展模块和它依赖的外部共享库,不是distutils所针对的场景),或者至少,我找不到任何东西。有一个build_clib模块,它提供了构建静态库(供扩展模块使用)的功能,但与选项#1相同。
setup.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | import sys import os from distutils.core import setup from distutils.extension import Extension from distutils.command.build_clib import build_clib from distutils.command.build_ext import build_ext from distutils.ccompiler import CCompiler __win = sys.platform[:3].lower() =="win" export_symbols_option ="export_symbols" class build_clib_dyn(build_clib): def finalize_options(self): self.set_undefined_options('build', ('build_lib', 'build_clib'), ('build_temp', 'build_temp'), ('compiler', 'compiler'), ('debug', 'debug'), ('force', 'force')) self.libraries = self.distribution.libraries if self.libraries: self.check_library_list(self.libraries) if self.include_dirs is None: self.include_dirs = self.distribution.include_dirs or [] if isinstance(self.include_dirs, str): self.include_dirs = self.include_dirs.split(os.pathsep) def build_libraries(self, libraries): for (lib_name, build_info) in libraries: sources = build_info.get('sources') if sources is None or not isinstance(sources, (list, tuple)): raise DistutilsSetupError( "in 'libraries' option (library '%s')," "'sources' must be present and must be" "a list of source filenames" % lib_name) sources = list(sources) macros = build_info.get('macros') include_dirs = build_info.get('include_dirs') objects = self.compiler.compile(sources, output_dir=self.build_temp, macros=macros, include_dirs=include_dirs, debug=self.debug) self.compiler.link(CCompiler.SHARED_OBJECT, objects, self.compiler.library_filename(lib_name, lib_type="shared"), output_dir=self.build_clib, export_symbols=build_info.get(export_symbols_option), debug=self.debug) if __win: class build_ext_w_dyn_dep(build_ext): def finalize_options(self): super(build_ext_w_dyn_dep, self).finalize_options() self.library_dirs.append(os.path.dirname(self.build_temp)) else: class build_ext_w_dyn_dep(build_ext): pass a_name ="a" b_name ="b" common_name = a_name + b_name +"common" swig_opts = ["-c++"] libraries = [common_name] lib_common_build_info = {"sources": [a_name +".cpp"]} if __win: extra_compile_args = None extra_link_args = None lib_common_build_info[export_symbols_option] = ["funcA"] else: extra_compile_args = ["-g"] extra_link_args = ["-Wl,-rpath,${ORIGIN}"] lib_common_info = (common_name, lib_common_build_info) ext_a = Extension("_" + a_name, [a_name +".i"], libraries=libraries, extra_compile_args=extra_compile_args, extra_link_args=extra_link_args, swig_opts=swig_opts) ext_b = Extension("_" + b_name, [b_name +".i", b_name +".cpp"], libraries=libraries, extra_compile_args=extra_compile_args, extra_link_args=extra_link_args, swig_opts=swig_opts) setup( name="test", version="1.0", libraries=[lib_common_info], cmdclass={"build_clib": build_clib_dyn,"build_ext": build_ext_w_dyn_dep}, ext_modules=[ext_a, ext_b], py_modules=[a_name, b_name] ) |
笔记:
- build_clib_dyn扩展了build_clib,因为必须修改其功能。覆盖了2个方法,但实际上只有一小部分发生了更改(未从基类方法(Python3.5.4代码库)中复制注释,以减少代码量,但这实际上不算作更改)
- 该任务进行了相当多的distutils代码浏览,因为一些选项没有记录(我对此不太熟悉)
- 还需要一些Nix知识,因为在加载不在系统路径中的共享库时,事情变得棘手,并且还使事情保持平稳(例如,不更改$ {LD_LIBRARY_PATH})
- build_ext_w_dyn_dep与build_clib_dyn(仅Win)相似。由于构建Win动态链接库(.dll)时会生成2个文件,并且在这种情况下它们不在同一目录中,因此需要对库搜索路径进行一些调整
- 兼容Python 3和Python 2
输出(再次运行以上命令):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 [cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q050938128]> ls
a.cpp a.h a.i b.cpp b.h b.i setup.py
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q050938128]> python3 setup.py build > /dev/null 2>&1
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q050938128]> echo $?
0
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q050938128]> ls
a.cpp a.h a.i a.py a_wrap.cpp b.cpp b.h b.i b.py build b_wrap.cpp setup.py
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q050938128]> ls build/lib.linux-x86_64-3.5/
_a.cpython-35m-x86_64-linux-gnu.so _b.cpython-35m-x86_64-linux-gnu.so libabcommon.so
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q050938128]> ldd build/lib.linux-x86_64-3.5/_a.cpython-35m-x86_64-linux-gnu.so
linux-vdso.so.1 => (0x00007fffadb49000)
libabcommon.so => /home/cfati/Work/Dev/StackOverflow/q050938128/build/lib.linux-x86_64-3.5/libabcommon.so (0x00007f91cd50f000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f91cd18d000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f91ccf77000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f91ccbad000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f91cc990000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f91cc687000)
/lib64/ld-linux-x86-64.so.2 (0x00007f91cd916000)
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q050938128]> PYTHONPATH=${PYTHONPATH}:./build/lib.linux-x86_64-3.5 python3 -c"import _a;_a.funcA()"
a.cpp 6 funcA
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q050938128]> PYTHONPATH=${PYTHONPATH}:./build/lib.linux-x86_64-3.5 python3 -c"import _b;_b.funcB()"
b.cpp 7 funcB
a.cpp 6 funcA
@ EDIT0:
-
增加了对Win的支持(我想这对问题不是很重要)
- 由于模块结构很简单,因此可以在setup.py的级别上进行所有操作(不必修改源文件)
- 唯一的额外要求是swig.exe的目录应位于%PATH%中