使用编译的动态共享库分发Python包

37

如何将预编译的 .so 库和 Python 模块打包在一起?具体来说,我该如何编写 setup.py,以便在 Python 中执行以下操作:

>>> import top_secret_wrapper

不需要设置 LD_LIBRARY_PATH,就可以轻松找到 top_secret.so 吗?

在我的模块开发环境中,我有以下文件结构:

.
├── top_secret_wrapper
│   ├── top_secret.so
│   └── __init__.py
└── setup.py

__init__.py中,我有以下代码:

import top_secret

这是我的setup.py文件

from setuptools import setup, Extension

setup(
    name = 'top_secret_wrapper',
    version = '0.1',
    description = 'A Python wrapper for a top secret algorithm',
    url = None,
    author = 'James Bond',
    author_email = 'James.Bond.007@mi6.org',
    license = 'Spy Game License',
    zip_safe = True,
)

我确定我的setup.py缺少一个设置,用于指定top_secret.so的位置,尽管我不确定如何做到这一点。

4个回答

15

我最终做的是:

setup(
    name='py_my_lib',
    version=version,  # specified elsewhere
    packages=[''],
    package_dir={'': '.'},
    package_data={'': ['py_my_lib.so']},
)
这样我就可以通过库的名称导入它,而不必再增加另一层嵌套:
import py_my_lib

而不是

from py_my_lib_wrapper import py_my_lib

我不确定这是否是一个好的解决方案,因为安装时它会直接将.so文件放在pythonx.x/site-packages目录层级下。在我看来,一个干净的软件包分发应该将每个软件包的文件放在软件包目录中,即:pythonx.x/site-packages/top_secret/。 - souch

4

3
如果该库也应该在安装期间进行编译,则可以将其描述为扩展模块。如果你只想分发它,将其添加为包数据即可。请参考描述扩展模块安装包数据

编译.so不是一个选项,因为我没有它的C源代码。 - Kit
那就把它变成package_data吧? - renefritze

1
我成功地将一个具有其他.so依赖项的.so文件捆绑到其Python包目录中,方法如下:
  • 使用pybind11和cmake构建mypackage_bindings.cpython-310-x86_64-linux-gnu.so,其中包含C++的Python绑定。
  • 使用这个最小的Python包目录结构:
setup.cfg
setup.py
README.md
mypackage/__init__.py
mypackage/mypackage_bindings.cpython-310-x86_64-linux-gnu.so
mypackage/some_deps.so

将 mypackage_bindings.so 及其 .so 依赖项的 rpath 设置为 $ORIGIN,在 Linux 上使用以下命令(以便链接器在相同的 .so 目录中搜索依赖项):
patchelf --set-rpath '$ORIGIN' mypackage_bindings.cpython-310-x86_64-linux-gnu.so
patchelf --set-rpath '$ORIGIN' some_deps.so

将以下内容放入`mypackage/__init__.py`文件中:
import os
import sys

cur_file_dir = os.path.dirname(os.path.realpath(__file__))

# add current file directory so that mypackage_bindings.so is found by python
sys.path.append(cur_file_dir)

# set current file directory as working dir so that mypackage_bindings.so dependancies
# will be found by the linker (mypackage_bindings.so and its deps RPATH are set to $ORIGIN)
os.chdir(cur_file_dir)

# load every symbols of mypackage_bindings into upper mypackage module
from mypackage_bindings import *
  • setup.py中添加:
from setuptools import setup

setup(
    name='mypackage',
    packages=['mypackage'],
    package_dir={'mypackage': 'mypackage'},
    package_data={'mypackage': ['*.so', 'lib*']},
    description='Provides mypackage to python users',
    version='0.1',
    url='https://yo.com',
    author='truc muche',
    author_email='contact@truc-muche.com',
    keywords=['pip', 'mypackage']
    )

从最小的Python软件包目录中,执行以下命令创建Python软件包:
``` python3 setup.py sdist ```
这样做的好处是无需设置LD_LIBRARY_PATH变量,.so文件将安装在pythonX.X/site-packages/mypackage/目录中。

你确定RPATH是由ld传播的吗? 根据我的例子,我认为第二个rpath是不必要的。但是对于我的代码来说,由于some_deps.so实际上依赖于另一个库(我将其放在同一个包目录中),我必须在some_deps.so上设置rpath。 - undefined
在你给出的链接中,我猜这一行让你认为RPATH被传播了:“1.库的DT_RPATH动态部分属性导致了查找”? 但对我来说,这并不意味着传播:如果liba.so(有RPATH)加载libb.so(没有RPATH),它会成功。然后,如果libb.so尝试加载libc.so(没有RPATH),它会失败,因为ld会查看导致查找的库(即libb.so)。 - undefined
嗯,确实链接不清楚,但RPATH是传播的,例如参见https://linux.die.net/man/3/dlopen的说明。事实是,即使对于“如果调用程序的可执行文件包含DT_RPATH标签,并且不包含DT_RUNPATH标签,则会搜索DT_RPATH标签中列出的目录。”在Python扩展的上下文中,“可执行文件”到底是什么并不是很清楚。 - undefined
在Python的上下文中,可执行文件就是Python解释器。 - undefined
有趣,我得找时间去研究一下,可能会用一些玩具来进行实验。 - undefined
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接