While I worked for Marko Karrpinen & Co. I only made one commit to BaseTen, but as Sappho would say, that one was a doozie! BaseTen is an open source Cocoa framework for PostgreSQL. It has an API resembling Apple’s Core Data framework, which uses SQLite. You can check out the source and build
BaseTen.framework as well as the optional
BaseTenAppKit.framework and an Interface Builder palette,
BaseTenPalette.palette. (By the way, I abhor Interface Builder. Or at least Interface Builder 2. I’ll reserve judgment on Interface Builder 3 until I learn more about it, and only then will I abhor it.) The frameworks are designed to be embedded within your application’s bundle, in the standard location for embedded frameworks: the directory
An app needs to know how to locate linked frameworks at runtime, so at compile time the app’s executable gets a record of each linked framework’s install name. An install name is, as you should expect by now, not a name. It’s a path, namely, the location of the dynamic library containing the framework’s code. To be exact, the install name is where the library should be at runtime, for a library wouldn’t even need an install name if it just indicated where the library actually is at compile time. Install names enable you to target Panther, for example, while still compiling with Tiger. You can use the command-line
otool -D to see that the install name of
Using absolute paths for install names is fine when your app links against system frameworks, which reside in pre-determined locations, but absolute paths won’t suffice when your app links against embedded frameworks, because the app could be installed almost anywhere in the file hierarchy, e.g.,
/Volumes/MyDistributionDmg. That’s why an embedded framework needs a relative path install name. The BaseTen and BaseTenAppKit projects achieve this by setting the build setting
INSTALL_PATH (what else would you do with a build setting but set it?) to
@executable_path/../Frameworks. The relative
@executable_path is the path to the
Contents/MacOS directory in your application’s bundle. When BaseTen is built with that build setting (to answer my last question, you would build with it), the install name of the framework becomes
as you can verify with
otool -D. Thus, when your app links against
BaseTen.framework and records the install name, it can find the framework in its own bundle at runtime.
BaseTen’s IB palette needs to use the BaseTen frameworks too. The problem, however, is that if the frameworks are built to be embedded in an application, they aren’t configured correctly to be embedded in the palette. When Interface Builder launches it will fail to load the palette, logging an error:
Interface Builder *** -[NSBundle load]: Error loading code /Users/jeff/Library/Palettes/BaseTenPalette.palette/Contents/MacOS/BaseTenPalette for bundle /Users/jeff/Library/Palettes/BaseTenPalette.palette, error code 4 (link edit error code 4, error number 0 (Library not loaded: @executable_path/../Frameworks/BaseTen.framework/Versions/A/BaseTen Referenced from: /Users/jeff/Library/Palettes/BaseTenPalette.palette/Contents/MacOS/BaseTenPalette Reason: image not found))
The reason that the image is not found — the reason behind the reason — is that the executable in this case in not actually BaseTenPalette but rather Interface Builder itself, which is trying to load the palette. The
@executable_path leads to
but BaseTen is embedded in
so the install name doesn’t locate the framework at runtime.
In Tiger, the relative
@loader_path was introduced to supplement
@loader_path is relative to the image loading the dynamic library, wherever that image may be. Thus, if we change the install name of BaseTen to
and the install name of BaseTenAppKit to
then BaseTenPalette should be able to locate the frameworks when Interface Builder launches. Problem solved, right?
If you’ve already skipped ahead to the end of this post, you’ll know that the problem is not solved. (Spoiler alert: Harry drops out of school to follow Trey Anastasio.) We’ve eliminated one error only to find another:
Interface Builder *** -[NSBundle load]: Error loading code /Users/jeff/Library/Palettes/BaseTenPalette.palette/Contents/MacOS/BaseTenPalette for bundle /Users/jeff/Library/Palettes/BaseTenPalette.palette, error code 4 (link edit error code 4, error number 0 (Library not loaded: @loader_path/../Frameworks/BaseTen.framework/Versions/A/BaseTen Referenced from: /Users/jeff/Library/Palettes/BaseTenPalette.palette/Contents/MacOS/../Frameworks/BaseTenAppKit.framework/Versions/A/BaseTenAppKit Reason: image not found))
Whereas BaseTenPalette can now find BaseTen at runtime, BaseTenAppKit cannot. They both have a record of BaseTen’s install name as
but they don’t have the same
At this point, you may throw up your hands and throw in the towel, exclaiming
Alas, BaseTen cannot have two install names! — or some such exclamation perhaps not suitable for children. (
Oh rat farts!). Yet your exclamation would be in vain, because the install name of the dynamic library doesn’t matter after linking. All that matters is the install name recorded in the linked executable, and that can be forged.
Apple provides the nefarious command-line
install_name_tool to forge install names for dylibs and give them fake id’s. This is how frameworks get into bars, since there are very few that are twenty-one years old. You can examine the details of my fix in the BaseTen Trac, but basically what I did to allow BaseTenAppKit to find BaseTen was to run the following command in a build phase script for BaseTenPalette:
install_name_tool -change \ "@executable_path/../Frameworks/BaseTen.framework/Versions/A/BaseTen" \ "@loader_path/../../../../Frameworks/BaseTen.framework/Versions/A/BaseTen" \ "$TARGET_BUILD_DIR/$FRAMEWORKS_FOLDER_PATH/BaseTenAppKit.framework/Versions/A/BaseTenAppKit"
I discovered the correct install name through a stroke a genius, or to put it another way, trial and error. The
@loader_path for BaseTenAppKit turns out to be
which makes sense in retrospect, but if you could guess
../../../.. on your first try, you’re a superfreak. Anyway, you can check the install names before and after with
otool -l, or succinctly with
Caveat developtor: for
install_name_tool to work, you may need to build your frameworks with the option
-header-pad_max_install_names. BaseTen already does this. See the man pages for more information, man.