Skeleton Files
27th May 2003
Skeleton files are a key part of the approach autopackage takes to managing dependancies. They are files which contain metadata about a dependancy. In autopackage, dependancies are heavyweight, that is, they are not simply links in a database. Although using them is normally just a case of saying:
require @foo.org/bar
they are backed by these skeleton files. Essentially, a skeleton file consists of some trivial metadata (name, summary) and a couple of scripts. One, the most important, tests the system for the presence of that dependancy. The other is responsible for getting hold of a package which contains the needed sofware. Let's take a look at a simple skeleton file.
[Meta]
RootName: @xiph.org/libogg
DisplayName: Ogg bitstream library
ShortName: libogg
Packager: Hongli Lai <h.lai@chello.nl>
Summary: A library for manipulating ogg bitstreams.
Skeleton-Version: 1
[Description]
Libogg is a library for manipulating ogg bitstreams.
It handles both making ogg bitstreams and getting packets from ogg bitstreams.
[Retrieval]
retrieve "$1"
[Notes]
This skeleton does not set SOFTWARE_VERSIONS
Interface versions start at 0.
[Test]
INTERFACE_VERSIONS=`testForLib -v libogg.so.0`
The first section is pretty self explanatory. The RootName field must be a version independant root name of the software (or more precisely, interface). Most of the time, software maintainers should provide skeleton files themselves, but if they *gasp* aren't interested in autopackage yet, just ask them if you can use a root name you just invented assuming they don't already have one. The trick is to ensure you don't end up with multiple root names for the same thing. That's not fatal, but it does lead to wasted effort.
The DisplayName should be something along the lines of what is recommended in the Gnome HIG, "Product Name Description" is good. Remember, clueless users will see this string, so make it non-scary.
The rest of the metadata is covered elsewhere, except for Skeleton-Version. Because software changes, sometimes so does the logic for detecting that software. Because of that, skeletons are versioned. The scheme is simple, an ordered sequence of integers. You can't have a skeleton version like "2.1a", it has to go 1, 2, 3 etc.
The Retrieval section at the moment is normally run when a dependancy check fails (ie after the Test section). It's responsible for getting the package from somewhere. Normally you'd just want to call into the autopackage APIs here, though we allow you to script it for flexibility reasons.
Retrieval normally consists of a line like this:
retrieve "@foo.org/whatever" $INTERFACE_VERSION
The INTERFACE_VERSION variable will be set by require, which calls that script
in the event that the test script fails (ie the dependency isn't present). That will ensure
that the right package is installed.
The most important section is the Test section. This is a script that must set two
variables, INTERFACE_VERSIONS and SOFTWARE_VERSIONS. These variables
should be set to a space separated list of versions discovered on the system.
The concept of the interface version as separate from the software version is an important one. Packages don't actually depend on explicit software versions, they depend on the presence of some functionality conforming to an interface. For instance, if you compile your application against GTK2.2, a dependancy on GTK2.2 would be wrong, because when GTK2.4 comes out, it will be backwards compatable. So you need to be able to represent this, and in such a way that if the unexpected happens you can still cope with it.
Earlier versions of autopackage allowed you to specify ranges of software versions (for instance, a dependancy on version 1.2.5 of libpng), however this didn't work very well and didn't jive with the idea that autopackages probe the system directly. It's often more robust to detect an interface, rather than try and figure that out from the software version number.
Because of this, the concept of the interface version was introduced. Interface versions are strictly controlled, they follow the "MAJOR.MINOR" form exactly, where MAJOR and MINOR are integers. The rules for interface versions are simple, and resemble the ones used in libtool. When something is added to the softwares interface, and the previous one is preserved, increment minor. When something is changed or removed (ie the interface broke backwards compatability), increment major and set minor to zero.
The exact way this relates to the software versions should be described in the Notes section. It's a good idea to start from 0.0, but often software follows this scheme anyway. Examples would be many shared libraries such as GTK. In that case, you can normally calculate the interface version of the installed software by looking at the sonames.
Writing tests for ELF shared libraries
Let's have a look at that test script then. It's only one line, but it might not be obvious what it's doing.
Obviously, this script does not set SOFTWARE_VERSIONS. libogg doesn't seem to have any real concept of software version anyway, so this is no big loss. Like most pure shared libraries, its soname is the version number. Setting SOFTWARE_VERSIONS is useful, but not mandantory. If you don't do it, you should make a note of this in the Notes section.
It does however set INTERFACE_VERSIONS:
INTERFACE_VERSIONS=`testForLib -v libogg.so.0`
This script calls an autopackage provided function, testForLib
. This takes at least one parameter, the soname of the library to check for. The exit
code is zero if a version of the library was found, otherwise it is 1. The parameter we use
here, -v, makes it output the versions of the libs found, as a space separated list. You can try
it from the shell, go into the autopackage shared directory and run:
$ . apkg-funclib
$ testForLib -v libX11.so
6.2
$ testForLib -v libogg.so
0.4.0
$ testForLib -v libpng.so
0.1.2.2 2.1.0.13
Blarg! What happened there? Well, testForLib follows the symlinks through the system, so even though you might have been expecting to see "2 3" for libpng.so.2 and libpng.so.3, on my Red Hat 9 system, things are not so simple :( As you can see, libpng is a bit of a mess. It's the job of skeleton files to abstract the packager away from this kind of mess, and let them focus on describing the dependancies of their package easily.
Luckily libogg is sane. It follows libtool style versioning, so we can extrapolate which versions are supported from the soname of the installed libogg libraries (you can have multiple versions installed even of the same major version number remember).
If INTERFACE_VERSIONS is set to, for instance, "2.2 1.3" then autopackage will expand that out into "2.2 2.1 2.0 1.3 1.2 1.1 1.0" before searching for the required interface version. Minor numbers are ignored, which is why it's safe to put the output of testForLib directly into the INTERFACE_VERSIONS variable. You are allowed to have for instance, "2.3 2.2 1.1" in INTERFACE_VERSIONS, duplicate entries will be automatically removed from the list.
Some libraries, like libpng, don't always have library soname versions that map cleanly onto interface versions, in which case the test will be a lot more involved, and may require specific knowledge of various distributions. This is fine, it's what skeletons are there for.
Interface versions don't have to start at zero, nor do they have to be contigous. It's valid for instance to go straight from 2.0 to 2.2, and miss out 2.1 - the only requirement is that if you claim a package supports interface 2.2, that a program which depends on 2.0 must be able to be installed against it.
The notes section should be used to describe how you set INTERFACE_VERSIONS, SOFTWARE_VERSIONS and so on. Sometimes it'll be obvious, but you should make it clear nonetheless.
Finally, it's possible to pass arbitrary arguments to test scripts, these are available in the usual $1 $2 $3... variables and $@, if your skeleton can be "customised" in this way, you should also write about this in the Notes section.

