Sunday, July 09, 2006

Dealing with outdated open source libs in Mac OS X

Mac OS X system frameworks heavily rely on open source libraries. For example, the NSXML classes of the Foundation framework are wrappers around libxml2. The problem is that libxml2 bundled into Mac OS X (10.4.7 as of writing) is version 2.6.16, dating back from november 2004! Current version is 2.6.26 and obviously has fixed a lot of bugs since version 2.6.16.

A specific bug I discovered was rather annoying: NSXMLDocument's validateAndReturnError: method would validate an invalid document. You guessed it, an up-to-date version of libxml2 doesn't suffer from this bug. So the solution to the problem would be to compile the latest version of libxml2 yourself and use this one for your application instead of the system version. This sounds easy but is in fact far from being trivial.

Compiling an universal binary version of libxml2 is easy, this is achieved with the following commands:
$ env CFLAGS="-arch i386 -arch ppc" LDFLAGS="-Wl,-syslibroot,/Developer/SDKs/MacOSX10.4u.sdk" ./configure --disable-dependency-tracking --enable-static=no --without-python
$ make

Now, libxml2.2.6.26.dylib is almost ready to use inside the .libs directory. I said almost because its install_name is /usr/local/lib/libxml2.2.dylib. Unless you plan to make an installer for your application, you should change it so that it is relative to your application. For example, if your application bundle looks like this:

Contents
  Info.plist
  MacOS
    MyGreatApp
  PkgInfo
  Resources
    ...
  lib
    mygreatlib.dylib
    libxml2.2.6.26.dylib

copy the built library (so that you still have the original dylib in case of problem) and change its install_name with the following commands:
$ cp .libs/libxml2.2.6.26.dylib .
$ install_name_tool -id @executable_path/../lib/libxml2.2.6.26.dylib libxml2.2.6.26.dylib

Now, your application must link against your version of libxml2. To do so, add libxml2.2.6.26.dylib into your Xcode project and check that it has been added to the Link Binary With Libraries phase of your current target.

The latest step is to make sure your libxml2.2.6.26.dylib is going to be used instead of /usr/lib/libxml2.2.dylib at runtime. The problem is that /usr/lib/libxml2.2.dylib uses two-level namespace, meaning that the Foundation framework will always use this one instead of yours. The solution is to force flat namespace by setting the DYLD_FORCE_FLAT_NAMESPACE environment variable. This is achieved by adding the following key in your Info.plist file:

<key>LSEnvironment</key>
<dict>
  <key>DYLD_FORCE_FLAT_NAMESPACE</key>
  <string>YES</string>
</dict>


Your application now uses the latest bug-free version of the lib :-)

This example used libxml2 but obviously apply to any other open source library.

3 comments:

Romain Goyet said...

Hi there !

Really nice tutorial ! I used it to bundle MySQL in my Cocoa app. Thanks for the tips :-)

andy said...

Holy crap,
you have no idea of how long it took me to find this info. I've been trying to build a universal binary of libxml2 for 2 days!
Getting the source code is easy, making the intel version is easy, but if you don't know the right flags you're sunk.

Thanks.

Anonymous said...

THANKS!! saved the day, today I successfully compiled libxml2 2.6.32 using macports and a special variant based on your flags.

again, thanks!!