Bundling

Once you have your application running, you'll want to create an application bundle that you (or your users) can drag to a convenient place and launch by double-clicking on it in Finder. If you can design the application so that it doesn't need files outside of the bundle, users will be able to install it by simply dragging it from a disk image that they download to whatever folder they like.

Almost wherever they like: Gtk+ applications typically depend heavily on environment variables, so the app bundle will have a shell script to set everything up (called a "launcher script"). The catch is that shell scripts tend to view spaces as delimiters between arguments, so paths with spaces cause trouble unless particular care is taken in writing the launcher script.

Gtk-OSX provides a tool, gtk-mac-bundler, to build the application bundle for you. You can download it as a tarball or clone the latest code with git from git://git.gnome.org/gtk-mac-bundler.

What follows is a basic procedure for getting and using gtk-mac-bundler. More details may be found in the README provided with the source and in the comments in the example files (found in the examples folder).

How to use gtk-mac-bundler

Download the source code, unpack, then install it:

$ cd ~
$ wget http://ftp.gnome.org/pub/gnome/sources/gtk-mac-bundler/0.7/gtk-mac-bundler-0.7.3.tar.xz
$ tar xf gtk-mac-bundler-0.7.3.tar.xz
$ cd gtk-mac-bundler-0.7.3
$ make install

or, to use the most recent code:

$ git clone git://git.gnome.org/gtk-mac-bundler
$ cd gtk-mac-bundler
$ make install

This will install gtk-mac-bundler in ~/.local/bin (the same place as the gtk-osx install script puts jhbuild).

Next, make a directory to hold your bundle configuration files (we'll call this the "bundle directory") and copy the contents of ~/gtk-mac-bundler-0.6.1/examples to it.

Bundle File

Copy or rename gtk-demo.bundle to something more like yourappname.bundle. We'll call this file the "bundle file".

Now, edit the bundle file. This file tells gtk-mac-bundler what files are required to run your application, and enables it to find all the dependencies which need to be included in your application bundle. There are a lot of comments in the file itself, and more explanation is available in ~/gtk-mac-bundler-0.6.1/README.

Gtk-mac-bundler can find dependencies which are linked at build-time, using the shell command "otool -L". It cannot find dependencies which are loaded with dlopen (a.k.a g_module_load), so you must include those explicitly in the bundle file with a <binary> tag. (Don't use a <data> tag or gtk-mac-bundler won't be able to find the shared libraries that the module depends on.)

Icon file

You will need to create an icon for your application. OSX doesn't use regular image files for icons. Instead, the .icns file format is used, which contains icons of up to four (five in Snow Leopard) standard sizes. You'll want to build one of these .icns files for your app and point to it with both Info.plist and a <data> entry in your bundle file. Icon Composer (/Developer/Applications/Utilities/Icon Composer.app) is the program to use for this for XCode before 4.4; see High Resolution Guidelines for details about how to create a .icns file with XCode 4.4 or later. If your application already has graphics files in the appropriate resolutions you can just drag them into the appropriate boxes in Icon Composer and save. You don't need to have all of the sizes filled for the icons to work (this is particularly true of the 512x512 icon size first provided in Snow Leopard). Note, however, that icons designed for Gnome are generally much lower resolution and flatter than is typical of Mac applications, particularly those for the new "retina" displays. You may want to create new more detailed application and document icons for your Mac port.

Info.plist

Next, edit Info.plist to refer correctly to your application and the application icon you created. This file is used by Mac OS X when actually launching your application. The basic Info.plist entries are pretty obvious, full documentation is at Apple Documentation).

Recent experimentation has revealed that unless it is an executable binary, the file that CFBundleExecutable names is ignored and launchd will attempt to open a file having the same name as the bundle: If the bundle is Foo.app, the launcher script must be Foo or foo.

Gtk-mac-bundler uses the CFBundleExecutable key to name both the bundle and the launcher script, though a recent change (not yet released, but in git) will use CFBundleName to name the bundle if it's present; this allows CFBundleExecutable to be lowercase and CFBundleName to be capitalized if your program has built-in scripts or references which need to call the launcher script and need it to be lower-cased as is normal for Unix commands.

launcher.sh

A template launcher.sh is included in the example. Either add to this any other shell commands you need to get your application ready to run, or call other shell scripts from it. If you do use other shell scripts, be sure include them in the bundle file.

Run the bundler

Assuming that your bundle description file is called yourappname.bundle, you should now be ready to run gtk-mac-bundler using the the following steps:

$ jhbuild shell
$ export PATH=$PREFIX/bin:~/.local/bin:$PATH
$ gtk-mac-bundler yourappname.bundle

