multipart-message nevyn bengtsson's blog

featured articles 🦄, about, archive, tags

Properly bundling .frameworks in your application package

Update: My blog post on @rpath supersedes/complements this post (it’s not finished yet, though).



I’m sure you’ve run into it. You build your app and it works fine, but when you distribute it, your users get:

Library not loaded: /Users/Richard/Library/Frameworks/libmng.framework/Versions/A/libmng
  Referenced from: /Users/nevyn/Downloads/Sphere-Mac RC3/SphereEngine.app/Contents/MacOS/SphereEngine
  Reason: image not found

One more time. This subject seems to pop up quite often, but I think I’ve finally gotten it nailed. Before I used to fetch the sources of all the libraries I was using, set up an .xcodeproj and set install_name to “@executable_path/../Frameworks/” (and that I did here). That’s not really necessary, and not possible for non-open source frameworks. So, no matter what framework or library you have, this is how you bundle it anyway, no matter what the install_name is.

  1. Add an extra Copy Files target action in your XCode project (and rename it Copy Frameworks)
  2. Get info on the new terget action, and set its destination to Frameworks.
  3. Then, drag all custom frameworks to this target action, and they will be automatically bundled with the application when you build.
  4. You will now need to gather some information. Run `otool -L on YourApp.app/Contents/MacOS/YourApp` and note its output for each of the lines corresponding to a framework you just bundled.
  5. Next, add a Shell Script target action. This target action will call the install_name_tool to rewrite the linking information in the built binary to reference the bundled frameworks instead, even if the frameworks haven’t been built with an install_name of @executable_path/../Frameworks. Copy and modify as appropriate:
function relocateLibraryInCurrentApp() {
  install_name_tool -change $1$2 @executable_path/../Frameworks/$2 $CONFIGURATION_BUILD_DIR/$EXECUTABLE_PATH
}

relocateLibraryInCurrentApp /usr/local/lib/ libfmodex.dylib #note the space
relocateLibraryInCurrentApp /Library/Frameworks/ Foobar.framework/Versions/A/Foobar #note the space
Note the two different styles for a loose dylib and for a .framework. Just add one relocateLibraryInCurrentApp for each library or framework you’re bundling. Good luck!


Addendum: I realized that making a post that just describes how to do something, not why, is kind of lame.

In Mac OS, each binary contains a list of paths to binaries which it was linked to and which need to be loaded for all code to be available. When you launch an app, the runtime will thus load the app’s code, and for each library it needs to find will try to load it from the path set in the app’s binary. (For frameworks it’ll also look in /System/Library/Frameworks and /Library/Frameworks). What install_name_tool does is simply to rewrite that path inside the binary given as the fourth argument, searching exactly for the string argument #2, and change it to argument #3.

Additional resources:

Tagged frameworks, cocoa, coding