我正在构建一个使用“第三方”库的C Python扩展--在本例中,我使用单独的构建过程和工具链构建了一个库。调用这个库libplumbus.dylib
。
目录结构如下:
grumbo/
include/
plumbus.h
lib/
libplumbus.so
grumbo.c
setup.py
我的setup.py
看起来近似于:
from setuptools import Extension, setup
native_module = Extension(
'grumbo',
define_macros = [('MAJOR_VERSION', '1'),
('MINOR_VERSION', '0')],
sources = ['grumbo.c'],
include_dirs = ['include'],
libraries = ['plumbus'],
library_dirs = ['lib'])
setup(
name = 'grumbo',
version = '1.0',
ext_modules = [native_module] )
由于lib铅是一个外部库,所以当我运行import grumbo
时,我得到:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: dlopen(/path/to/grumbo/grumbo.cpython-37m-darwin.so, 2): Library not loaded: lib/libplumbus.dylib
Referenced from: /path/to/grumbo/grumbo.cpython-37m-darwin.so
Reason: image not found
libplumbus
----什么是最简单的方法来设置这些东西,以便包含在发行版中,并在导入grumbo
时正确加载?(请注意,这应该适用于虚拟服务器)。
我尝试过将lib/libplumbus.dylib
添加到package_data
中,但这不起作用,即使我将-Wl,-rpath,@loader_path/grumbo/lib
添加到扩展的extra_link_args
中。
发布于 2020-09-10 13:17:08
这篇文章的目标是创建一个setup.py
,它将创建一个源发行版。这意味着在跑步之后
python setup.py sdist
生成的dist/grumbo-1.0.tar.gz
可通过以下方式用于安装
pip install grumbo-1.0.tar.gz
我们将开始为Linux/MacOS创建一个setup.py
,但随后对其进行调整,使其在Windows上也能正常工作。
第一步是将附加数据(包括/库)输入到发行版中。我不确定是否真的不可能为一个模块添加数据,但是setuptools
提供了为包添加数据的功能,所以让我们从您的模块中创建一个包(这可能是个好主意)。
包grumbo
的新结构如下:
src/
grumbo/
__init__.py # empty
grumbo.c
include/
plumbus.h
lib/
libplumbus.so
setup.py
改变了setup.py
from setuptools import setup, Extension, find_packages
native_module = Extension(
name='grumbo.grumbo',
sources = ["src/grumbo/grumbo.c"],
)
kwargs = {
'name' : 'grumbo',
'version' : '1.0',
'ext_modules' : [native_module],
'packages':find_packages(where='src'),
'package_dir':{"": "src"},
}
setup(**kwargs)
它还不起什么作用,但至少我们的包可以通过setuptools
找到。生成失败,因为缺少包含。
现在,让我们通过include
-folder将所需的包含添加到发行版中
...
kwargs = {
...,
'package_data' : { 'grumbo': ['include/*.h']},
}
...
这样,包含-文件就会被复制到源发行版中。但是,由于它将构建在我们还不知道的“某个地方”,因此将include_dirs = ['include']
添加到Extension
定义中并不能减少它。
找到正确的包含路径必须有更好的方法(并且不那么脆弱),但这正是我想出的:
...
import os
import sys
import sysconfig
def path_to_build_folder():
"""Returns the name of a distutils build directory"""
f = "{dirname}.{platform}-{version[0]}.{version[1]}"
dir_name = f.format(dirname='lib',
platform=sysconfig.get_platform(),
version=sys.version_info)
return os.path.join('build', dir_name, 'grumbo')
native_module = Extension(
...,
include_dirs = [os.path.join(path_to_build_folder(),'include')],
)
...
现在,扩展已经构建,但还不能加载,因为它没有与共享对象libplumbus.so
链接,因此一些符号没有得到解决。
与头文件类似,我们可以将库添加到发行版中:
kwargs = {
...,
'package_data' : { 'grumbo': ['include/*.h', 'lib/*.so']},
}
...
并为链接器添加右lib路径:
...
native_module = Extension(
...
libraries = ['plumbus'],
library_dirs = [os.path.join(path_to_build_folder(), 'lib')],
)
...
现在,我们就快到了:
扩展是构建到site-packages/grumbo/
ldd
libplumbus.so
的libplumbus.so
,可以看到,在ldd
libplumbus.so
的帮助下,它被放入了site-packages/grumbo/lib
但是,我们仍然不能导入扩展,因为import grumbo.grumbo
导致
ImportError: libplumbus.so:无法打开共享对象文件:没有这样的文件或目录
因为加载程序找不到所需的共享对象,它驻留在相对于我们的扩展的文件夹.\lib
中。我们可以使用rpath
“帮助”加载程序:
...
native_module = Extension(
...
extra_link_args = ["-Wl,-rpath=$ORIGIN/lib/."],
)
...
现在我们结束了:
>>> import grumbo.grumbo
# works!
此外,建造和安装一个轮子应该可以:
python setup.py bdist_wheel
然后:
pip install grumbo-1.0-xxxx.whl
第一英里的石头实现了。现在我们对它进行了扩展,因此它也可以运行其他平台。
用于Linux和Macos的相同的源代码发行版:
为了能够在Linux和MacOS上安装相同的源代码发行版,必须提供两个版本的共享库(用于Linux和MacOS)。一个选项是在共享对象的名称中添加一个后缀:例如,有libplumbus.linux.so
和libplumbis.macos.so
。根据平台的不同,可以在setup.py
中选择正确的共享对象:
...
import platform
def pick_library():
my_system = platform.system()
if my_system == 'Linux':
return "plumbus.linux"
if my_system == 'Darwin':
return "plumbus.macos"
if my_system == 'Windows':
return "plumbus"
raise ValueError("Unknown platform: " + my_system)
native_module = Extension(
...
libraries = [pick_library()],
...
)
对Windows的调整:
在Windows上,动态库是dlls而不是共享对象,因此需要考虑一些差异:
当生成C-扩展时,它需要lib
-subfolder.
plumbus.dll
-file.
rpath
的概念,因此我们需要将dll放在扩展旁边,这样就可以找到它(请参阅此SO-post以获得更多详细信息)。这意味着文件夹结构应该如下:
src/
grumbo/
__init__.py
grumbo.c
plumbus.dll # needed for Windows
include/
plumbus.h
lib/
libplumbus.linux.so # needed on Linux
libplumbus.macos.so # needed on Macos
plumbus.lib # needed on Windows
setup.py
setup.py
中也有一些变化。首先,扩展package_data
以便获取dll
和lib
:
...
kwargs = {
...
'package_data' : { 'grumbo': ['include/*.h', 'lib/*.so',
'lib/*.lib', '*.dll', # for windows
]},
}
...
其次,rpath
只能在Linux/MacOS上使用,因此:
def get_extra_link_args():
if platform.system() == 'Windows':
return []
else:
return ["-Wl,-rpath=$ORIGIN/lib/."]
native_module = Extension(
...
extra_link_args = get_extra_link_args(),
)
就这样!
完整的安装文件(您可能想要添加宏定义或类似的内容,我已经跳过了):
from setuptools import setup, Extension, find_packages
import os
import sys
import sysconfig
def path_to_build_folder():
"""Returns the name of a distutils build directory"""
f = "{dirname}.{platform}-{version[0]}.{version[1]}"
dir_name = f.format(dirname='lib',
platform=sysconfig.get_platform(),
version=sys.version_info)
return os.path.join('build', dir_name, 'grumbo')
import platform
def pick_library():
my_system = platform.system()
if my_system == 'Linux':
return "plumbus.linux"
if my_system == 'Darwin':
return "plumbus.macos"
if my_system == 'Windows':
return "plumbus"
raise ValueError("Unknown platform: " + my_system)
def get_extra_link_args():
if platform.system() == 'Windows':
return []
else:
return ["-Wl,-rpath=$ORIGIN/lib/."]
native_module = Extension(
name='grumbo.grumbo',
sources = ["src/grumbo/grumbo.c"],
include_dirs = [os.path.join(path_to_build_folder(),'include')],
libraries = [pick_library()],
library_dirs = [os.path.join(path_to_build_folder(), 'lib')],
extra_link_args = get_extra_link_args(),
)
kwargs = {
'name' : 'grumbo',
'version' : '1.0',
'ext_modules' : [native_module],
'packages':find_packages(where='src'),
'package_dir':{"": "src"},
'package_data' : { 'grumbo': ['include/*.h', 'lib/*.so',
'lib/*.lib', '*.dll', # for windows
]},
}
setup(**kwargs)
https://stackoverflow.com/questions/63804883
复制