How to make standalone OS X application bundles from PyQt apps using py2app

Py2app Pyqt

You can build cross-platform GUI applications in Python using a library called PyQt, a set of Python bindings for Nokia's Qt application framework. Using PyQt, you can create Python applications that look and feel like native applications regardless of whether they happen to be running on Linux, OS X, or Windows.

The application files themselves, however, do not look like native executables; they are simply Python files and require Python, PyQt, and other dependencies to be installed to run. Needless to say, you cannot distribute your applications in this manner unless your target audience is entirely comprised of alpha geeks. To provide a usable user experience that is consistent with native applications, you can bundle PyQt applications into native executables for each of the platforms you're supporting. When deploying on OS X, for example, you can bundle your PyQt application into a standalone OS X application bundle. This process, which involves the use of the excellent py2app application, comes with several gotchas that I hadn't bargained on.

Specifically, you cannot build standalone application bundles with the system python in OS X using py2app. If you want to build standalone PyQt apps, I recommend setting up Python, PyQt, etc., under MacPorts.

If you're comfortable with Terminal and don't need to be spoon fed, here's a quick overview of how to get your system in shape to compile standalone OS X application bundles from PyQt apps using py2app on Leopard 10.5.5 (which happens to be the only operating system version I've tested this on; this may work on other versions of OS X but YMMV).

(If you need step-by-step instructions, see the Macports installation details section, below).

Setting up MacPorts

  1. Install MacPorts. You will be installing Python, PyQt, py2app and everything else you need under MacPorts and using that environment for everything.
  2. Install the following portfiles: python25, py25-macholib-devel, py25-sip, py25-pyqt4, py25-py2app-devel, py25-pyqt, python_select.

Note that I am installing the -devel version of py25-py2app, as well as py25-macholib-devel on purpose. I couldn't get it to work with the stable versions. You may have to deactivate py25-macholib if it gets installed automatically as a dependency for py25-py2app-devel (this is what I did, but I'm assuming that installing py25-macholib-devel before py25-py2app-devel should mean that you won't have to).

Deploying a PyQt application as a Mac OS X application bundle

Once you've successfully installed everything under MacPorts, change your environment to use the version of Python in Macports via python_select:

sudo python_select python25

Once you're done, create a setup.py file for py2app. Something along the lines of the following example:

from setuptools import setup

APP = ['myapp.pyw']
OPTIONS = {'argv_emulation': True, 'includes': ['sip', 'PyQt4._qt']}

setup(
    app=APP,
    options={'py2app': OPTIONS},
    setup_requires=['py2app'],
)

Note that the includes array contains sip and PyQt4._qt. Those two entries are essential if you're bundling a PyQt app.

Finally, issue the following command to create a standalone OS X application bundle from your application:

python setup.py py2app

That's it. Double click on your app, which you can find in the dist folder and it should run.

If you run into any problems, see the My Macports setup section, below, for an exact dump of my Macports portfiles that you can use to compare (obtained using port installed).

Read on if you want to the full story and details of how I got my system up and running with MacPorts to deploy OS X app bundles using PyQt and py2app. I'm including this with as much information (error messages, etc., as possible so that others may find this post if they're similarly stuck.) Or, if you're not interested, you can jump to the Reference section.

The gory details

My initial attempt at creating a standalone PyQt app with py2app resulted in the app crashing on launch.

The error message:

Fatal Python error: Interpreter not initialized (version mismatch?)

Judging by the error message, and emboldened by a forum exchange between Derrick Hendricks and Christopher Baker I guessed that the conflict was due to my having two versions of Python installed (the System python and a 2.5 through MacPorts). So I removed MacPorts completely, ran py2app again and the app launched but with a different error:

ImportError: '/System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/lib-dynload/PyQt4/QtCore.so' not found

Searching for a solution, I stumbled upon a thread where someone who ran into the same issue suggested adding the following line to __boot__.py in the Contents/Resources folder of the application bundle:

sys.path = [os.path.join(os.environ['RESOURCEPATH'], 'lib', 'python2.5', 'lib-dynload')] + sys.path

Unfortunately, doing that caused the app to crash at launch.

It was only when I actually read the output from py2app that I noticed that it appeared to be creating a semi-standalone app instead of the standalone app that I asked it to create. In retrospect, I should have realized this from the very first error since I should not have gotten a version mismatch error on the Python interpreter if it had been correctly embedded in the app bundle.

The message in the py2app log read:

creating . . ./bdist.macosx-10.5-i386/python2.5-semi_standalone/app/collect/PyQt4

A Google search later, I had a thread where someone else was having the same problem and I found a definitive answer from Bob Ippolito, one of my Python heroes and the author of py2app, stating the following:

[py2app] refuses to build a standalone bundle out of the system framework.

OK, that's something that should be in the docs page on H1! (I couldn't find any mention of it in the docs at all.)

This makes sense, as David Warde-Farley states in a forum posting:

First of all, if you want to build bundle .app's that are redistributable, it's a good idea not to use the system Python, especially if you want to support Tiger (10.4). There are things in Apple's Python (i.e. DTrace support) that I imagine won't play nicely on an older OS.

At this point, I knew that I had to install a separate Python and decided to use MacPorts. It did take a while to install everything but that approach paid off in the end.

Reference

My Macports setup

Here's a full list of my MacPorts setup (which I set up specifically to compile PyQt apps with py2app, so there aren't any superfluous packages in there) which you can compare to yours if something isn't working:

Macports installation details

Here are step-by-step instructions on how I got things working:

  1. Install MacPorts
  2. sudo port install python25
  3. sudo port install py25-macholib-devel
  4. sudo port install py25-sip
  5. sudo port install py25-pyqt4
  6. sudo port install py25-py2app-devel
  7. sudo port -v install py25-pyqt
  8. sudo port install python_select

Note that at one point during the PyQt installation, MacPorts aborted with:

Target org.macports.activate returned: Image error: /opt/local/bin/a2p is being used by the active perl5.8 port. Please deactivate this port first, or use the -f flag to force the activation.

It appears that there were two versions of PERL installed. I simply restarted the installation (sudo port install py25-pyqt) and, after waiting a long while, and having to restart it once more, it completed successfully in installing Qt and PyQt.

(Note that the PyQt installation will take seemingly forever so it might be an idea to (a) start it in verbose mode, as shown above, so you know it's still doing something and hasn't hung, and (b) set up a growlnotify message to let you know when the process is complete.)

Comments