Operable Ruby
Ruby today is a moving target. The reference implementation is releasing rapidly on several branches and at least three alternatives to MRI are in wide use. The RubyGems package manager is releasing nearly every week and the dizzying array of variously compatible gems are releasing whenever they feel like it. Exciting times, to be sure, but also an operator’s nightmare. At every level of this dependency tree, robust packaging brings order to the chaos. Given that, it surprises me to read folks recommending RVM for production use.
An operable production environment relies on packages to account for most files on the system. Think of it as a paper trail: dpkg-query -S pathname
will tell you the package that owns a particular file. With the mapping of package names to sets of pathnames, packages become uninstallable. Naturally, anything that can be uninstalled entirely can be upgraded. Do you trust that shell script to measure up?
Hopefully we’ve settled that packages are not optional in production. By my philosophy, development systems should faithfully mimic production systems in every affordable way, so I extend the policy to my development virtual machines, too. Reliable development environments are just as important to me as reliable production environments.
There’s been an interesting discussion on the devops-toolchain mailing list this week about how best to package the Ruby environment. Unsurprisingly, RVM came up as well as several better options. I’d like to offer executable proof that building packages from arbitrary Ruby versions is easy.
The remainder of this article will walk through building up a shell script that uses debra
(1) to package arbitrary versions of Ruby for Debian-based Linux distributions.
We’re building a system package here: the prefix is /usr
and the files should be owned by root
. It isn’t debra
’s business who owns the files in the package, so we will run the entire build process as root to ensure this is true.
First things first, the version numbers we’re using:
VERSION="1.9.2" PATCH="180" DPKG_BUILD_ARCH="$(dpkg --print-architecture)"
Note well that set -e
should really appear in every single shell script you write so they will exit on failure.
set -e
Next, use debra-create
(1) to start building up a Debian package in a temporary directory. The first argument to all debra
commands is the directory being used for the build. This block uses one of my favorite shell scripting idioms: using mktemp
(1) to create a temporary directory and trap
to clean up on EXIT
(using debra-destroy
(1) in this case).
DESTDIR="$(mktemp -d)" debra create "$DESTDIR" trap "debra destroy \"$DESTDIR\"" EXIT
The extensions we’re going to enable in the Ruby build require a few development libraries:
apt-get -y install libssl-dev libreadline5-dev zlib1g-dev
Now for the meat of the Ruby build. Using debra-sourceinstall
(1) makes this very easy. It downloads, unpacks, and builds from the tarball given by the second argument. -b
provides a “bootstrapping” command that is run immediately after unpacking to setup the extensions we want to enable. -p
relays its argument as --prefix
to the ./configure
program and -f
provides other arbitrary arguments to ./configure
. After configuring, sourceinstall
(1) runs make
and make install
to install the package in DESTDIR
.
debra sourceinstall "$DESTDIR" \ "ftp://ftp.ruby-lang.org/pub/ruby/1.9/ruby-$VERSION-p$PATCH.tar.gz" \ -b"sh -c 'echo fcntl\\\nopenssl\\\nreadline\\\nzlib >ext/Setup'" \ -p/usr -f"--program-suffix=-$VERSION"
(See sourceinstall
(1) for more options affecting the build process.)
The last artifact a Debian package needs is a control file for version and dependency metadata. Other metadata like the MD5 sum of each file are handled automatically by debra
.
cat >$DESTDIR/DEBIAN/control <<EOF Package: ruby-$VERSION Version: $VERSION-p$PATCH Section: devel Priority: optional Essential: no Architecture: $DPKG_BUILD_ARCH Depends: libc6, libssl0.9.8, libreadline5, zlib1g Maintainer: Richard Crowley <r@rcrowley.org> Description: Ruby $VERSION. EOF
With that, we can build the package in a file that follows the Debian convention of package_version_architecture.deb
.
debra build "$DESTDIR" "ruby-${VERSION}_${VERSION}-p${PATCH}_${DPKG_BUILD_ARCH}.deb"
This package is useful in development and production, can be uninstalled or upgraded with ease, coexists peacefully with the system’s Ruby plus other Rubies built this way, and isn’t gated by anyone else’s feature freezes or patch schedules. This whole example is available at https://gist.github.com/891447.