Richard Jones' Log: Sane Python application packaging: conclusion (for now?)

Thu, 15 Jan 2009

This is part four of my continuing adventure in packaging my Python application.

In part three I was close to a solution. In the end I have decided to:

  1. Use the "build_apps" distutils command I wrote to generate zip application bundles for the three target platforms. The zip file contains all libraries (pure python, thankfully) and a launcher script to run the application.
  2. Split out the examples from the rest so there's just one examples zip file and the other zip files are about half as heavy.
  3. Generate a standard distutils source distribution which is intended for Linux system package maintainers.
  4. Upload all those files to Google Code using a modified script.
  5. Still register with PyPI but set the download_url to point to Google Code.

The upload script was modified in two ways:

  1. First, it's broken with the svn module available under Ubuntu (and others). I modified the code to directly parse the svn passwords. It's a gross hack and probably not widely useful so I won't submit a patch. For the curious the code is below.
  2. Secondly I hard-coded it to upload the five files I want to send, just to make my life easier.

I'll await feedback from actual users but I believe that this solution will be good enough.

By the way, I just released a new version of the application in question. It's Bruce, the Presentation Tool (who puts reStructuredText in your projector). The 3.2 release is pretty nice with support for external styles, recording, automated playback of various types, gradual exposure of lists and some other stuff. It's pretty cool.

On to the change to, which replaces get_svn_auth() with:

def get_svn_auth(project_name, config_dir):
  """Return (username, password) for project_name in config_dir."""
  realm = (' Google Code Subversion Repository'
           % project_name)
  authdir = os.path.join(config_dir, 'auth', 'svn.simple')
  for fname in os.listdir(authdir):
      info = {}
      key = None
      for line in open(os.path.join(authdir, fname)):
          line = line.strip()
          if line[0] in 'KV':
          if line == 'END':
          if line in 'password username svn:realmstring'.split():
              key = line
              info[key] = line
      if info['svn:realmstring'] == realm:
          return (info['username'], info['password'])
  return (None, None)
Comment by Rene Dudfield on Fri, 16 Jan 2009


I'd be interested to know why you decided not to make platform specific installers?

I very much like a zip file as an option, but mostly use platform installers if available.

However, maybe I would prefer zips for python applications that I'd like to hack on :)


Comment by Richard Jones on Sat, 17 Jan 2009

There's really only one OS I would build an installer for, Windows. Not having easy access to a Windows test system I'm loathe to release an installer if I can't test it thoroughly (and I most likely need a Windows system to create the installer anyway.)

As discussed, OS X doesn't need an installer and system packagers can handle Linux installation for me.

Comment by Tim Ferguson on Wed, 25 Feb 2009

G'day Richard,

I have just recently started playing with Python with the aim of implement system level testing of a cross-platform product. I was looking at your packaging of Bruce for ideas on cross-platform packaging of combined custom and third-party Python modules. I noticed for Linux that your entry point is "" which is actually a Python script. It suffers the problem that your current working directory must be the same path as for the sys.path.insert() to work. Is there any major advantage to having this entry point a Python script? What I was considering is a shell script along the lines of the following:


BASE=`dirname $0`
python $BASE/

I am sure you could probably achieve the equivalent pathing in Python using sys.argv and it may reduce the number of files?

Hope all is well in your life.


Comment by Richard Jones on Wed, 25 Feb 2009

Good point, thanks. I'll include the following in the next release:

#! /usr/bin/env python

# force module loading from my zip file first
import sys, os
zip_path = os.path.join(os.path.dirname(sys.argv[0]), '')
sys.path.insert(0, zip_path

from bruce import run