go generate: barely a framework

I’ve been leaning on go generate at work a lot lately and, when discussing it with friends, found that they had trouble understanding it. I figured I’d show some examples to help.

A few years ago the go authors posted about go generate. I remember reading it and being super confused. I also tried using genny, which is go generate based generics, and the (relatively minimal) magic obscured the simplicity of go generate.

So let’s be clear: go generate is almost literally the following shell script:

grep '^//go:generate' -r . -h | cut -d ' ' -f2- | bash

There’s a little more to it, but not much. It chdir’s into each package dir before running the listed command, and sets a handful of environment variables. Oh and I guess the order is well defined, but in any case there’s not a whole lot to it.

This isn’t a criticism, just to clarify what’s going on. So how can you use this? Here are two examples:

At work we have a thing that uses go-astilectron. We wanted the binary that was built to have version metadata (which git commit it was built from, what time it was built, etc.) Normally you would do this with go build -ldflags "-X 'main.compiledAt=whatever'", but it was not clear how, or even if, the go-astilectron build system exposed a way to set flags like that. One solution is to use go generate. I made a simple toy example of this in a repo in case someone wants to see it and play with it.

Another thing I made at work was to avoid needing access to our monorepo when code is actually running. I needed to find and maintain a list of Dockerfiles. I could do it by actually walking the dirs at runtime but our monorepo is huge and if this is supposed to run in production it’s not supposed to have access to git. Solution: build the list with go generate:

#!/bin/sh

cd "$(dirname "$0")/../.." || exit

exec >aws/expiration-date/listing_generated.go

echo "package main"
echo ""
echo "var apps = map[string]bool{"
find . -name Dockerfile | sort | cut -b3- | sed 's/^/	"/;s#/Dockerfile$#": true,#'
echo "}"

There’s one caveat to point out with both of the examples above though: the Go authors explicitely want users of go generate to commit the results of their work so that clients do not need to run go generate etc. I think that in the world of OSS this is right and good. At work instead want to make sure that our CI system can run go generate, which is easier to manage if we simply never check in generated code.

I hope these much simpler examples make it clear what go generate is actually doing, and what it’s not doing. If anything I feel like the important part of go generate is the explicit blessing of the Go team, rather than the tooling around the feature. It sounds silly, but the idea that, at the very least, we have a standard way to express these things is totally useful.


(The following includes affiliate links.)

If you don’t already know Go, you should definitely check out The Go Programming Language. It’s not just a great Go book but a great programming book in general with a generous dollop of concurrency.

Another book to consider learning Go with is Go Programming Blueprints. It has a nearly interactive style where you write code, see it get syntax errors (or whatever,) fix it, and iterate. A useful book that shows that you don’t have to get all of your programs perfectly working on the first compile.

Posted Mon, Nov 19, 2018

If you're interested in being notified when new posts are published, you can subscribe here; you'll get an email once a week at the most.