Easy GTK binary compatibility
If you compile ROX-Filer on a system with GTK 2.4, it will still work on a system with GTK 2.8. Compile it on a 2.8 system, and it will fail to run with 2.4. This problem isn't specific to ROX-Filer (or to GTK); recent versions of ROX-CLib and programs using it stopped working because they gained a phantom dependency on libglitz. Here's how to avoid these problems...
Introduction
One solution is to keep a complete Linux system around with the old version of GTK installed (either on a separate machine or in a chooted jail) and compile everything on that. This is how the ROX-Filer binary releases are normally built. There are some problems with this approach:
- A complete Linux distribution with GTK takes up a lot of disk space and takes a while to download.
- Not all programs need GTK 2.4. If you want to compile something for 2.2, you need yet another Linux installation.
- It's more hassle to build this way.
- People producing binaries for other platforms (PPC, x86_64, etc) need to set up a similar system.
- If you use a function from a newer GTK, you don't find out about the problem until release time.
Another approach, favoured by autopackage, is to use header versioning. Here, you add code to the GTK header files so that they can emulate older versions. The program sets some pre-processor macros to indicate the version it wants, and the headers emulate that version. This also has some problems:
- Annotating all the header files with this emulation code is hard work, and you have to keep doing it for every new version.
- The headers become more and more cluttered over time as more versions must be supported.
- If the library author makes a mistake, they probably won't notice. The developer of a program using the library won't notice. The packager creating a binary release won't notice. Only the end-user user notices, when the binary they download doesn't run. The fix requires going all the way back to the library author.
So, this method is a lot of work, is easy to get wrong, and makes the code more complicated. Not ideal.
A third solution is to install various different versions of the headers out-of-prefix (e.g., under /opt/gtk+-2.x) and set environment variables to make the compiler use one of them rather than the system default version. Luckily, downloading and installing out-of-prefix and setting environment variables are the Zero Install injector's speciality!
Using the injector to compile your program
To test this approach, I made a set of GTK, GLib and Pango headers available through Zero Install. I created a source interface file for ROX-Filer, called ROX-Filer/ROX-Filer-src.xml, which uses them:
<interface xmlns="http://zero-install.sourceforge.net/2004/injector/interface"> <name>ROX-Filer-src</name> <summary>source code for ROX-Filer</summary> <description> Executing this program will compile a ROX-Filer binary. </description> <group> <requires interface="http://0install.net/2006/interfaces/GTK-dev"> <version not-before='2.4' before='2.6'/> <environment insert="lib/pkgconfig" name="PKG_CONFIG_PATH"/> <environment insert="" name="GTKHEADERS"/> </requires> <requires interface="http://0install.net/2006/interfaces/GLib-dev"> <version not-before='2.4' before='2.6'/> <environment insert="lib/pkgconfig" name="PKG_CONFIG_PATH"/> <environment insert="" name="GLIBHEADERS"/> </requires> <requires interface="http://0install.net/2006/interfaces/Pango-dev"> <version before='1.5'/> <environment insert="lib/pkgconfig" name="PKG_CONFIG_PATH"/> <environment insert="" name="PANGOHEADERS"/> </requires> <implementation main='src/build' id="." version="2.4.1.0" released='Snapshot'/> </group> </interface>
When executed, this runs ROX-Filer/src/build with PKG_CONFIG_PATH pointing to the .pc (pkgconfig) files for GTK 2.4.14, GLib 2.4.8 and Pango 1.4.1. The build script just runs make:
#!/bin/sh cd `dirname "$0"` make "$@"
I modified ROX-Filer's Makefile.in to set the gtkprefix and glibprefix pkgconfig variables to whereever Zero Install has placed the header files (normally the variable is simply called prefix, but we want to avoid affecting anything else here so I renamed them in the .pc files). The new Makefile has these lines:
PKG_CONFIG_FLAGS=--define-variable=gtkprefix="${GTKHEADERS}" \ --define-variable=glibprefix="${GLIBHEADERS}" \ --define-variable=pangoprefix="${PANGOHEADERS}" CFLAGS = [...] `${PKG_CONFIG} ${PKG_CONFIG_FLAGS} --cflags gtk+-2.0 libxml-2.0` LDFLAGS = [...] `${PKG_CONFIG} ${PKG_CONFIG_FLAGS} --libs gtk+-2.0 libxml-2.0`
Now, when I build ROX-Filer (by executing 0launch ROX-Filer-src.xml) it opens a dialog box prompting me to choose a version of the GTK and GLib headers:
When I select GTK 2.4.14, GLib 2.4.8 and Pango 1.4.1 (currently the only versions I've made available) it compiles a binary against those headers. This binary works with both my current GTK (2.8.16), and on my real (chrooted) GTK 2.4.14 installation.
Of course, an extra benefit here is that users who want to compile from source don't need to have the GTK headers already installed; Zero Install will get them automatically. Also, because compiling this way all the time is easy, I should notice immediately if I use a function that isn't in GTK 2.4.
Dependencies
For comparison, here are the dependencies listed in the resulting object files (objdump -p ROX-Filer|grep NEEDED):
Compiled with 0launch Compiled normally libgtk-x11-2.0.so.0 libgtk-x11-2.0.so.0 libgdk-x11-2.0.so.0 libgdk-x11-2.0.so.0 libatk-1.0.so.0 libXrandr.so.2 libgdk_pixbuf-2.0.so.0 libXi.so.6 libpangox-1.0.so.0 libXinerama.so.1 libpango-1.0.so.0 libXext.so.6 libgobject-2.0.so.0 libatk-1.0.so.0 libgmodule-2.0.so.0 libgdk_pixbuf-2.0.so.0 libdl.so.2 libpangocairo-1.0.so.0 libglib-2.0.so.0 libfontconfig.so.1 libxml2.so.2 libXcursor.so.1 libz.so.1 libpango-1.0.so.0 libm.so.6 libcairo.so.2 libX11.so.6 libXrender.so.1 libICE.so.6 libX11.so.6 libSM.so.6 libgobject-2.0.so.0 libc.so.6 libgmodule-2.0.so.0 libdl.so.2 libglib-2.0.so.0 libxml2.so.2 libz.so.1 libm.so.6 libICE.so.6 libSM.so.6 libc.so.6
As you can see, the older version has considerably fewer requirements, making it much easier for people to install.
Future work
I've tested this with ROX-Filer, but not with ROX-CLib's libglitz problem (because my version of GTK doesn't insert a dependency on libglitz). Hopefully it will work there too.
GTK 2.4 is the earliest version of GTK that ROX supports. However, other projects will want to compile against different versions (2.0, 2.2, 2.6, etc). Zero Install's requires element needs a way to set the default version (otherwise you have to select different versions depending on which program you are compiling).
Only GTK, GLib and Pango have been done. This approach could also be used for ATK, etc.
See also: Discussion on rox-devel
- Thomas Leonard's blog
- Login to post comments
GLib headers
Update: I've now made GLib 2.4.8 headers available in the same way and updated the post to include them.
Changes in GLib 2.6 meant that when an assertion failed (e.g., g_return_if_fail) it would try to call a non-existant function and crash if it was running against an earlier copy of GLib. Using Zero Install to compile against the old headers fixes this problem.
GTK headers
The changes have now been committed to SVN.
Also, the interfaces have now moved from testing to here:
http://0install.net/2006/interfaces/GTK-dev
http://0install.net/2006/interfaces/GLib-dev
Pango headers
Pango headers are now also available:
http://0install.net/2006/interfaces/Pango-dev
ATK headers
And, to complete the set, here are the ATK headers:
http://0install.net/2006/interfaces/ATK-dev
Note that building in the way shown above doesn't quite work if you don't already have the -dev packages installed, because ld needs a symlink at link time. See Compiling with SCons and GTK for a solution.
gtk-2.4 script
I've now put up a simple script to compile 'legacy' programs against the GTK 2.4 headers. Get it like this:
Typical usage:
Note that if your program is built using Zero Install to fetch the build dependencies then it's easier to add GTK as described above in the main post.
What about --as-needed?
Did you try the --as-needed flag to ld? I checked here and adding it dropped direct requirements for about 5-6 libraries. (using ldd -u)
I changed
LDFLAGS = ...
toLDFLAGS = -Xlinker --as-needed ...
Not sure how portable this is (BSD, Solaris, ?).
--as-needed
--as-needed is quite good (don't know why it isn't the default), but it doesn't solve all the problems. For example, you'll still get the crash mentioned above if you compile a program with GLib 2.6 and then run on 2.4 (when a warning should be printed).
Also, there's still the problem that you might accidentally use a newer function without realising.
--as-needed
The --as-needed flag can generate broken binaries when compiling shared libs that are meant to be plugins loaded using dlopen() by an application. That's probably why it's not the default, though there's probably a way around this problem.
I found this out the hard way when I tried to set it in my global LDFLAGS on my Gentoo system. I recall xine-lib breaking (it's a bit disconcerting for the app to run, but to be unable to play any video or audio formats), but I don't remember what else.
--as-needed on Solaris
Solaris ld doesn't have --as-needed, not up to Solaris 9 anyway.
As a general rule of thumb, long options (--wibble) are less portable than short options. I was going to say they weren't portable at all, but I'm not sure although I can't actually think of any in Solaris 9.