February 01, 2005

Creating OS X application bundles step by step

Introduction

This guide is the result of attempting to package a network visualiser (tcprose) I wrote so that it can be distributed as an application bundle. I gathered information from many places, and while I try to be accurate as possible should you notice any bugs feel free to contact me.

Step 1 - The folder hierachy

Each application bundle is on disk a file that ends in .app and contains a strict folder hierachy. The folder hierarchy is the form:
  • application.app
    • Contents
      • Info.plist
      • Frameworks
        • dependent non-standard libraries
      • MacOS
        • executable binary
      • Resources
        • icons and other support files
The first thing you need to do then is to create such a folder tree somewhere.

Step 2 - The binaries

The binaries of your program reside in the MacOS folder. If your program depends on libraries that are not present by default, then you need to do the following:
  1. Figure out what libraries your application depends on, and figure out what libraries need to be distributed with the program. In order to see the dynamic libraries your binaries depend on, use the otool thus:
    otool -L binary
    In my case:
    solaris:~/code/tcprose steve$ otool -L tcprose
    tcprose:
    /sw/lib/libSDL-1.2.0.dylib (compatibility version 8.0.0, current version 8.1.0)
    /System/Library/Frameworks/Cocoa.framework/Versions
    /A/Cocoa (compatibility version 1.0.0, current version 9.0.0)

    /System/Library/Frameworks/OpenGL.framework/Versions
    /A/OpenGL (compatibility version 1.0.0, current version 1.0.0)

    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 71.1.1)
    /sw/lib/libpcap.0.dylib (compatibility version 0.8.3, current version 0.8.3)
    solaris:~/code/tcprose steve$
  2. Anything in /System or /usr can be safely assumed to be present on all default systems. Notice then the 2 libraries in /sw/lib. For those unfamiliar with fink, its where it installs all its packages. Those 2 libraries need to be copied into the Frameworks folder. Do be careful however that you copy the actual file, not the symbolic links. For example:
    solaris:/sw/lib steve$ ls -l libpcap.0.dylib
    lrwxr-xr-x 1 root admin 19 20 Aug 03:35 libpcap.0.dylib -> libpcap.0.8.3.dylib
    solaris:/sw/lib steve$
    Notice that libpcap.0.dylib is in fact a symlink to libpcap.0.8.3.dylib, thus you need to copy /sw/lib/libpcap.0.8.3.dylib, not /sw/lib/libpcap.0.dylib.
  3. Once the required libraries have been copied into Frameworks folder, its time to do some linking manipulation. Use the install_name_tool to remapped the dynamically linked libraries so your binary links against the libraries in Frameworks folder. To do this you issue the command:
    install_name_tool -change old_library_path new_library_path binary
    In tcprose's case:
    solaris:/sw/lib steve$
    install_name_tool -change /sw/lib/libpcap.0.dylib @executable_path/../Frameworks/libpcap.0.8.3.dylib ./tcprose
    Notice the use of @executable_path: this is what makes it "tick". @executable_path will automatically map to the path where the executable is located. Now if you move the binary into the MacOS directory, it will link against the libraries you have copied into the Frameworks folder
Word of caution: as far as I know there is no way to re-map the paths of any files in the binaries. Thus if you open files by using fopen("file", "rw"); then it will fail, because the working directory will be the directory that contains the application bundle, not the directory where the binary is stored. You will therefore need to take this into account, and adjust your paths accordinly.

Note
: It might be possible to set the environmental variable PWD via Info.plist (see next step) to certain directory so that the working directory is somewhat more manageable. I didn't have any luck with this, so kindly let me know if you figure something out.

Update - 6/3/05: as pointed out by a reader, you can extract the path of the executable from argv[0], and thus determine the relative paths of whatever resources your program needs.

Step 3 - Info.plist

Now we come to the most important step of creating an application bundle: creating a proper Info.plist inside the Contents folder. Info.plist is an xml file that describes properties that finder reads Info.plist and determines many things, including which binary inside MacOS to execute, what icon inside Resources to display, etc. The best tool to use for editing it is no doubt Property List Editor in /Developer/Applications/Utilities. You can of course do this by hand. Apple has a detailed document on how to create a proper Info.plist file, but at the absolute minimum the following keys are required:
Extra: if you want to specify a custom icon, first create a .icns file with Icon Composer found in /Developer/Applications/Utilities then save the .icns file in the Resources folder, and add the CFBundleIconFile key.

Conclusion

Creating an application bundle is a relatively easy affair, the most difficult part being remapping the dynamically linked libraries and adjusting any hardwired paths and generating a proper Info.plist file. Have fun, and if you have any suggestions or bug reports, do let me know!

Cheers,
Steve