跳转到主要内容

将macOS动态库移动到包中

项目描述

https://img.shields.io/pypi/v/delocate https://codecov.io/gh/matthew-brett/delocate/branch/master/graph/badge.svg?token=wvAWRBK5Di

Delocate

macOS实用工具,用于

  • 查找从Python扩展导入的动态库

  • 将所需动态库复制到包内的目录中

  • 更新macOS install_namesrpath,以使代码从库的副本中加载

提供脚本

  • delocate-listdeps – 显示树依赖的库

  • delocate-path – 将树依赖的库复制到树中并重新链接

  • delocate-wheel – 重写具有复制和重新链接库依赖项的wheel到wheel树。

  • delocate-merge – 将具有不同架构的两个wheel合并为一个具有双架构二进制的wheel。

Auditwheel 是一个类似的Linux工具。Auditwheel最初是Delocate的部分分支。

问题

假设你在某个地方构建了一个轮子,但是它链接到机器上的其他动态库,因此你无法分发它,因为其他人可能没有这些相同的库。在这里,我们分析Scipy轮子的依赖关系。

$ delocate-listdeps scipy-0.14.0b1-cp34-cp34m-macosx_10_6_intel.whl
/usr/local/Cellar/gfortran/4.8.2/gfortran/lib/libgcc_s.1.dylib
/usr/local/Cellar/gfortran/4.8.2/gfortran/lib/libgfortran.3.dylib
/usr/local/Cellar/gfortran/4.8.2/gfortran/lib/libquadmath.0.dylib

默认情况下,这不包括/usr/lib/System中的库。也可以用以下方式查看这些库:

$ delocate-listdeps --all scipy-0.14.0-cp34-cp34m-macosx_10_6_intel.whl
/System/Library/Frameworks/Accelerate.framework/Versions/A/Accelerate
/usr/lib/libSystem.B.dylib
/usr/lib/libstdc++.6.dylib
/usr/local/Cellar/gfortran/4.8.2/gfortran/lib/libgcc_s.1.dylib
/usr/local/Cellar/gfortran/4.8.2/gfortran/lib/libgfortran.3.dylib
/usr/local/Cellar/gfortran/4.8.2/gfortran/lib/libquadmath.0.dylib

输出告诉我,Scipy已经从我的Homebrew安装的gfortran(以及系统库)中获取了动态库。

您可以使用--depending标志列出每个库所依赖的文件。

$ delocate-listdeps --depending scipy-0.14.0-cp34-cp34m-macosx_10_6_intel.whl
/usr/local/Cellar/gfortran/4.8.2/gfortran/lib/libgcc_s.1.dylib:
    scipy/interpolate/dfitpack.so
    scipy/special/specfun.so
    scipy/interpolate/_fitpack.so
    ...

解决方案

我们可以这样修复

$ delocate-wheel -w fixed_wheels -v scipy-0.14.0-cp34-cp34m-macosx_10_6_intel.whl
Fixing: scipy-0.14.0-cp34-cp34m-macosx_10_6_intel.whl
Copied to package .dylibs directory:
    /usr/local/Cellar/gfortran/4.8.2/gfortran/lib/libgcc_s.1.dylib
    /usr/local/Cellar/gfortran/4.8.2/gfortran/lib/libgfortran.3.dylib
    /usr/local/Cellar/gfortran/4.8.2/gfortran/lib/libquadmath.0.dylib

-w标志告诉delocate-wheel将输出到新的轮子目录而不是覆盖旧的轮子。标志-v(详细)会告诉你delocate-wheel正在做什么。在这种情况下,它已经在轮子zip文件中创建了一个新目录,命名为scipy/.dylibs。它已经将所有位于macOS系统树之外的库依赖项复制到这个目录中,并且修复了轮子中的python .so 扩展以使用这些副本而不是在/usr/local/Cellar/gfortran/4.8.2/gfortran/lib中查找。

再次检查链接以确认

