Structuring my Go projects

Written by Will Rouesnel

Recently I’ve been maintaining a Github repository to serve as a generic template for my Golang projects, and its been working rather well for me.

The repository is here: Self-contained Go Project

The basic idea is that using this template, you can setup a Go project with vendored dependencies not just for the main project but also for every tool used in building and linting it (with the exception of make, git and a working Golang install).

go get <my project>
cd $GOPATH/src/<my project>
make

does a production build.

How to Use It

Out of the box (i.e. git clone https://github.com/wrouesnel/self-contained-go-project.git) on a Linux machine it should be all setup to go. I’ve made some effort to try and remove Linux specific things from it, but since I don’t run Mac OS or Windows for Go development it’s probably not working too well there.

Essentially, it’ll build multi-platform, CGO-less binaries for any main package you place in a folder underneath the cmd directory. Running make binary will build all current commands for your current platform and symlink them into the root folder, while running make release will build all binaries and then create tarballs with the name and version in the release directory.

It also includes bevy of other CI-friendly commands - namely make style which checks for gofmt and goimports formatting and make lint which runs gometalinter against the entire project.

Philosophy

Just looking at the commands, the main thing accomplished is a lot of use of make. It’s practically used for ergonomics more then utility to some level since make is a familiar “build whatever this is” command in the Unix world.

But, importantly, make is used correctly - build dependencies are expressed and managed in a form it understands so it only rebuilds as necessary.

But there is more important element, and that is not just that there is a Makefile but that the repository for the project, through govendoring includes not just the code but also the linting and checking tools needed to build it, and a mechanism to update them all.

Under the tools directory we have a secondary Makefile which is called from the top-level and is reposible for managing the tools. By running make update here we can go get a new version of gometalinter, extract the list of tools it runs, then automatically have them updated and installed inside the source directory and made available to the top level Makefile to use to run CI tasks.

This combines to make project management extremely ergonomic in my opinion, and avoids dragging a heavier tool like Docker into the mix (which often means some uncontrolled external dependencies).

Basically: you check in everything your project needs to be built and run and tested into the one Git repository, because storage is cheap but your time is not and external dependencies can’t be trusted to always exist.

Conclusion

It’s not the be all and end all - in build tooling there never is one, but I’m thusfar really happy with how this basic structure has turned out as I’ve evolved it and it’s proven relatively easy to extend when I need to (i.e. adding more testing levels, building web assets as well with npm and including them in the go-binary etc.)