分发 python 包时处理 dylib

Dealing with dylibs when distributing python packages

在使用 python setup.py bdist_wheel 或等效命令构建 Cython 模块后,我需要 运行 某些命令。这可能吗?

具体情况是这样的: 我正在为 osx 开发一个 Cython 程序。它链接到我的主目录中的某些 .dylib 文件并要构建程序,您要么必须 运行

export DYLD_LIBRARY_PATH=/path/to/parent/dir

(这很麻烦)或在 setup.py 到 运行

中包含一个指令
install_name_tool -change @executable_path/my.dylib /path/to/my.dylib

这基本上是告诉二进制文件在哪里寻找 dylib。这两种选择都不是理想的,但后者似乎更便于携带。现在我只是将这些说明添加到 setup.py 的底部,但这仍然不可移植:假设我按如下方式打包和上传我的包:

python setup.py sdist  # I think this collects source files into a .tar
python setup.py bdist_wheel  # I think this builds a binary. This is where I assume the post-compilation stuff should happen.
twine upload dist/*   # This puts stuff up on PyPi.

现在,我已经阅读了文档和一些教程,但我仍然不能 100% 确定这些命令的作用。但我知道,如果随后,在另一个项目中,我 运行

pip install mypackage

dylib 问题浮出水面:

ImportError: dlopen(/path/to/my/module.cpython-36m-darwin.so, 2): 
Library not loaded: @executable_path/my.dylib
  Referenced from: /path/to/my/module.cpython-36m-darwin.so
  Reason: image not found

解决方案似乎是在setup.py中添加某种自动post-编译方向,以指示需要执行install_name_tool操作。假设这是可能的,人们会怎么做呢?理想情况下,我想使用现成的 Cython.Build.cythonizesetuptools.Extension,但也许我需要做一些更个性化的事情。感谢您的建议!

解决方案

Danny 正确地指出 运行ning install_name_tool 不是一个可移植的解决方案,dylib 应该以某种方式包含在包中。 delocate 是完成这项工作的工具,但我遇到的第一个挑战是 delocate 不适用于 @executable_path:

❯ delocate-wheel -w fixed_wheels -v dist/*.whl
Fixing: dist/my-package-1.0.9-cp36-cp36m-macosx_10_12_x86_64.whl
/Users/ethan/virtualenvs/my-package/lib/python3.6/site-packages/delocate/delocating.py:71: UserWarning: Not processing required path @executable_path/my.dylib because it begins with @
  'begins with @'.format(required))

使用 otool,我能够验证 my.dylibid 使用 @executable_path:

❯ otool -L my.dylib
my.dylib:
    @executable_path/my.dylib (compatibility version 0.0.0, current version 0.0.0)
    /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 120.1.0)
    /System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL (compatibility version 1.0.0, current version 1.0.0)
    /System/Library/Frameworks/IOKit.framework/Versions/A/IOKit (compatibility version 1.0.0, current version 275.0.0)
    /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1258.1.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1226.10.1)

为了摆脱@executable_path我运行以下:

❯ install_name_tool -id my.dylib my.dylib

现在看看otool -L的输出:

❯ otool -L my.dylib
my.dylib:
    my.dylib (compatibility version 0.0.0, current version 0.0.0)
    /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 120.1.0)
    /System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL (compatibility version 1.0.0, current version 1.0.0)
    /System/Library/Frameworks/IOKit.framework/Versions/A/IOKit (compatibility version 1.0.0, current version 275.0.0)
    /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1258.1.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1226.10.1)

一般来说,这不是一个很好的 id 用于 dylib,但它足以 delocate-wheel 使用。我重建了 .whl:

python setup.py bdist_wheel

现在当我 运行 delocate_wheel:

❯ delocate-wheel -w fixed_wheels -v dist/*.whl
Fixing: dist/my.whl
Traceback (most recent call last):
...    
delocate.delocating.DelocationError: library "/Users/ethan/my-package/my.dylib" does not exist

这清楚地告诉我们 delocate 期望找到 my.dylib 的位置。所以我将 my.dylib 复制到 /Users/ethan/my-package/my.dylib 并重新 运行 命令:

❯ delocate-wheel -w fixed_wheels -v dist/*.whl
Fixing: dist/my.whl
Copied to package .dylibs directory:
  /Users/ethan/my-package/my.dylib

成功!这个 .dylibs 目录是什么?我 运行 tar -xvf my.whl 打开车轮包装并检查其内容:

❯ tree -a fixed_wheels/my-package
fixed_wheels/my-package
├── .dylibs
│   └── my.dylib
├── __init__.py
└── package.cpython-36m-darwin.so

如您所见,my.dylib 已被复制到 .dylibs/ 目录中,该目录打包在 my.whl 中。将 my.whl 上传到 pypi 后,我可以下载 运行 代码就好了。

看看delocate and the Linux equivalent auditwheel

不必在 运行 时更改 link 加载器路径,delocate 工具将第三方库嵌入轮子并相应地调整加载时间路径。

结果是一个可分发的二进制轮,其中正确包含了所有依赖库。

设置 运行 时间加载路径不可移植,不能跨 python 版本或机器架构工作。

例如,在 python setup.py bdist_wheel 运行:

创建轮子后

delocate-wheel -w fixed_wheels <wheel file>

固定轮文件将放置在fixed_wheels目录下。 运行 与 -v 一起查看它嵌入了哪些库。