$ delocate-listdeps --all fixed_wheels/scipy-0.14.0-cp34-cp34m-macosx_10_6_intel.whl
/System/Library/Frameworks/Accelerate.framework/Versions/A/Accelerate
/usr/lib/libSystem.B.dylib
/usr/lib/libstdc++.6.0.9.dylib
@loader_path/../../../../.dylibs/libgcc_s.1.dylib
@loader_path/../../../../.dylibs/libgfortran.3.dylib
@loader_path/../../../../.dylibs/libquadmath.0.dylib
@loader_path/../../../.dylibs/libgcc_s.1.dylib
@loader_path/../../../.dylibs/libgfortran.3.dylib
@loader_path/../../../.dylibs/libquadmath.0.dylib
@loader_path/../../.dylibs/libgcc_s.1.dylib
@loader_path/../../.dylibs/libgfortran.3.dylib
@loader_path/../../.dylibs/libquadmath.0.dylib
@loader_path/../.dylibs/libgcc_s.1.dylib
@loader_path/../.dylibs/libgfortran.3.dylib
@loader_path/../.dylibs/libquadmath.0.dylib
@loader_path/libgcc_s.1.dylib
@loader_path/libquadmath.0.dylib

因此,系统dylibs相同,但其他库已移动到轮子树中。

这使得轮子在另一台机器上更有可能工作,该机器没有安装与Gfortran相同的版本 - 在这个例子中。

检查所需的架构

当前的Python.org Python和macOS系统Python(/usr/bin/python)都是双架构的二进制文件。例如

$ lipo -info /usr/bin/python
Architectures in the fat file: /usr/bin/python are: x86_64 arm64e

注意:您可以编译用于基本ARM(arm64)的二进制文件,或者用于一些扩展的ARM功能(arm64e) - 请参阅这篇SO帖子。这两种类型的二进制文件都适用于Mac M1和M2机器,因此我们将使用arm64来指代arm64arm64e

上述Big Sur macOS Python具有融合到同一个文件中的x86_64arm64(M1)版本。早期的macOS版本具有双架构,分别是32位(i386)和64位(x86_64)。

为了与系统和Python.org Python完全兼容,为Python.org Python或系统Python构建的轮子应该具有相应的架构 - 例如Python扩展和它们的库的x86_64arm64版本。很容易错误地将Python扩展链接到单一架构库,并因此获得单一架构扩展和/或库。事实上,我的Scipy轮子就是这样一个例子,因为我无意中链接到了仅限于x86_64的Homebrew库。要检查此,您可以使用--require-archs标志

$ delocate-wheel --require-archs=intel scipy-0.14.0-cp34-cp34m-macosx_10_6_intel.whl
Traceback (most recent call last):
File "/Users/mb312/.virtualenvs/delocate/bin/delocate-wheel", line 77, in <module>
    main()
File "/Users/mb312/.virtualenvs/delocate/bin/delocate-wheel", line 69, in main
    check_verbose=opts.verbose)
File "/Users/mb312/.virtualenvs/delocate/lib/python2.7/site-packages/delocate/delocating.py", line 477, in delocate_wheel
    "Some missing architectures in wheel")
delocate.delocating.DelocationError: Some missing architectures in wheel

请注意,此命令使用的是支持Python 2的早期版本的Delocate;我们现在仅支持Python 3。

上面--require-archsintel参数需要32位和64位架构的扩展和库。您可以通过添加-v(详细)标志来查看哪些扩展有错误

$ delocate-wheel -w fixed_wheels --require-archs=intel -v scipy-0.14.0-cp34-cp34m-macosx_10_6_intel.whl
Fixing: scipy-0.14.0-cp34-cp34m-macosx_10_6_intel.whl
Required arch i386 missing from /usr/local/Cellar/gfortran/4.8.2/gfortran/lib/libgfortran.3.dylib
Required arch i386 missing from /usr/local/Cellar/gfortran/4.8.2/gfortran/lib/libquadmath.0.dylib
Required arch i386 missing from scipy/fftpack/_fftpack.so
Required arch i386 missing from scipy/fftpack/convolve.so
Required arch i386 missing from scipy/integrate/_dop.so
...

