Building binaries on Mac OS/X

Compiling sources into binaries on Mac OS/X, and getting everything right when creating an application bundle (= a .app directory which represents an application), there are many details that might frustrate the casual coder or are not that easy to find information about. I've described some of them here, in any order. Use the search function of your browser, etc.

Which Python version

Several python versions are bugged or behave annoyingly when compiling binaries. For instance, by default, any Universal Binary version of Python will insist on creating fat binaries even if we want to create modules for only the current platform. The build process will fail by lack of dependencies for the other platform. To fix this, set MACOSX_DEVELOPMENT_TARGET=10.3 to force compiling for OS/X 10.3. However, some Python versions are bugged in the handling of this variable (in distutils/util.py around line 110). Since this keeps on changing between versions, I often use Python 2.3 (non-fat, shipped with Mac OS/X) to build the binaries. The only downside is a warning when loading the libraries with Python 2.4/2.5.

Dependencies

  1. Be careful not to ship one of the following prefixes for your dependencies, or any other custom installed library that the end user does not have:
    1. /usr/local/, containing manually installed applications
    2. /opt/local/, containing applications installed through MacPorts
    3. /sw/, containing applications installed through Fink
    4. /Users/, containing applications installed with some ./configure --prefix=/Users/ script.
  2. Dependencies can have the form of @executable_path/...., which only makes sense inside an application bundle. @executable_path points to APPNAME.app/Contents/MacOS. Note that libraries are often installed in APPNAME.app/Contents/Frameworks, making the prefix @executable_path/../Frameworks/ common.
  3. Use the DYLD_LIBRARY_PATH environment variable for a colon-seperated list of directories to look for libraries. Ugly when shipping binaries, but handy when debugging or running a checked-out source from the command line. "man dyld" for more information about.
  4. To support older versions of OS/X, set the MACOSX_DEPLOYMENT_TARGET environment variable to, for instance, 10.3 when building with gcc or Python.
  5. Use the lipo tool with grep to see what files are open by the running applications. Useful for checking whether an application really doesn't use the wrong binaries.

Library analysis tools

Dynamic libraries come with the .dylib extension, or with an .so extension if they were badly ported from Linux. Most of the following tools work on executables as well.

  1. otool -L libname.dylib to view which libraries are required for libname to load. Note that Mac OS/X, unlike Linux, often uses absolute paths. See the dependencies section for more information.
  2. install_name_tool -change old new libname.dylib changes a dependency from "old" to "new" in libname.dylib. Succeeds whether the dependency is found or not.
  3. strip -x libname.dylib removes debugging symbols and the like, to save space without changing the functionality. Can save quite a lot.

Furthermore, there are tools to work with fat binaries (Universal Binaries). These binaries contain object code for both PPC and i386.

  1. lipo -info libname.dylib prints which platforms are represented in the binary. Note that some compilation processes insert stubs for one of the platforms. Use -detailed_info with lipo to see the respective sizes for ppc and i386.
  2. lipo -thin i386 libname.dylib -output libname-i386.dylib to extract the i386 object code out of a fat library. Use a similar syntax for ppc. Fails on a non-fat library. Use the [source:abc/branches/mainbranch/Tribler/Player/Build/Mac/smart_lipo_thin smart_lipo_thin] script to be able to thin a file, keeping the same name, and can be run on anything.
  3. lipo -create libname-i386.dylib libname-ppc.dylib -output libname.dylib to merge two single-platform libraries into an universal library. Use the [source:abc/branches/mainbranch/Tribler/Player/Build/Mac/smart_lipo_thin smart_lipo_merge] script to be able to merge two libraries regardless of whether they happen to have stubs for another architecture.

Disk image tools

Disk images (.dmg files) are similar to .iso files in that they contain a mountable directory structure within a single container. First, some handy tools:

  1. hdiutil create -srcfolder /dir/containing/contents -format UDRW -scrub -volname "Volume Name" diskimage.dmg to create a r/w disk image with a certain volume name and contents. UDRW=r/w, UDZO=r/o, UDCO=r/o compressed.
  2. hdiutil attach -readwrite -noverify -noautoopen diskimage.dmg /my/mountpoint to mount a disk image.
  3. hdiutil detach /my/mountpoint to unmount a disk image. Can fail if the image is busy.
  4. hdiutil convert diskimage.dmg -format UDZO -imagekey zlib-level=9 -o diskimage-readonly.dmg to convert an unmounted disk image to a proper read-only one, and to compress it.
  5. To add an EULA, see the EULA section.

Note that you might get the error:


hdiutil: create failed - Device not configured



According to man hdiutil, this can be caused by running inside a reattached screen session and other detached shells.

On a mounted image, you can do the following:

  1. bless --folder /my/mountpoint --openfolder /my/mountpoint to let the root folder open when the disk image is opened. bless controls what happens when the disk image is opened. Some source recommended a sleep 1 command after bless to make sure it completed.
  2. /Developer/Tools/SetFile -a C /my/mountpoint to enable the use of the volume icon (the icon of the disk image itself). Call the icon .VolumeIcon.icns and put it in the root directory. You can edit the icon file with /Developer/Applications/Utilities/Icon Composer.app. Make sure to include at least the highest resolution icon since it's really used.

EULA

Mac OS/X can present the user with an EULA when the disk image is opened. This EULA is stored in the resource fork of the disk image. Special care is needed, as the EULA has special file format and subversion cannot be used to store resource forks.

An example EULA file can be found in the SLA SDK of Apple, and is called SLAResources. We'll first extract the EULA from the example file's resource fork:

cp SLAResources/rsrc SLAResources.rsrc

The file SLAResources.rsrc can be stored in Subversion. To edit it, use for example RezKnife.

Finally, to attach the EULA to a disk image, the image needs to be unflattened (i.e. data/resource forks restored), but it needs to be flattened again in the end:

hdiutil unflatten Tribler.dmg

/Developer/Tools/DeRez -useDF SLAResources.rsrc > sla.r

/Developer/Tools/Rez -a sla.r -o Tribler.dmg

hdiutil flatten Tribler.dmg

I used this on read-only disk image.