Creating automated multi-platform Golang executable binary releases with xgo and Github Actions.

Glen Keane
6 min readJan 24, 2020

--

A hipster gopher, wearing a github shirt and holding a wrench

TL/DR: If you’re not interested in the journey it took for me to get here, and details on the implementation, jump to the end of this post to see what the actions pipeline looks like. If you want to use it, you will need to take the configuration and adapt it to your use-case.

The Backstory

I’m pretty much a beginner with the golang language and to help learn it I’ve been working on a proof of concept of a http1.1 benchmarking tool. I have previously worked on and help to maintain another tool for the same purpose: autocannon. I used this as a fun exercise to learn something with a complex task that I already understand deeply.

The power of autocannon is that it is a cross-platform compatible tool out of the box because it is a node.js based tool. Once you had node.js on your system all you ever had to do to use autocannon was run `npm i -g autocannon` and you could use it without needing any native dependencies. It just worked.

Shipping something for end-users to easily get started

Code, build and deploy pipeline image

The primary goal of any software developer should be to make life better for their users. That means their software should be as easy to use as possible. The installation step is the first interaction anyone has with any software. This step needs to be a familiar and straightforward process for users to understand and trust your software.

I did know there were patterns already set such as how compiled CLI tools, such as kubectl, get shipped and installed so I aimed to imitate that. Being someone who likes shipping software with the free tools easily available to me, my goal was to replicate this known pattern by making use of built-in Git and Github features, using tagging, Github releases with assets and then to tie it all together with Github Actions.

Using these tools, I wanted to enable the ability to develop the code/test/verify locally, commit and push as needed, and tag for a release. On a tag push, I would like there to be a new release created with all the binaries attached, after all of the tests passed successfully.

Lessons Learned Using Github Actions

Github actions mockup

I had very briefly looked at Github Actions and knew at a high level how they worked. I knew that only running on a tag and then creating a tag release action would be straightforward, but I didn’t know the exact tooling available to me.

As I investigated I quickly discovered actions/upload-release-asset, and the simple pipeline it provides allows developers to upload a single asset to a release. I’m sure using the matrix feature of Github actions you could potentially upload more, but that turned out to be a rabbit hole I very quickly got frustrated with.

I then discovered crazy-max/ghaction-xgo, which is an awesome tool that has been written to use xgo as an action. This would allow you to compile a go executable binary in your action pipeline for multiple systems. These compiled executable binaries are then stored in a configured sub-directory in the repo. This super useful tool is what I needed to get multiple platform binaries built and ready for uploading for a tagged release, but this didn’t solve the problem of actually uploading them.

I then tested wiring the xgo action up to the upload-release-asset action, but it fell over very quickly. I couldn’t use that release asset action multiple times in an action pipeline for the several binaries created, so I needed to do something different. At this point, I started searching for something to allow me to do this, but I turned up nothing. I knew what I needed to do was somehow take the directory of built binaries and upload them to a release.

While upload-release-asset only worked on a single file, I wanted something that worked on a single directory.

Building My Tools

To achieve my goal, I started to look into writing my own Github Action and I quickly found that the getting started guide uses simple node.js code with some simple YAML configuration. With the knowledge that it is very easy to just write with node.js code, I took a peek at the existing upload-release-asset action. I found it easy to understand with only~40 lines in the implementation that is very straightforward.

Using this existing code as the base I was able to very quickly build, push and debug my action. This action now lives at glentiki/xbin-release-action. This action allows me to just point the action at a directory and the files within it are uploaded to the release. They are auto-configured to be downloaded as binary files for the users.

Some limitations exist but they still allow the delivery of the binaries in a pattern similar to kubectl. For example, you may have to chmod the binary on macOS before using it to make it executable, etc.

The Action Configuration

So for the important bit. I had the details figured out and decided to put it all together in an easy to use/re-use action pipeline. The below outlines the configuration needed, and the documentation I provided for users to install and use the binaries. 🚀

The below action runs on a tag push and tests the code and then creates a Github Release. It cross-compiles the go binary with xgo and uploads the generated directory of binaries to the Release as assets. These can be downloaded and used by users, much like kubectl can be downloaded and used.

The above configuration provides the following pipeline:

Which then provides the following in your repository releases:

Installing published binaries

At this stage, I had binaries that existed somewhere, but I needed to supply the details for users to install them. The following paragraphs are an example of information I could put within my readme for users.

Binaries are published in the format: https://github.com/glentiki/autocannon-go/releases/download/$GIT_TAG/autocannon-go-$SYSTEM-$PLAFORM

To install on macOS, you would need to run the following and then add to your path:

curl -LO "https://github.com/GlenTiki/autocannon-go/releases/download/v0.0.1/autocannon-go-darwin-10.6-amd64" && chmod +x autocannon-go-darwin-10.6-amd64

To install on Windows you could install from this link or if you have curl installed then run the following and then add to your path:

curl -LO "https://github.com/GlenTiki/autocannon-go/releases/download/v0.0.1/autocannon-go-windows-4.0-amd64.exe"

To install on Linux, you could curl the following and then add to your path:

curl -LO "https://github.com/GlenTiki/autocannon-go/releases/download/v0.0.1/autocannon-go-linux-amd64" && chmod +x autocannon-go-linux-amd64

You can use the following URLs to retrieve autocannon-go as needed, substituting the $GIT_TAG for the released version tag. E.g. v0.0.1.

https://github.com/glentiki/autocannon-go/releases/download/$GIT_TAG/autocannon-go-darwin-10.6-amd64https://github.com/glentiki/autocannon-go/releases/download/$GIT_TAG/autocannon-go-darwin-10.6–386https://github.com/glentiki/autocannon-go/releases/download/$GIT_TAG/autocannon-go-linux-amd64https://github.com/glentiki/autocannon-go/releases/download/$GIT_TAG/autocannon-go-linux-386https://github.com/glentiki/autocannon-go/releases/download/$GIT_TAG/autocannon-go-windows-4.0-amd64.exehttps://github.com/glentiki/autocannon-go/releases/download/$GIT_TAG/autocannon-go-windows-4.0-386.exe

--

--

Glen Keane

20-something | Tech, Science, Laughing | Prophet of the Party Parrot | Wannabe Chewbacca | He/Him | Generic NPC