debian/rake: A Tale of Rubies

A while ago Rohan and I talked about the possibility of writing debian/rules in ruby rather than make. Even though this is not exactly policy compliant I was reasonably certain that this is possible, I just was not quite sure how much resistance the existing tools would give a rules file that is not written in make. So, I set out to hack up a debian/rules file using rake (a make-like build tool using ruby).

Before I dive into the actual hack, let me briefly outline how a Debian package build generally works, so we are all on the same page.

A Debian source package consists of a bunch of files in a debian/ subdirectory inside the actual upstream source. Most importantly and also at the very least it will contain:

  • changelog: the changelog
  • control: the metadata for the source package as well as the binary debs created (this for example includes the names of the packages as well as their dependencies and a description)
  • rules: the build script written in make (make is a somewhat powerful language that expresses how one derives the final result from a bunch of build steps called targets)

To build deb packages a tool called dpkg-buildpackage is used. It mostly just calls debian/rules where all the heavy lifting happens (actually buildpackage calls out to a bunch more dpkg-* scripts to get all sorts of stuff done, they are of no consequence to the subject at hand though).

Additionally most debian packages will use a suite of tools called debhelper to help with creating high quality packages even when the packaging rules themselves would be very complex. Debhelper helps with all sorts of tasks, ranging from making a bunch of files go into one deb and the rest of the files in another deb to slightly more magical things such as automatically gzipping manpages to save disk space. Debhelper also includes a really awesome goody called dh, it’s a command sequencer for debhelper which helps packagers run all debhelpers in the correct order at the right time. More on this later.

With debhelper being so awesome this is what the debian/rules file of a straightforward single-deb package looks like today:

#!/usr/bin/make -f

    dh $@

Not particularly exciting. It also makes you wonder why debian/rules needs to be a makefile considering there are but very few packages left that actually play to the strengths of make, let alone uses it in a fashion that goes beyond defining a bunch of standardized targets and writing everything else in limited sh syntax. It is not surprising that dh abstracts pretty far away from make though. Most people do not enjoy writing makefiles and in fact it gets very cumbersome very quickly, it’s why most software actually uses higher level tools such as autotools or cmake to generate the actual makefiles for a build. Given that fact it almost seems unfortunate that the build instructions for the packaging would be in a language not even the upstream software developers enjoy using.

So. Let’s rake the build instead 😉

Easiest way is to take the simple rules from above and port it to rake. If it still manages to create a deb we are good.

#!/usr/bin/rake -f

rule /.*/ do |t|
  sh "dh #{t}"

This does exactly what the make based version does. It has a shebang pointing to rake, it defines a pattern rule matching everything and then runs a shell that calls dh with the target name of the match as argument. To make this a bit clearer we can call the rules file like so:

./debian/rules clean

Which will ultimately do nothing more than call

dh clean

Incredibly dull. Surely that can not be all there is to it.

Well, it isn’t. But to explain why this is actually not as sweet as it looks we have to take a closer look at dh, or rather the dh manpage which has the following to say: “A debian/rules file using dh can override the command that is run at any step in a sequence, by defining an override target.”. Quite the abstract statement. Here’s an example:

#!/usr/bin/make -f

    dh $@


There is a debhelper called dh_strip which does something of no consequence to us. And we can override it being called by defining an override target in our rules, this target will then be called instead of the actual dh_strip. Let us give it a whirl in rake…

#!/usr/bin/rake -f

rule /.*/ do |t|
  sh "dh #{t}"

task :override_dh_strip do

Alas, this doesn’t actually print the expected output. In fact it still calls dh_strip. Now the reason for it not working becomes apparent after having thought about how dh could possibly know that we have an override.

Dh, like the rest of the debhelper suite, is written in perl. So it has no direct understanding of a make file, meaning in order for dh to let the rules override things it really only has 3 major options:

  1. Calling a possible override target and only call the executable directly if the target call raises an error (which has the notable disadvantage of not being able to tell when the target itself failed to run and when it is not defined to begin with)
  2. Parsing the debian/rules file (god I hope, it is not this… mind you, dh is written in perl not make)
  3. Querying the rules’ target list through make and only call the target if it is defined

Since it is clearly not using the first option as that would work just fine (we can call the target ourselves without problem using ./debian/rules override_dh_strip) it must be one of the latter two. It doesn’t really matter which of the two methods dh is employing since both are pretty much entirely incompatible with using anything but make for the rules file. Shame on you, dh, shame on you.

Screw it. This exercise was way too boring anyway. We have learned that we can generally use something other than make to write the rules file. We just can not expect to use debhelper’s dh sequencer with our non-standard rules. It is a good thing the sequencer is separate from the actual debhelper scripts I guess..

Next time: How to rewrite dh using rake and then use that rake sequencer for debian/rules.

One thought on “debian/rake: A Tale of Rubies

  1. See “sub rules_explicit_target” in /usr/bin/dh; calls make to see if it has the target it’s looking for – open(MAKE, “LC_ALL=C make -Rrnpsf debian/rules $dummy_target 2>/dev/null |”);

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s