Friday 28 August 2009

Keeping old branches building when the world has moved on

I recently upgraded the system used to build our software to Debian GNU/Linux x86-64. The main software is cross-compiled for an embedded target but there are various host programs and unit tests that are compiled natively. Parts of the software are quite old and don't compile successfully for 64-bit. Luckily -m32 mostly solved this problem so everything keeps on compiling. The cross compiler is compiled as 64-bit so it can take advantage of the extra registers to hopefully improve performance.

Unfortunately -m32 didn't solve everything. The software required many libraries and not all of these were included in the various 32-bit library compatibility packages provided by Debian. The best solution was to modify the build system to build these libraries too. This makes us less reliant on the operating system versions and let me use the same versions as we do with buildroot for the target which reaped other benefits too. One day I'd like to move to a world where the only part of the host system that is used is the compiler to compile own host and cross compilers.

Eventually the trunk is all fine and everything builds happily. But what about older branches? The changes were quite invasive and applying them to supposedly stable branches didn't give me a warm fuzzy feeling. The logical solution was to build them in an entire 32-bit environment. I considered using something like VirtualBox or QEMU but using a completely separate host would have caused confusion for all the developers.

In the end I discovered schroot and followed the instructions. It was very quick and easy to set up. I added various bind mounts to /etc/fstab to ensure that /home was mounted and it was then easy to set a 32-bit environment build going with just a single command. The instructions in the link are clear so I won't go over them again. I made use of linux32 to ensure that uname gave the expected result.

But, I thought it could be even simpler.

Each branch knows what build environments it supports. This can easily be indicated via a file in the top level directory. If the file doesn't exist then some sensible defaults can be used. Once this information exists it is easy to write a script which reads this, checks against the current native build environment and either runs the build directly or inside the chroot. Something like this:
 top=.
while [ ! / -ef "${top}" -a ! -f "${top}/Make.rules" ]; do
top=${top}/..
done

if [ / -ef "${top}" ]; then
echo "Not in source tree."
exit 1
fi

# Defaults
HOST_BUILD_ENVIRONMENTS=i686

if [ -f "${top}/BuildInfo" ]; then
source "${top}/BuildInfo"
fi

this_env="`uname -m`"
use_chroot=
for e in ${HOST_BUILD_ENVIRONMENTS}
if [ "${e}" = "{this_env}" ]; then
use_chroot=
break
elif [ "${e} = "i686" ]; then
use_chroot="$chroot_i686"
fi
done

if [ -n "${use_chroot}" ]; then
echo "Compiling in ${use_chroot} chroot"
schroot -p -q -c ${use_chroot} -d `pwd` -- linux32 make "$@"; then
else
make "$@"
fi

The actual script is more complex than this because it contains better error handling. It also deals with make -C dir and deciding whether -jN can be passed too.

This can easily be extended to support building under different chroots for older operating system versions for example.

No comments: