In my previous post I discussed how to use a Makefile
to set version and
build information at compile time. Although this approach may work fine for you, it has
three drawbacks I want to discuss.
1. Simplicity#
Andrew responded on the golang-nuts mailing list with the following comment:
To me it seems like you took something simple and cross platform “go generate” + “go install/build” and turned it into something more complicated and less portable.
Although I’m not sure go generate
is relevant in this case, I agree that on some level
a Makefile
is complicating things unnecessarily. Let’s remove it!
2. Non-reproducable builds#
Guilio responded with:
I only have an issue with buildTime: it makes the build not reproducible.
This is a valid point. The idea that if you compile a given version of your application, the resulting binary’s hash (be it MD5 or whatever) should be equal to that of any other binary build from that specific version.
By using BuildTime
the binary is never the same.
Build time is also irrelevant. It does not matter when a binary was compiled, but it does matter which precise version was build.
Let’s replace BuildTime
with the current git commit hash instead.
3. Why is there a VERSION
file?#
If you’re going to store version information under version control, you might as well put it right in the code, where it belongs.
Let’s remove VERSION
and instead create a nice version.go
to handle everything.
Let’s refactor#
Okay, first things to go are Makefile
and VERSION
.
Next, I created a core/version.go
which contains the necessary version information.
I’ve also taken the liberty of creating a proper struct for the version information,
including major, minor and patch numbers, as well as a label and release name.
package core
import "fmt"
type version struct {
Major, Minor, Patch int
Label string
Name string
}
var Version = version{1, 2, 3, "dev", "Chuck Norris"}
var Build string
func (v version) String() string {
if v.Label != "" {
return fmt.Sprintf("Roll version %d.%d.%d-%s \"%s\"\nGit commit hash: %s", v.Major, v.Minor, v.Patch, v.Label, v.Name, Build)
} else {
return fmt.Sprintf("Roll version %d.%d.%d \"%s\"\nGit commit hash: %s", v.Major, v.Minor, v.Patch, v.Name, Build)
}
}
As you can see, it’s quite easy to set and update the version numbers, label and release name.
Build
is still set at compile time and contains the current git commit hash.
Because the go build
command is quite long, I’ve put it in a nice build.sh
file that
makes building easier.
go build -ldflags "-X github.com/ariejan/roll/core.Build=`git rev-parse HEAD`" -o roll main.go
This will result in a build that reports version information like this:
$ ./roll version
Roll version 1.2.3-dev "Chuck Norris"
Git commit hash: b72b076af8b18ef4f6b10296f12840f23258acec
Check that SHA#
If you want, you can grab the code and run ./build.sh
yourself. The resulting binary
has a SHA-1 of 3ad7509279690d99e4144332dc200ede732663fd
. Yay for reproducable builds!
Naming variables in ldlags#
A short note on Peter Kleiweg’s comment. He pointed out that I could use
"-X main.Build=`git rev-parse HEAD`"
This would be true if the Build
variable is in the main
package. But because it’s
not (it’s in core
) I have to specify the full package name.
Thank you!#
Thanks to all the awesome gophers responding to my previous post! It’s great to get feedback and get to learn more about Golang. Keep the comments coming, please!