Using Py2App with GTK

In order to present D-RATS as a nice and neat App package on MacOS X, I have been using Py2App.  When it works, this utility generates a App package that contains everything D-RATS needs aside from the base operating system to run.  To do this, I install MacPorts and then build a full Python 2.5 stack with things like pygtk, libxml2, libxslt, etc.  When I run Py2App, it captures all of the necessary libraries and support files from the MacPorts installation and relocates them into the App package to form a nice, neat, and easy-to-run package for Mac users.

Each time I set out to create this environment, I was presented with the same problem.  Since it took me several attempts spanning many months to figure out the proper way to solve it, I decided to document it here so that there would be a single place that explained how to get out of this particular jam.

The problem was that when running Py2App, I would inevitably get the following error message:

ValueError: New Mach-O header is too large to relocate

In order to figure out what it was complaining about, I would type print self in the debugger, which would show me the path of the library it was trying to relocate.  I think that the first one I would aways hit with this problem was  libpangoxft-1.0.0.dylib.  After hacking up a solution, I believe there were a couple more in the stack that I needed that would present the same problem.

It turns out that the problem is one of space.  When the dynamic libraries for MacPorts are created, they encode the full path of the library into the header, which is something like /opt/local/lib/libpangoxft-1.0.0.2203.1.dylib.  When Py2App goes to relocate the library into the package, it needs to change this to a relative path which points to its location in the package relative to some other object.   It would appear that if the length of the original string is shorter than the one it needs to become in the package, the relocation logic used by Py2App is stuck and thus dies with the above error message.

The Py2App author indicates that the only solution to the problem is to re-link the library with an option that will instruct the linker to create the maximum amount of space for the library name in the header, despite what is requires at link time.  This ensures that the relocated library path name will fit in the space allocated.  It turns out that in addition to the prescribed option, the -Xlinker option is also required to make some component in the chain obey the request to pad the header.  This bit took me quite a while to figure out.

I figured that the safest (and easiest to reproduce) option would be to convince MacPorts to build everything with this option instead of trying to insert it into the build environment of only the affected libraries.  To do this, I modified the port command’s library file where the default configure options are held.  For me, this was /opt/local/share/macports/Tcl/port1.0/portconfigure.tcl.  I edited the line starting with default configure.ldflags to look like this:

default configure.ldflags   {"-L${prefix}/lib -Xlinker -headerpad_max_install_names"}

After doing that on a fresh install of MacPorts, I proceeded to build the required stack.  In the end, Py2App agreed to relocate everything into the app package and it worked well.

Since I’ve got this going, I will point out an additional issue that I ran into in the whole process, which took some actual work to figure out.

The problem was with the pango configuration files.  These normally live in /etc/pango, or /opt/local/etc/pango in the MacPorts installation.  These must be in place in the App package or the pango library will not properly render fonts.  Moreover, the configuration files must properly point to the relocated objects within the package.

In order to capture the act of copying and modifying the pango configuration scripts, I wrote the following script which can be run after the App build is complete:

#!/bin/bash

make_pango_modules() {
        local src=$1
        local dst=$2
        local sf=${src}/etc/pango/pango.modules
        local df=${dst}/etc/pango/pango.modules

        cat $sf | sed ‘s//opt/.*/lib/../Resources/’ > $df
}

make_pango_rc() {
        local src=$1
        local dst=$2
        local sf=${src}/etc/pango/pangorc
        local df=${dst}/etc/pango/pangorc

        cat $sf | sed ‘s//opt/.*/etc/./etc/’ > $df
}

make_pangox_aliases() {
        local src=$1
        local dst=$2

        cp ${src}/etc/pango/pangox.aliases ${dst}/etc/pango
}

usage() {
        echo ‘Usage: make_pango.sh [PATH_TO_MACPORTS] [PATH_TO_APP]’
        echo ‘Example:’
        echo ‘  make_pango.sh /opt/local dist/d-rats.app’
}

if [ -z “$1” ]; then
        usage
        exit 1
fi

if [ -z “$2” ]; then
        usage
        exit 1
fi

base=$1
app=”$2/Contents/Resources”

mkdir -p ${app}/etc/pango

make_pango_modules $base $app
make_pango_rc $base $app
make_pangox_aliases $base $app

By running this script with something like:

./make_pango.sh /opt/local dist/myprogram.app

You’ll get a properly configured pango environment.

Category(s): Codemonkeying
Tags:

One Response to Using Py2App with GTK