Gtk-mac-bundler copies all of the files you indicated in your bundle file, and also pulls in the dependencies it can find, and it adjusts the install paths to reflect the new locations. Note that some libraries and applications (dbus and Gconf are particularly common) hard-code paths during build and you may find that you need to keep at least some of your installation directory in place. You can finesse this problem by installing the required files (often they're text configuration files) into the bundle and having the launcher script check for and if not present or correct create a symlink from the bundle Resources directory (e.g., Yourappname.app/Contents/Resources) to the installation directory. If you do this, you probably want to set up your prefix when you build to somewhere outside of your home directory. Apple recommends /Library for application data and both /opt and /usr/local are traditional in Unix and Linux. Do be careful with both of these, as it's common for autotools-based programs to build in /usr/local by default, and MacPorts uses /opt/local for its own purposes.

Gtk-mac-bundler reads the Info.plist to determine the app bundle's name, and puts it in the folder indicated by the <destination> tag; in the examples, this is ~/Desktop.

For more details about what gtk-mac-bundler is actually doing here, see the ASCEND wiki.

Bundling PyGTK apps

PyGTK programs are a bit trickier to bundle, because a python program isn't a mach-o binary, so it can't go in the <main-binary> element. At the moment you can make libpygtk.dylib the main-binary. Add your python modules to your bundle with <data> tags and add their location to $PYTHONPATH in your launcher script.

Gtk-mac-bundler isn't aware of Python module dependencies (as opposed to, for example, modulefinder), so any modules which aren't installed somewhere in $PREFIX/lib/python2.7/ must be manually added to your bundle file in <data> tags.

There is an example which bundles pygtk-demo. Look at examples/pygtk-demo.bundle, examples/Info-pygtk-demo.plist, examples/pygtk-demo.sh (the launcher script), and examples/pygtk-demo (a wrapper python program which does the python part of setting up and calling pygtk-demo.py). Yes, the last one could be better named.

Code Signing

Since MacOSX 10.8 "Mountain Lion" Macs have used Gatekeeper to control what sorts of applications can be installed. The default behavior is that on the first run of a downloaded application it will refuse to open with a double-click in Finder unless the app has been signed by a trusted developer. Again by default, "trusted" means registered with Apple and using Apple-issued certificates to do the signing. All of this can be overridden by knowledgeable users, but bundlers may wish to avoid making their users apply those overrides. Read the Code Signing Guide for details on the process and how to obtain certificates. One note: Unless you enjoy authenticating, install your certificates in the Login keychain rather than the System one that Apple recommends.

Once you've obtained your certificates and made your bundle, you can sign it with:

codesign -s "certificate name" /Path/to/Foo.app/Contents/MacOS/*

Codesign is available on all versions of MacOS from 10.5 "Leopard" on, but for a signed app to be accepted in newer versions of OS X (10.9.5 and later), you must run codesign on a mac running 10.9.5 or later. Also, you will need to sign all nested binaries. You can do that with the --deep argument to codesign, but Apple recommends signing each individually. gtk-mac-bundler will do that for you if you set $APPLICATION_CERT=<certificate name> in your environment. Since gtk-mac-bundler needs to be run in a jhbuild shell, you can easily do this from .jhbuildrc-custom.

As originally designed gtk-mac-bundler uses a shell script launcher to set up the environment before launching the executable. This causes trouble for code-signing on Mac OS X 10.11.3 and later; on previous versions it does work even though Apple has long recommended not having script files in Contents/MacOS and not signing script files. As of August 2016 gtk-mac-bundler includes an example C program, examples/python-launcher.c, which initializes a bundled python library and passes control to a python script indicated in the bundle file. The python script can set almost all of the necessary environment variables; an example is provided as examples/gtk_launcher.py.

"Almost all" because it does no good to set DYLD_LIBRARY_PATH, DYLD_FALLBACK_PATH, or LD_LIBRARY_PATH if your program relies on dlopen() path resolution. dlopen() is a call into the dynamic loader and that was initialized including reading the environment, at program launch. Consequently all dlopen() calls need to use rpaths beginning with @executable_path/../Resources; the link editor created those rpaths with $PREFIX at build time so they need to be converted. gtk-mac-bundler has always transformed all files indicated with <binary> elements and found by running otool -L on those files. Since September 2016 it also transforms the paths in Gir and Typelib files indicated in a <gir> tag so that bound interpreted languages like Python will find libraries linked via gobject-introspection. Any other binding mechanism will require modification outside of gtk-mac-bundler to ensure that its paths can be found without setting DYLD_LIBRARY_PATH.

Projects/GTK+/OSX/Bundling (last edited 2016-09-12 23:03:56 by jralls)