# Packaging for Alpine I recently started using Alpine Linux on my main daily-use Linux system, which has been by and large and smooth and pleasant experience. You can read more about it in a previous post if you'd like. => 2022-10-20-alpine-framework.txt One of the things that drew me to Alpine is that I quite like its package manager, apk. Packages in apk are basically tarballs with some metadata[1], which is a pleasing change from, say, debian-alikes. I also find apk small enough to be reasonably understandable. As part of understanding it more deeply, I decided to try packaging something for Alpine, and I chose Angband, a roguelike game I've done a bunch of development for. I specifically picked Angband because: * It wasn't already packaged * It is free software * It has relatively few deps and almost all of them are optional * I understand the codebase very well * I can make upstream fixes if I need to ## Building Angband Angband, like a lot of C programs of a certain vintage, uses autotools[2]. As is proper for such projects, its git repo does not contain a configure script, but its release tarballs do so that you don't have to have autotools installed yourself to build it. As such, the build process for the git repo looks like[3]: $ aclocal -I m4 $ autoheader $ autoconf $ ./configure --with-foo --with-bar --prefix=/baz ... $ make $ make tests $ make install And from a tarball you only need to do the last four steps. Pretty standard so far, except that in most packages the test target is called 'check' instead. That installs a bunch of the game's data files in $PREFIX/lib and installs the game binary itself into $PREFIX/games, which is pretty vintage aesthetically (newer games just go in $PREFIX/bin) but acceptable. It also installs the game's *documentation* in $PREFIX/lib, which is pretty strange - I will shortly be changing upstream Angband to put it in $PREFIX/share/doc or $PREFIX/man instead, I think. ## What's In An Alpine Package? Alpine packages (called "APKs" generally) are binary packages produced by a centralized system from manifest files, which are themselves called APKBUILD files. An APKBUILD file lists what the package depends on, a bunch of metadata about it, and also how to actually build, test, and install it. They are, in fact, just special types of shell scripts that are run in a kind of weird way. To get started making one, I followed an excellent guide on the Alpine Wiki: => https://wiki.alpinelinux.org/wiki/Creating_an_Alpine_package And then basically just cloned the aports repo to $APORTS and created an APKBUILD like so: $ cd $APORTS/testing $ newapkbuild -n angband $TARBALL_URL With the tarball URL pointing at the latest Angband source release: => https://github.com/angband/angband/releases/download/4.2.4/Angband-4.2.4.tar.gz That generated a template APKBUILD file, into which I filled the the package description (pkgdesc), url, license[4], and dependencies (just ncurses). So far so good. To test the package: $ abuild -r angband And it... immediately failed to compile. ## Autotools To The Un-Rescue When you run autotools, it generates a configure script, but it also generates a pair of helper scripts called config.guess and config.sub. The Angband tarball has very elderly (2012) versions of these which don't know what a "musl" is. Fortunately, both Alpine and every non-Linux Unix run into this problem and the best practice is well established: just replace those helper scripts with better ones. Alpine has a builtin: prepare() { default_prepare update_config_sub } ## Dependencies After that fix, the configure script runs, hooray! But it immediately complains that it has no valid frontends, which basically means it couldn't find any of curses, SDL1, SDL2, or Xlib, even though I had ncurses in the deps list. Hmmm. The APKBUILD lists a few different kinds of deps, but the most important ones for my purposes were depends and makedepends. The depends var is packages that need to be present at runtime to run the package, and the makedepends var is packages that need to be present at compile time to build the package. Having package foo in depends doesn't imply having foo-dev in makedepends or vice versa! So, I first tried this: depends="ncurses sdl2 ..." makedepends="ncurses-dev sdl2-dev ..." But then, during code review of my APKBUILD, I learned something very cool about alpine: actually, the packaging tool itself will check which libraries the resulting binaries are linked against and *infer* which packages it should depend on automatically! So, since my resulting binary links against: so:libncurses.so.6 so:libSDL2.so:xyz ... The package is automatically inferred to depend on those libraries. Cool! As a result, I ended up with just: makedepends="ncurses-dev sdl2-dev sdl2_image-dev sdl2_mixer-dev sdl2_ttf-dev" ## Tests I originally tried to get Angband to build and run its test suite as part of Alpine's 'check' step, but unfortunately couldn't get this to happen. They passed locally, but when the Alpine continuous integration tried to build the package, the tests died with complaints about missing data files. The problem turned out to be that Angband's *unit tests* look in the *install location* for the data files they need to work, not in the source tree! On my own development system, I had Angband installed, so the data files were there; on the CI system, Angband was being built in a clean chroot and they were missing. This is an Angband bug which I sent a fix for: => https://github.com/angband/angband/pull/5518 However, until the Angband upstream both merges that and cuts a release with the fix, it'll have to wait, so for now tests are disabled: options="!check" I also learned a useful tool for avoiding this kind of problem in future: $ abuild rootbld This builds the package inside a clean chroot, like the CI system does[5], which helps flush out places where the package is depending on the system state. Very handy! Once you've done that, you get a resulting package in the REPODEST set in /etc/abuild.conf, and you can install it: $ apk add -X $REPODEST angband And voila, the built package installs (from your local out directory[6]) and runs locally :) ## Publishing So, at this point I had a brand new file at $APORTS/testing/angband/APKBUILD. This file (along with any needed patches/etc) are the only things you actually need to make a package - the package binaries are built for you by Alpine's CI system from the instructions in the APKBUILD. Angband doesn't need any patches so the APKBUILD is the entire package. To contribute APKs, you can either mail alpine-aports@lists.alpinelinux.org or use their GitLab instance. As of this writing the mailing list had a warning about it being unmonitored for a while, so I went for GitLab. Sign up, verify email, the whole usual web song & dance, and then follow a GitHub-like fork-and-merge-request flow: => https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/40686 A couple rounds of code review and my package got merged! Yay, exciting :). I tried installing it a few minutes later and it was already available :D ## Closing Thoughts It was overall a lot easier than I feared it would be to package Angband, although I perhaps should not have worried too much since it's more or less a standard program that uses configure[7]. This entire process, including a bit of debugging and discussion on IRC, took maybe 90 minutes total to do and was pretty painless. I might start packaging some other roguelikes as well now, since Alpine appears to have very few of them. I want to give a special mention to the Alpine community as well! The folks in #alpine-linux and #alpine-devel were unfailingly helpful and patient, especially psykose who answered a whole lot of questions, gave a whole lot of advice, and ended up reviewing my eventual pull request. It felt very welcoming and I'm looking forward to being more of a part of this community if I can. => ircs://irc.oftc.net/alpine-linux => ircs://irc.oftc.net/alpine-devel => ircs://irc.oftc.net/psykose,isnick And extra extra special thanks to psykose for fact-checking this blog post :D Thanks for reading! Hopefully this is just the second of many more Alpine posts :) [1]: At least until APK version 3, where they are a custom format of some sort. I don't know of any docs about this but if you do please mail me! [2]: Actually, it uses autotools *and* cmake in parallel, which is distressing. The CMakeLists file reimplements what the autotools build does. Ugh. [3]: There is a script in the source tree called 'autogen.sh' that automates re-running autotools, with some ugly BSD-specific hackery. [4]: Angband is old, and like many roguelikes was originally released under its own custom license, which was sort of BSD-ish but had a noncommercial clause. This has caused a lot of trouble over the years and one of the prior maintainers put in a ton of work to track down every contributor and get them to relicense to GPLv2. However, a bunch of the game's assets are still under strange licenses. See the full copying file for the gory details: => https://github.com/angband/angband/blob/master/docs/copying.rst [5]: The CI system actually makes new Docker containers rather than new chroots and doesn't use rootbld, but the idea's the same. [6]: You can also put your local build directory into /etc/apk/repositories to avoid using -X every time. [7]: I kind of wish it did not use configure, though. The configure script is nearly 9,000 lines of shell and nearly all of it is testing things that are either obsolete or irrelevant for Angband. Perhaps a hand-written configure script is in order.