install_name_tool and Universal Binaries How arm64 Affected an x86_64 App, and How We Fixed It
With Apple moving to Apple Silicon for all of their new products, software developers are having to figure out how to support both Intel and Apple Silicon devices. Like some of our customers, you may not be ready to move to Apple Silicon. Unfortunately, that doesn’t necessarily exempt you from dealing with universal binaries, especially when it comes to third party dependencies.
One of our customers has recently started to see the following error in their package builds for macOS:
$ install_name_tool -delete_rpath /usr/local/lib <app bundle>/Contents/Python3/dist-packages/scipy/odr/__odrpack.cpython-39-darwin.so
error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/install_name_tool: no LC_RPATH load command with path: /usr/local/lib found in: <app bundle>/Contents/Python3/dist-packages/scipy/odr/__odrpack.cpython-39-darwin.so (for architecture arm64), required for specified option "-delete_rpath /usr/local/lib"
The customer project packages the SciPy python module and, as part of the packaging step, it uses install_name_tool
to strip away any unwanted RPATHs.
If we read the error message closely, we can see that the path /usr/local/lib
is not found “for architecture arm64.” We don’t care about Apple Silicon for this project, so why is this affecting us?
It turns out that SciPy has started distributing universal binaries — binaries that support both Intel (x86_64) and Apple Silicon (arm64) architectures:
$ lipo -archs <app bundle>/Contents/Python3/dist-packages/scipy/odr/__odrpack.cpython-39-darwin.so
x86_64 arm64
We can’t make install_name_tool
ignore the missing path for arm64. Instead, we can strip away the unwanted arm64 parts, making install_name_tool
happy with our request:
# Rename the original library
$ mv <app bundle>/Contents/Python3/dist-packages/scipy/odr/__odrpack.cpython-39-darwin.so <app bundle>/Contents/Python3/dist-packages/scipy/odr/__odrpack.cpython-39-darwin.so.universal
# Extract the x86_64 parts
$ lipo -thin x86_64 -output <app bundle>/Contents/Python3/dist-packages/scipy/odr/__odrpack.cpython-39-darwin.so <app bundle>/Contents/Python3/dist-packages/scipy/odr/__odrpack.cpython-39-darwin.so.universal
# Clean up the original
$ rm -f <app bundle>/Contents/Python3/dist-packages/scipy/odr/__odrpack.cpython-39-darwin.so.universal
Now we’ve seen how a new architecture that we don’t yet support has affected an existing application. As universal binaries are becoming more pervasive in our dependency chains, we expect to see more of this kind of problem in the future. However, the tooling aims to resolve these kinds of issues. We hope that our simple solution helps anyone else who encounters this kind of issue.
Our consulting and troubleshooting services extend across the full stack at any level of integration. We have the flexibility to work with you from definition, to review and implementation.
If you like this article and want to read similar material, consider subscribing via our RSS feed.
Subscribe to KDAB TV for similar informative short video content.
KDAB provides market leading software consulting and development services and training in Qt, C++ and 3D/OpenGL. Contact us.