Embedding Data Into Go Programs

In this lesson, we'll use a built-in technique to embed the git tag and the time the program that was built into our executable. Then, we'll use this information inside the program.

Incorporating git tag for auto versioning

Versioning your code is a good practice. If you use a source control system like git, then you can tag commits using the git tag command. However, when you build your code, the program has no notion of the version of the source code it was built from.

One approach is to maintain a VERSION file or a version constant in your code and increment it every time you make a change. This is somewhat error-prone and redundant if you already tag your code.

Instead, we can embed the git tag automatically during build time. The key is to pass a -X option to -ldflags that specifies a package and variable in the program and the value.

$ go build -ldflags="-X '<package>.<variable>=<value>'"

To get the git tag during build time we can use the following command:

$ git describe --tags
v0.6

As long as we embed information we can also add the time it was built:

$ date -u
Thu Jul  2 23:51:55 UTC 2020

Embedding information into multi-git

Letโ€™s put everything together and embed the git tag and build time into multi-git.

First, letโ€™s define the GitTag and Timestamp variables in main.go:

var (
	GitTag    string
	Timestamp string
)

Then, if the variables are specified, weโ€™ll print them out:

func main() {
	if GitTag != "" {
		fmt.Printf("Git tag : %s\nBuilt at: %s\n\n", GitTag, Timestamp)
	}

	cmd.Execute()
}

To embed the git tag and the build timestamp, we can use the following build command:

$ go build -ldflags "-X 'main.GitTag=$(git describe --tags)' -X 'main.Timestamp=$(date -u)'"

Here is the result:

$ ./multi-git
Git tag : v0.6
Built at: Fri Jul  3 00:20:51 UTC 2020

Error: accepts 1 arg(s), received 0
Usage:
  multi-git [flags]

Flags:
      --config string   config file path (default is $HOME/multi-git.toml) (default "/Users/gigi.sayfan/.config/multi-git.toml")
  -h, --help            help for multi-git
      --ignore-errors   will continue executing the command for all repos if ignore-errors is true
                                        otherwise it will stop execution when an error occurs

2020/07/02 17:20:58 accepts 1 arg(s), received 0

The git tag and the build time are displayed first before any input. If multi-git had a version command or flag, I would probably incorporate this information in the version output.

For convenience, we can write the build command to its own file:

#!/usr/bin/env zsh

go build -ldflags "-X 'main.GitTag=$(git describe --tags)' -X 'main.Timestamp=$(date -u)'"

Letโ€™s commit our changes and try again:

(๐Ÿ™)/multi-git/
$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	new file:   build.sh
	modified:   main.go

Untracked files not listed (use -u option to show untracked files)
(๐Ÿ™)/multi-git/
$ git add .
(๐Ÿ™)/multi-git/
$ git commit -m "embed git tag and build time + display at runtime"
[master 385da0a] embed git tag and build time + display at runtime
 2 files changed, 15 insertions(+)
 create mode 100755 build.sh

Now, letโ€™s build using the shell script and run again:

(๐Ÿ™)/multi-git/
$ ./build.sh
(๐Ÿ™)/multi-git/
$ ./multi-git
Git tag : v0.6-1-g385da0a
Built at: Fri Jul  3 00:28:06 UTC 2020

Error: accepts 1 arg(s), received 0
Usage:
  multi-git [flags]

Flags:
      --config string   config file path (default is $HOME/multi-git.toml) (default "/Users/gigi.sayfan/.config/multi-git.toml")
  -h, --help            help for multi-git
      --ignore-errors   will continue executing the command for all repos if ignore-errors is true
                                        otherwise it will stop execution when an error occurs

2020/07/02 17:28:12 accepts 1 arg(s), received 0

As you can see, the tag has changed. Since the last commit wasnโ€™t tagged, git describe --tags returns the most recent tag (v0.6), followed by the number of commits since this tag (1), followed by a short SHA algorithm of the most recent commit. The result is a human-readable description of the current version of the code and its relation to any tags.

Conclusion

Go has sophisticated tooling as a first-class citizen of the language. You can cross-compile, integrate with C, build self-contained executables with all their dependencies and embed data in your executables. The go build command provides a lot of knobs to control the build process. In this lesson, we embedded the git tag and built a timestamp into multi-git. I suggest you study the various flags of the go build command because you may find some hidden treasures.

Quiz

Get hands-on with 1400+ tech skills courses.