There are two ways to create an app bundle on MacOSX, the Easy and the Ugly.
The easy way is just to use XCode.
Done.
The problem is sometimes you can't.
In my case I'm building an app that builds other apps.
I can't assume the user has XCode installed.
I'm also using MacPorts to build the libraries my app depends on.
I need to make sure that these dylibs get bundled with the app before I distribute it.
Disclaimer: I'm totally unqualified to write this post, everything in
is has been gleamed from Apple docs, picking apart existing apps and
trial and error. It works for me, but is most likely wrong. Please
email me if you have any corrections.
First thing you should know is that an app bundle is just a directory.
Let's examine the structure of a hypothetical foo.app.
foo.app/
Contents/
Info.plist
MacOS/
foo
Resources/
foo.icns
Info.plist is a plain XML file.
You can edit it with a text editor or the Property List Editor app that comes bundled with XCode.
(It's in /Developer/Applications/Utilities/ directory).
The key things you need to include are:
CFBundleName - The name of the app.
CFBundleIcon - An Icon file assumed to be in Contents/Resources dir.
Use the Icon Composer app to create the icon. (It's also in the /Developer/Applications/Utilities/ directory)
You can just drag and drop a png onto it's window and should automatically generate the mip-levels for you.
CFBundleExecutable - Name of the executable file assumed to be in Contents/MacOS/ sub-folder.
There are lots more options, the ones listed above are only the bare minimum.
Here's some Apple documentation on the
Info.plist
file and
App bundle structure.
Also, Here's a sample Info.plist.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleGetInfoString</key>
<string>Foo</string>
<key>CFBundleExecutable</key>
<string>foo</string>
<key>CFBundleIdentifier</key>
<string>com.your-company-name.www</string>
<key>CFBundleName</key>
<string>foo</string>
<key>CFBundleIconFile</key>
<string>foo.icns</string>
<key>CFBundleShortVersionString</key>
<string>0.01</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>IFMajorVersion</key>
<integer>0</integer>
<key>IFMinorVersion</key>
<integer>1</integer>
</dict>
</plist>
In a perfect world you could just drop your executable into the
Contents/MacOS/ dir and be done. However, if your app has any
non-standard dylib dependencies it won't work. Like Windows, MacOS
comes with it's own special kind of DLL Hell.
If you're using MacPorts to build libraries that you link against, the locations of the the dylibs will be hard-coded into your executable.
If you run the app on a machine that has the dylibs in the exact same location, it will run fine.
However, most users won't have them installed; when they double-click your app it will just crash.
Before you distribute you executable you'll need to collect all the dylibs it loads and copy them into the app bundle.
You will also need to edit the executable so that it will look for the dylibs in the correct place. i.e. where you copied them to.
Hand editing an executable sounds dangerous right?
Luckily there are command line tools to help.
otool -L executable_name
This command will list all the dylibs that your app depends on.
If you see any that are NOT in the System/Library or usr/lib folder, those are the ones you'll need to copy into the app bundle.
Copy them into the /Contents/MacOS/ folder.
Next you'll need to edit the executable to use the new dylibs.
First, you need to make sure that you link using the -headerpad_max_install_names flag.
This just makes sure that if the new dylib path is longer then the previous one, there will be room for it.
Second, use the install_name_tool to change each dylib path.
install_name_tool -change existing_path_to_dylib @executable_path/blah.dylib executable_name
As a practical example, Let's say your app uses libSDL, and otool lists it's location as "/opt/local/lib/libSDL-1.2.0.dylib".
First copy it into the app bundle.
cp /opt/local/lib/libSDL-1.2.0.dylib foo.app/Contents/MacOS/
Then edit the executable to use the new location (NOTE: make sure you built it with the -headerpad_max_install_names flag)
install_name_tool -change /opt/local/lib/libSDL-1.2.0.dylib @executable_path/libSDL-1.2.0.dylib foo.app/Contents/MacOS/foo
Whew, we're almost done.
Now there's a small issue with the current working directory.
When you start your app the current directory will be the directory above where the application is located.
For example: If you place the foo.app in the /Applcations folder then the current directory when you launch the app will be the /Applications folder.
Not the /Applications/foo.app/Contents/MacOS/ as you might expect.
You can alter your app to account for this, or you can use this magic little launcher script that will change the current directory and launch your app.
#!/bin/bash
cd "${0%/*}"
./foo
Make sure you adjust the Info.plist file so that CFBundleExecutable points to the launch script and not to the previous executable.
Ok, all done now.
Luckily, once you know all this stuff you bury it in a build script.