Zombie Zen

How I packaged a Go program for Windows and Linux

By Ross Light
Icon by Philipp Petzka, used under a Creative Commons license.

Icon by Philipp Petzka, used under a Creative Commons license.

In the two months since I published gg 1.0, a project to reduce the friction in working with Git, I’ve been working to make it more accessible and easier to install. To this end, I’ve made three big improvements:

  • A standalone Go library, gg-scm.io/pkg/git, allows any Go program to interact with Git repositories. (I may end up writing another blog post just about this — stay tuned!)
  • Windows support, complete with MSI installer.
  • An APT repository for Debian and Ubuntu users.

If you’re interested in trying out gg, it’s never been easier: see the instructions at gg-scm.io. Read on if you’re interested in how to package a Go program for Windows and Linux.

Packaging gg has been an exciting learning experience: this is the first time I’ve built Debian/Ubuntu or Windows packages from scratch. It even inspired me to create a more general tool to help other Debian packagers, but more on that in a moment.

Windows

Surprisingly, I didn’t have to make too many changes to the core of gg to get it running on Windows (yay Go!). The test suite mostly passed on the first run; the biggest difficulty was mimicking Git on Windows’s editor invocation. Once I figured that out, everything else went smoothly.

Windows packaging is relatively straightforward with WiX. The installer source is a declarative XML file. The GitHub Actions Windows runner has WiX preinstalled, so the release workflow is pretty short. From zero to finish, this took me about a day’s worth of work. Nice!

Debian and Ubuntu

For Linux, there’s a wealth of different packaging formats: DEB, RPM, Nix, just to name a few. I decided to package for Debian-based systems (which includes Ubuntu) for two reasons:

  1. Personal familiarity. I’ve been using Ubuntu and Debian for over a decade now.
  2. Ubiquity. Most people I talk to use some Debian flavor and various outlets confirm that Debian makes up most of the Linux installs out there.

Packaging for Debian proved to be challenging. Unlike Windows, where it is commonplace to ship just binaries, Debian encourages shipping source packages. This meant I couldn’t just tar up the built binary and call it a day: I had to make gg conform to the Debian build process. Much to my chagrin, decades of backward compatibility and multitudes of different use cases make it difficult to discover the best set of tools to use from the docs.

Luckily, I was in the audience for Michael Stapelberg’s Go in Debian talk back in 2018, so I remembered that there was a more streamlined approach to packaging Go code for Debian. Unfortunately for me, most of the documentation is geared toward existing Debian maintainers, not someone who wants to create something outside the official Debian repositories. However, the dh-make-golang program pointed me in enough of the right directions that I was able to cobble together a reasonable setup through trial and error.

I’ll spare you the details of the setups that didn’t work. Ultimately, I used git-buildpackage on a separate debian branch of the GitHub repository, and used the version tags as the upstream source. I had to do a little customization beyond what dh-make-golang generated for me to build the man page and stamp the built binary, but once I knew what to look for, this didn’t take long. Lintian did not hesitate to tell me what I messed up!

And now to the most uncomfortable part: creating an APT repository. As of 2020-09-07, the official Debian repository setup instructions list about a dozen different tools that can generate APT repositories, with wildly differing levels of documentation and support. Talk about tyranny of small decisions! On top of that, all the tools were built with the intent to host a full Linux machine to the internet. But nowadays there are much easier ways of hosting (mostly) static content! I wanted to upload directly to a Google Cloud Storage or S3 bucket, so I wrote a new tool: aptblob. aptblob uploads Debian binary and source packages to a bucket and automatically updates the APT index files appropriately. It’s still a little rough, but it’s enough for me to use as part of a GitHub Actions workflow.

Conclusion

Packaging your software for multiple platforms can be a lot of hard work, but it makes the software accessible to more users. I find Go to be an excellent language for writing cross-platform developer tools. While Debian packaging was definitely difficult, I’m excited that aptblob may make it easier for others to create their own APT repositories. And don’t worry macOS friends, I plan to delve into Homebrew soon!

Like this post? Star gg on GitHub and consider sponsoring me!