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, 2018If 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.