CloudFlare's DNS server, RRDNS, is written in Go and the DNS team used to generate a file called version.go
in our Makefile. version.go
looked something like this:
// THIS FILE IS AUTOGENERATED BY THE MAKEFILE. DO NOT EDIT.
// +build make
package version
var (
Version = "2015.6.2-6-gfd7e2d1-dev"
BuildTime = "2015-06-16-0431 UTC"
)
and was used to embed version information in RRDNS. It was built inside the Makefile using sed
and git describe
from a template file. It worked, but was pretty ugly.
Today we noticed that another Go team at CloudFlare, the Data team, had a much smarter way to bake version numbers into binaries using the -X
linker option.
The -X
Go linker option, which you can set with -ldflags
, sets the value of a string variable in the Go program being linked. You use it like this: -X main.version 1.0.0
.
A simple example: let's say you have this source file saved as hello.go
.
package main
import "fmt"
var who = "World"
func main() {
fmt.Printf("Hello, %s.\n", who)
}
Then you can use go run
(or other build commands like go build
or go install
) with the -ldflags
option to modify the value of the who
variable:
$ go run hello.go
Hello, World.
$ go run -ldflags="-X main.who CloudFlare" hello.go
Hello, CloudFlare.
The format is importpath.name string
, so it's possible to set the value of any string anywhere in the Go program, not just in main. Note that from Go 1.5 the syntax has changed to importpath.name=string
. The old style is still supported but the linker will complain.
I was worried this would not work with external linking (for example when using cgo) but as we can see with -ldflags="-linkmode=external -v"
the Go linker runs first anyway and takes care of our -X
.
$ go build -x -ldflags="-X main.who CloudFlare -linkmode=external -v" hello.go
WORK=/var/folders/v8/xdj2snz51sg2m2bnpmwl_91c0000gn/T/go-build149644699
mkdir -p $WORK/command-line-arguments/_obj/
cd /Users/filippo/tmp/X
/usr/local/Cellar/go/1.4.2/libexec/pkg/tool/darwin_amd64/6g -o $WORK/command-line-arguments.a -trimpath $WORK -p command-line-arguments -complete -D _/Users/filippo/tmp/X -I $WORK -pack ./hello.go
cd .
/usr/local/Cellar/go/1.4.2/libexec/pkg/tool/darwin_amd64/6l -o hello -L $WORK -X main.hi hi -linkmode=external -v -extld=clang $WORK/command-line-arguments.a
# command-line-arguments
HEADER = -H1 -T0x2000 -D0x0 -R0x1000
searching for runtime.a in $WORK/runtime.a
searching for runtime.a in /usr/local/Cellar/go/1.4.2/libexec/pkg/darwin_amd64/runtime.a
0.06 deadcode
0.07 pclntab=284969 bytes, funcdata total 49800 bytes
0.07 dodata
0.08 symsize = 0
0.08 symsize = 0
0.08 reloc
0.09 reloc
0.09 asmb
0.09 codeblk
0.09 datblk
0.09 dwarf
0.09 sym
0.09 headr
host link: clang -m64 -gdwarf-2 -Wl,-no_pie,-pagezero_size,4000000 -o hello -Qunused-arguments /var/folders/v8/xdj2snz51sg2m2bnpmwl_91c0000gn/T//go-link-mFNNCD/000000.o /var/folders/v8/xdj2snz51sg2m2bnpmwl_91c0000gn/T//go-link-mFNNCD/000001.o /var/folders/v8/xdj2snz51sg2m2bnpmwl_91c0000gn/T//go-link-mFNNCD/go.o -g -O2 -g -O2 -lpthread
0.17 cpu time
33619 symbols
64 sizeof adr
216 sizeof prog
23412 liveness data
Do you want to work next to Go developers that can always make you learn a new trick? We are hiring in London, San Francisco and Singapore.