我需要重新构建这个轮子以链接到双架构库。

制作双架构二进制文件

现代Mac轮子可以是arm64(M1/M2 ARM)、x86_64(64位Intel)或两者都(universal2)。

构建一个双架构的整个 Python 轮子可能会很困难,可能是因为你需要在两种情况下链接不同的库,或者你需要不同的编译器标志,或者因为你在某个持续集成平台(如 - 在撰写本文时 - Cirrus CI)上为 arm64 构建,而在另一个平台上为 x86_64 构建。

解决这个问题的一个方案是先进行整个 arm64 轮子构建,然后进行整个 x86_64 轮子构建,并将这两个轮子 融合 成一个通用轮子。

这就是 delocate-merge 命令所做的事情。

假设你已经构建了一个 ARM 和 Intel 轮子,分别命名为

  • scipy-1.9.3-cp311-cp311-macosx_12_0_arm64.whl

  • scipy-1.9.3-cp311-cp311-macosx_10_9_x86_64.whl

然后你可以使用以下命令在 tmp 子目录中创建一个新的融合(universal2)轮子

delocate-merge scipy-1.9.3-cp311-cp311-macosx_12_0_arm64.whl scipy-1.9.3-cp311-cp311-macosx_10_9_x86_64.whl -w tmp

在这种情况下,输出轮子将是

  • tmp/scipy-1.9.3-cp311-cp311-macosx_12_0_universal2.whl

在新轮子中,你会找到,使用 lipo -archs - 所有轮子中同名的二进制文件现在都是通用的(x86_64arm64)。

:warning: 注意: 在之前的版本(<0.12.0)中,使用 delocate-fuse 命令来创建双架构的二进制文件。默认情况下,该命令会覆盖传入的第一个轮子。这导致用户需要重命名轮子以正确描述其支持的平台。由于这个和其他原因,使用这种方式创建的轮子通常是不正确的。从版本 0.12.0 开始,已删除 delocate-fuse 命令,并替换为 delocate-mergedelocate-merge 命令将创建一个新轮子,其名称基于合并的轮子自动生成。不需要对合并轮子的名称进行任何其他更改。如果需要旧行为(不推荐),则将版本固定为 delocate==0.11.0

故障排除

DelocationError: “库不存在”

在运行 delocate-wheel 或其姐妹命令 delocate-path 时,你可能会遇到这样的错误

delocate.delocating.DelocationError: library "<long temporary path>/wheel/libme.dylib" does not exist

当你的某个库提供了一个具有相对路径的库依赖时,会发生这种情况

myext.so:
    libme.dylib (compatibility version 10.0.0, current version 10.0.0)
    /usr/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 60.0.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1197.1.1)

例如,假设我轮子中的某个文件对于 otool -L myext.so 的输出是这样的

myext.so:
    /path/to/libme.dylib (compatibility version 10.0.0, current version 10.0.0)
    /usr/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 60.0.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1197.1.1)

第一行表示 myext.so 预期在路径 ./libme.dylib 中找到 libme.dylib - 这是运行可执行文件的当前工作目录。输出应该是类似的东西

libme.dylib (compatibility version 10.0.0, current version 10.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1197.1.1)

要设置库的路径,链接器正在使用链接库时选择的 install name id。在这种情况下,otool -L libme.dylib 将给出类似的东西

代码

参见 https://github.com/matthew-brett/delocate

基于BSD双条款许可协议发布 - 请参阅源代码分发中的文件 LICENSE

GitHub Actions 自动测试代码。

最新发布的版本在 https://pypi.python.org/pypi/delocate

支持

请在 Delocate问题跟踪器 上提出问题。

项目详情


下载文件

下载适用于您的平台的文件。如果您不确定选择哪个,请了解更多关于 安装软件包 的信息。

源代码分发

delocate-0.12.0.tar.gz (238.8 kB 查看哈希值)

上传时间 源代码

构建分发

delocate-0.12.0-py3-none-any.whl (236.1 kB 查看哈希值)

上传时间 Python 3

支持者