Versioned Frameworks Considered Harmful?
It has recently  come to my attention that using versioned frameworks without binary compatible APIs simply doesn't work all too well. Essentially, you may have an older version installed, but without swapping symlinks around, you you won't be able to use the older version for anything except runtime compatibility with software that is already linked.
This isn't a problem with the dyld runtime, simply a problem with gcc's compiler and linker (and supposedly there may be runtime bugs with CFBundle and/or NSBundle in this arena as well).
Mac OS X frameworks are structured in such a way that allow for versioned code, data, and headers. A typical multi-versioned framework looks something like this:
Foo.framework/ Foo -> Versions/Current/Foo Headers -> Versions/Current/Headers Resources -> Versions/Current/Resources Versions/ Current -> 2.0 2.0/ Foo Headers/ Resources/ 1.0/ Foo Headers/ Resources/
Mach-O, MH_DYLIB, and the dyld runtime
The MH_DYLIB object file, the part that you link to, is "Foo". If this were not a framework, it would have the following layout, and it would be application-specific to put the headers and resources in the right place:
libFoo.dylib -> libFoo.2.0.dylib libFoo.1.0.dylib libFoo.2.0.dylib
Due to the way that MH_DYLIB object files work, each of these dylibs know their own (supposed) location on the filesystem. This is called the "install name" or the "id" of the dylib. Technically, this is a LC_ID_DYLIB load command in the Mach-O header. When you create an Mach-O object file (executable, dylib, bundle, etc.) that depends on another, it will generate a load command (LC_LOAD_DYLIB) referencing the other dylib. The data associated with the LC_LOAD_DYLIB is exactly the same data that was provided by the linked-to MH_DYLIB in its LC_ID_DYLIB load command, unless explicitly overrided with the -dylib_file option to ld(1) or rewritten post-link with a tool such as install_name_tool(1). The otool(1) tool can be used to view these load commands in a Mach-O file, among other things. It's very important to have these values set correctly because they are used by the dyld runtime to locate the intended MH_DYLIB.
gcc, ld, and frameworks
Apple's GCC includes many changes to support Objective C/C++ and framework based development. Unfortunately, none of them have any explicit support for versioned frameworks. For example, gcc(1) states that it uses the following algorithm for finding frameworks in headers:
-Fdir In Apple's version of GCC only, add the directory dir to the head of the list of directories to be searched for frameworks. The framework search algorithm is, for an inclusion of <Fmwk/Header.h>, to look for files named path/Fmwk.framework/Head- ers/Header.h or path/Fmwk.framework/PrivateHeaders/Header.h where path includes /System/Library/Frameworks/ /Library/Frameworks/, and /Local/Library/Frameworks/, plus any additional paths specified by -F. All the -F options are also passed to the linker.
ld(1) has an similar option, -framework, which has a similar algorithm:
-framework name[,suffix] Specifies a framework to link against. Frameworks are dynamic shared libraries, but they are stored in different locations, and therefore must be searched for differently. When this option is specified, ld searches for framework `name.framework/name' first in any directories specified with the -F option, then in the standard framework directories /Library/Frameworks, /Net- work/Library/Frameworks, and /System/Library/Frameworks. The placement of the -framework option is significant, as it deter- mines when and how the framework is searched. If the optional suffix is specified the framework is first searched for the name with the suffix and then without.
Note that neither of these options allow for any consideration for the Versions directory in a framework, and therefore only link to whichever version was installed last, because the installation process for a framework will create the symlinks that point to locations inside the Versions directly.
Unfortunately, since there is no support or hook that will allow proper usage of versioned frameworks, the workarounds are all ugly. I think the following workaround is the most appropriate: Just Don't Use GCC's Search Algorithms.
- Always use the compiler option -I/Path/To/Foo.framework/Versions/IntendedVersion/Headers
- use #include "Foo.h" instead of #include <Foo/Foo.h> in your sources
- Always use the direct path to the MH_DYLIB rather than any combination of -framework and -F.
- Or, if you're building extension bundles that will be used by an executable that already has the correct version of Foo linked in, use the -undefined dynamic_lookup linker option
A minimal compiler/link line for an executable would look like the following:
env MACOSX_DEPLOYMENT_TARGET=10.3 cc -I/Path/To/Foo.framework/Versions/IntendedVersion/Headers -o usesFoo usesFoo.m /Path/To/Foo.framework/IntendedVersion/Foo
And a minimal compiler/link line for an extension bundle would look like:
env MACOSX_DEPLOYMENT_TARGET=10.3 cc -I/Path/To/Foo.framework/Versions/IntendedVersion/Headers -o fooUsingExtension.bundle fooUsingExtension.m -bundle -undefined dynamic_lookup
Quite ugly, eh? At least it works as intended. Unfortunately you'll need to emulate enough of GCC's search algorithms to find the framework, which is probably quite problematic from Xcode, but shouldn't be too hard from say, distutils, SCons, autoconf, etc. Note that you should also probably consider the NEXT_ROOT environment variable when building against an SDK.
|I wrote this a few months ago, before I had replaced PyDS, so "recently" means Novemberish.