Setting Go(1.5) variables at compile time for Versioning

Josh Roppo
3 min readDec 29, 2015

--

Injecting the Git Hash, Tag, date, build system, and version attributes into variables of a Golang binary can be very useful to validate that your build process is functioning correctly. Recently myself and a co-worker suffered some serious confusion due to a previous file manipulating versioning scheme based on sed which got stuck in a bad state and failed to update as we were debugging test builds.

Go Gopher created byRenée French

Manipulating files to set const values which are then compiled into the binary created a step separate from the compile process, and added a point for failure and confusion. Fortunately it’s easy to avoid our mistake. Go provides access to setting variables by compile time linker flags! I’ve created an example repo on Github, which has what I’m about to cover with full working examples.

-ldflags is the go [build | install] flag necessary to override values of variables defined in code which aren’t constants.

The reason I’m writing this post is because while the simple case is well documented, deeper configuration for import-able variables isn’t well defined from what I was able to Google. Being able to set flags on a version package which can be imported into multiple binaries/microservices/etc is a clean way to lock down exactly what code version is running where.

NOTE: This post is written against Go 1.5.2 This feature has been supported in older versions and flags might change in future versions; so always check the documentation.

The common case, Set a variable’s value in your main package:

package mainimport “fmt”
var MainVar string
func main() {
fmt.Printf(“MainVar: %s\n”, MainVar)
}

Compile with linker flags to set variable:

go build -ldflags "-X main.MainVar=hihi"

Execute:

$ ./cmd
MainVar: hihi

Tada! Variables will default to their type’s zero value if not specified via compile flags. So if you just ran go build; MainVar would be an empty string.

If you want to share the same compile time injected variables shared across multiple mains for separate binaries, setting the variable in main requires a separate var VarName string declaration for each main package. Creating a package which defines exported variables that can be imported into all the main packages of different binaries|microservices seems cleaner to me. The following working example is all available in the repo.

Create a package to contain your version information. This example file is written to github.com/ropes/go-linker-vars-example/src/version/version.go:

package versionvar (Version, Date, BuildUser, GitTag string)

Import and utilize the version package in your code:

package mainimport “fmt”
import “github.com/ropes/go-linker-vars-example/src/version”
func main() {
fmt.Printf(“Git Tag: %s\n”, version.GitTag)
fmt.Printf(“Build User: %s\n”, version.BuildUser)
fmt.Printf(“Version: %s\n”, version.Version)
}

Then use the standard import path syntax to set the version package’s variables. I couldn’t find clear examples of this; thus this post. I’d recommend using bash string formatting to inject variables to help keep things strait, unlike the static version.Version definition…

$ gittag=$(git describe — tags)$ go build -ldflags “-X github.com/ropes/go-linker-vars-example/src/version.GitTag=${gittag} 
-X github.com/ropes/go-linker-vars-example/src/version.BuildUser=${USER}
-X github.com/ropes/go-linker-vars-example/src/version.Version=v0.0.1”

Please pardon the formatting.. it looks cleaner in the Github README.

Output:

$./complex 
Git Tag: v0.0.1–1-gd75e8db
Build User: josh
Version: v0.0.1

Boom! There’s now valuable build information injected into the binary to aid in debugging your services! Again full working examples can be found in the Github repo, follow the README.md if you want to play with the project examples.

Hopefully this saves someone some time digging through linker docs, happy compiling!

--

--

Josh Roppo

Data Engineering, @PDXGolang person, Go Lang, Python, enthusiast, Ops, cat herder.