Thursday 27 October 2011

Make a Mac OS X application bundle for your Linux app

That's something pretty handy and no so much documented. I mean you have plenty of examples on how making you Xcode project into a nice Mac OS X application, but when making a cross platform application using the good old Make, you don't really want to have two building systems on you hands. Plus you will need to embed somehow the dependency libraries that are not provided by Mac OS X. But rejoice, you can build a Mac OS X application bundle from scratch without to much of a hassle.

For this solution, I heavily inspired from the GTK+ application bundler. The principe is quite simple, you need to create an application bundle directory structure as described here. Place you executable in the directory Content/MacOS, and all your libs in Content/Resources. Now the Info.plist. This file will be the entry point for running you application bundle. It can do a lot of things, but we will use the bare minimum here (define the startup executable and the icon):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
<plist version="0.9">
  <dict>
    <key>CFBundleName</key>
    <string>PouillotPouillot</string>


    <key>CFBundleDisplayName</key>
    <string>Pouillot Pouillot</string>


    <key>CFBundleIdentifier</key>
    <string>com.PouillotPouillot</string>


    <key>CFBundleVersion</key>
    <string>0.0.2</string>


    <key>CFBundlePackageType</key>
    <string>APPL</string>


    <key>CFBundleSignature</key>
    <string>puyo</string>


    <key>CFBundleExecutable</key>
    <string>launcher.sh</string>


    <key>CFBundleIconFile</key>
    <string>pouillotpouillot.icns</string>
  </dict>
</plist>


The icon must be in the Content/Resources directory. You noticed here we don't directly use the binary to startup the application, but a launcher script. This is the key element of this bundle. It's a neat trick to save us the pain of creating a Framework bundle with all the libs required by our application. Let me show you this script:

#!/bin/sh
name="`basename $0`"
tmp="`pwd`/$0"
tmp=`dirname "$tmp"`
tmp=`dirname "$tmp"`
bundle=`dirname "$tmp"`
bundle_contents="$bundle"/Contents
bundle_res="$bundle_contents"/Resources
bundle_lib="$bundle_res"/lib
bundle_bin="$bundle_res"/bin
bundle_data="$bundle_res"/share
bundle_etc="$bundle_res"/etc


export DYLD_LIBRARY_PATH="$bundle_lib"


exec "$bundle_contents/MacOS/pouillotpouillot"

This where the magic happens. We retrieve the bundle directory, and its various sub-directories, and then set the DYLD_LIBRARY_PATH variable to be able to load properly the different libraries in the Resources directory. This script does the bare minimum, I encourage you to check the launcher script of the GTP+ app bundler.

That's it we're done, but you still have to be very careful with the loading of your libs with the other from Mac OS X. There can be conflicts. For instance if your application embeds libpng you can get this kind of error at startup:

dyld: Symbol not found: __cg_png_create_info_struct
  Referenced from: /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ImageIO.framework/Versions/A/ImageIO
  Expected in: ///Users/antoine/progs/pouillotpouillot/PouillotPouillot.app/Contents/Resources/lib/libpng15.15.dylib
 in /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ImageIO.framework/Versions/A/ImageIO

What happened is your application loads its libpng library, then loads ImageIO frameworks, and ImageIO framework itself uses libpng. But instead of getting the libpng from /usr/X11/lib that was expected, it gets the one from your application bundle. And yours is different from the one from the system. So you better use the one from the system. In case you really need your version and avoid the library from the system, or seems you can use DYLD_FALLBACK_LIBRARY_PATH, but i didn't tried it yet.

Hope this will help you make more cross-platform applications. It's still a pity to see nice apps not available for your beloved system ;)