Package: delocate
DelocatemacOS utilities to:find dynamic libraries imported from python extensionscopy needed dynamic libraries to directory within packageupdate macOSinstall_namesandrpathto cause code to load from copies
of librariesProvides scripts:delocate-listdeps– show libraries a tree depends ondelocate-path– copy libraries a tree depends on into the tree and relinkdelocate-wheel– rewrite wheel having copied and relinked library
dependencies into the wheel tree.delocate-fuse– combine two wheels with different architectures into one
wheel with dual architecture binaries.Auditwheelis a similar tool for Linux.
Auditwheel started life as a partial fork of Delocate.The problemLet’s say you have built a wheel somewhere, but it’s linking to dynamic
libraries elsewhere on the machine, so you can’t distribute it, because others
may not have these same libraries.  Here we analyze the dependencies for
a Scipy wheel:$ 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.dylibBy default, this does not include libraries in/usr/liband/System.
See those too with:$ 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.dylibThe output tells me that Scipy has picked up dynamic libraries from my
Homebrew installation ofgfortran(as well as the system libs).You can get a listing of the files depending on each of the libraries,
using the--dependingflag:$ 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
    ...A solutionWe can fix like this:$ 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.dylibThe-wflag tellsdelocate-wheelto output to a new wheel directory
instead of overwriting the old wheel.-v(verbose) tells you whatdelocate-wheelis doing.  In this case it has made a new directory in the
wheel zipfile, namedscipy/.dylibs. It has copied all the library
dependencies that are outside the macOS system trees into this directory, and
patched the python.soextensions in the wheel to use these copies instead
of looking in/usr/local/Cellar/gfortran/4.8.2/gfortran/lib.Check the links again to confirm:$ 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.dylibSo - system dylibs the same, but the others moved into the wheel tree.This makes the wheel more likely to work on another machine which does not have
the same version of Gfortran installed - in this example.Checking required architecturesCurrent Python.org Python and the macOS system Python (/usr/bin/python)
are both dual architecture binaries.  For example:$ lipo -info /usr/bin/python
Architectures in the fat file: /usr/bin/python are: x86_64 arm64eNote: you can compile ARM binaries for basic ARM (arm64), or to use
some extended ARM capabilities (arm64e) - seethis SO post.  Both
types of binaries work on Mac M1 and M2 machines, so we will usearm64to
refer to eitherarm64orarm64e.The Big Sur macOS Python above has bothx86_64andarm64(M1) versions
fused into one file.  Earlier versions of macOS had dual architectures that
were 32-bit (i386) and 64-bit (x86_64).For full compatibility with system and Python.org Python, wheels built for
Python.org Python or system Python should have the corresponding architectures
— e.g.x86_64andarm64versions of the Python extensions and their
libraries.  It is easy to link Python extensions against single architecture
libraries by mistake, and therefore get single architecture extensions and /
or libraries. In fact my Scipy wheel is one such example, because I
inadvertently linked against the Homebrew libraries, which werex86_64only. To check this you can use the--require-archsflag:$ 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 wheelNotice that this command was using an earlier version of Delocate that
supported Python 2; we now support Python 3 only.Theintelargument to--require-archabove requires dual 32- and 64-
bit architecture extensions and libraries. You can see which extensions are at
fault by adding the-v(verbose) flag:$ 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
...I need to rebuild this wheel to link with dual-architecture libraries.Making dual-architecture binariesModern Mac wheels can be eitherarm64(M1/M2 ARM),x86_64(64-bit
Intel) or both (universal2).Building an entire Python wheel as dual-architecture can be difficult, perhaps
because you need to link different libraries in the two cases, or you need
different compiler flags, or because you build forarm64on one continuous
integration platform (such as - at time of writing - Cirrus CI), andx86_64on another.One solution to this problem is to do an entirearm64wheel build, and then
an entirex86_64wheel build, andfusethe two wheels into a universal
wheel.That is what thedelocate-fusecommand does.Let’s say you have built an ARM and Intel wheel, called, respectively:scipy-1.9.3-cp311-cp311-macosx_12_0_arm64.whlscipy-1.9.3-cp311-cp311-macosx_10_9_x86_64.whlThen you could create a new fused (universal2) wheel in thetmpsubdirectory with:delocate-fuse scipy-1.9.3-cp311-cp311-macosx_12_0_arm64.whl scipy-1.9.3-cp311-cp311-macosx_10_9_x86_64.whl -w tmpThe output wheel in that case would be:tmp/scipy-1.9.3-cp311-cp311-macosx_12_0_arm64.whlNote that we specified an output directory above with the-wflag.  If we
had not done that, then we overwrite the first wheel with the fused wheel.  And
note that the wheel written into thetmpsubdirectory has the same name as
the first-specified wheel.In the new wheel, you will find, usinglipo-archs- that all binaries with
the same name in each wheel are now universal (x86_64andarm64).To be useful, you should rename the output wheel to reflect the fact that it is
now a universal wheel - in this case to:tmp/scipy-1.9.3-cp311-cp311-macosx_12_0_universal2.whlTroubleshootingDelocationError: “library does not exist”When runningdelocate-wheelor its sister commanddelocate-path, you
may get errors like this:delocate.delocating.DelocationError: library "<long temporary path>/wheel/libme.dylib" does not existThis happens when one of your libraries gives a library dependency with a
relative path.  For example, let’s say that some file in my wheel has this for
the output ofotool-Lmyext.so: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)The first line means thatmyext.soexpects to findlibme.dylibat
exactly the path./libme.dylib- the current working directory from which
you ran the executable.  The outputshouldbe something like: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)To set the path to the library, the linker is using theinstall name idof
the linked library.  In this bad case, thenotool-Llibme.dylibwill give
something like: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)where the first line is theinstall name idthat the linker picked up when
linkingmyext.sotolibme.dylib.  Your job is to fix the build process
so thatlibme.dylibhas install name id/path/to/libme.dylib.
This is not a problem specific to Delocate; you will need to do this to
make sure thatmyext.socan loadlibme.dylibwithoutlibme.dylibbeing in the current working directory.  ForCMAKEbuilds you may want to
check outCMAKE_INSTALL_NAME_DIR.CodeSeehttps://github.com/matthew-brett/delocateReleased under the BSD two-clause license - see the fileLICENSEin the
source distribution.travis-cikindly tests the
code automatically under Python 3.6 through 3.9.The latest released version is athttps://pypi.python.org/pypi/delocateSupportPlease put up issues on theDelocate issue tracker.