Go 1.18 debug/buildinfo features

Hello and welcome to another blog article. Today, I would like to discuss one feature of Go 1.18, that I am interested in. No, this will not be another article about generics. The feature I would like to write about is something that might be under the radar for most people, but it still might be useful.

If you ever wrote a CLI app in Go you are very familiar with injecting information during the build process into global variables. For instance:

package main

import "fmt"

var Version string

func main() {
	fmt.Println("Version: ", Version)
}
❯ go build -ldflags="-X 'main.Version=v1.0.0'"
❯ ./test 
Version:  v1.0.0

Go 1.18 introduced support for version control systems for the debug/buildinfo package. Therefore, instead of using a global variable and injecting the information during the build process, you can just let Go handle this:

package main

import (
	"fmt"
	"runtime/debug"
)

func main() {
	info, _ := debug.ReadBuildInfo()
	fmt.Println(info)
}
❯ git tag v1.0.0
❯ go build .
❯ ./test
go	go1.18
path	github.com/shibumi/test
mod	github.com/shibumi/test	(devel)	
build	-compiler=gc
build	CGO_ENABLED=1
build	CGO_CFLAGS=
build	CGO_CPPFLAGS=
build	CGO_CXXFLAGS=
build	CGO_LDFLAGS=
build	GOARCH=amd64
build	GOOS=linux
build	GOAMD64=v1
build	vcs=git
build	vcs.revision=7e22e19e829d84170072d2459e5870876df495ed
build	vcs.time=2022-04-03T16:59:50Z
build	vcs.modified=false

Isn’t this cool?! Go will automatically detect the version control system and will automatically add the current revision and time to it. With this revision we are now able to get the version back:

❯ git describe --contains 7e22e19e829d84170072d2459e5870876df495ed
v1.0.0

The disadvantage of the new feature is that it is less customizable, but I don’t really think this is an issue to be honest. Moreover, there is no need to explicitly use debug.ReadBuildInfo() in your code, it is also possible to see the same information via:

❯ go version -m ./test 
./test: go1.18
	path	github.com/shibumi/test
	mod	github.com/shibumi/test	(devel)	
	build	-compiler=gc
	build	CGO_ENABLED=1
	build	CGO_CFLAGS=
	build	CGO_CPPFLAGS=
	build	CGO_CXXFLAGS=
	build	CGO_LDFLAGS=
	build	GOARCH=amd64
	build	GOOS=linux
	build	GOAMD64=v1
	build	vcs=git
	build	vcs.revision=7e22e19e829d84170072d2459e5870876df495ed
	build	vcs.time=2022-04-03T16:59:50Z
	build	vcs.modified=false

Another interesing side note: If your project uses external dependencies, these dependencies will get listed as well:

❯ go version -m ./embedmd 
./embedmd: go1.18
	path	github.com/campoy/embedmd
	mod	github.com/campoy/embedmd	v1.0.0	h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY=
	dep	github.com/pmezard/go-difflib	v1.0.0	h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
	build	-compiler=gc
	build	CGO_ENABLED=1
	build	CGO_CFLAGS=
	build	CGO_CPPFLAGS=
	build	CGO_CXXFLAGS=
	build	CGO_LDFLAGS=
	build	GOARCH=amd64
	build	GOOS=linux
	build	GOAMD64=v1

By the way, this information survives if you decide to strip the binary!

To summarize this little article:

  • Go 1.18 provides fancy new build information
  • VCS are automatically identified and used
  • If you only care for revision and build date, you can stop injecting values.