Building and releasing Go binaries
Tags: go, tdd, testing
Published: 2023-01-01 19:41:45


## Introduction

Whether you’ve written a CLI or a HTTP server, most of the time Go code can be compiled down to a static binary which is self-contained and can run on the target system you specify. In an earlier chapter we covered cross-compilation, at this point you should consider whether you need to target one environment such as Windows, Linux or MacOS, or multiple.

## The release-it example

Release-it repo is an example of a static Go binary that can be built and released through GitHub Actions. It uses a Makefile, so it can also be run in other CI pipelines or manually as and when you create a new release. The Makefile has the following properties, all of which were covered in previous chapters already:
	• It builds binaries for multiple platforms and Operating Systems
	• It injects the Commit and Version from git into the final binary

```go
Version := $(shell git describe --tags --dirty)
# Version := "dev"
GitCommit := $(shell git rev-parse HEAD)
LDFLAGS := "-s -w -X main.Version=$(Version) -X main.GitCommit=$(GitCommit)"
.PHONY: all
all: gofmt dist
.PHONY: gofmt
gofmt:
@test -z $(shell gofmt -l ./ | tee /dev/stderr) || (echo "[WARN] Fix formatting issues
↪
with 'make fmt'" && exit 1)
.PHONY: dist
dist:
mkdir -p bin/
CGO_ENABLED=0 GOOS=linux go build -mod=vendor -a -ldflags $(LDFLAGS) -installsuffix cgo
↪
-o bin/release-it-amd64
CGO_ENABLED=0 GOOS=darwin go build -mod=vendor -a -ldflags $(LDFLAGS) -installsuffix cgo
↪
-o bin/release-it-darwin
GOARM=7 GOARCH=arm CGO_ENABLED=0 GOOS=linux go build -mod=vendor -a -ldflags $(LDFLAGS)
↪
-installsuffix cgo -o bin/release-it-arm
GOARCH=arm64 CGO_ENABLED=0 GOOS=linux go build -mod=vendor -a -ldflags $(LDFLAGS)
↪
-installsuffix cgo -o bin/release-it-arm64
GOOS=windows CGO_ENABLED=0 go build -mod=vendor -a -ldflags $(LDFLAGS) -installsuffix cgo
↪
-o bin/release-it.exe
```
In addition, it runs gofmt for good measure.
If there were any unit tests in the project, then we could add an additional target for them, such as:
```go
.PHONY: gotest
gotest:
	go test ./...
```

Then update the all line to gofmt gotest dist which would run each of the tasks for us.
Clone the example and build it:

```shell
$ mkdir $GOPATH/src/github.com/alexellis/
$ cd $GOPATH/src/github.com/alexellis/
$ git clone https://github.com/alexellis/release-it
$ cd release-it
$ make
```

By default make will try to run the all target, but you can also `run make gofmt` if you want to test formatting before committing to a build. You’ll find each binary in the bin/ folder and can run them on your system if it matches the OS/architecture pair, or copy it to another system to run it there. When you release binaries to users, GitHub’s release functionality can come in handy because it allows you to upload the binaries manually or via automation.

## Releasing your binaries with a GitHub Action

GitHub Actions is free to use for open source repositories (i.e public ones) and comes with a number of free minutes for private ones also. This example should be adaptable to other platforms like Jenkins or GitLab CI, since the concepts are largely the same.
In our example, we’ll first checkout the code from GitHub for the release tag, then run make all, followed by uploading the release artifacts to the GitHub release.

```yaml
name: publish
on:
  push:
    tags:
      - '*'
jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@master
      with:
        fetch-depth: 1
    - name: Make all
      run: make all
    - name: Upload release binaries
      uses: alexellis/upload-assets@0.2.2
      env:
        GITHUB_TOKEN: ${{ github.token }}
      with:
        asset_paths: '["./bin/release-it*"]'
```

Each step in the workflow file can use a specific GitHub Action like alexellis/upload-assets@0.2.2 or actions/checkout@master, but when you leave that field blank, it uses the base system such as ubuntu-latest. On the base Ubuntu runner you can execute standard shell and build commands like make all. The alexellis/upload-assets action is one that I built to use on all the various OSS projects I maintain and allows a number of files to be uploaded.
Users can then go ahead and download your binary and run it on their system.

For instance:
```shell
$ cd /tmp/
$ curl -sLSf \
-o ./release-it \
https://github.com/alexellis/release-it/releases/download/0.2.11/release-it-darwin \
	&& chmod +x ./release-it \
	&& ./release-it
release-it: 0.2.11, commit: 4379cbf694c55bd98f5080d76ac5972fd89dcc65
```