Image by Percy Bolmér. Gopher by Takuya Ueda, Original Go Gopher by Renée French (CC BY 3.0)
Have you ever used hardcoded version numbers, or tried passing configurations to a binary that states the version of the software?
I have, and it works, but it is very error-prone. It is easy to forget to change that configuration etc or maintain the version across multiple merge requests.
I finally found a great solution, we can modify variables inside the binaries during the build time by adding build flags. This allows us to set up the CI/CD to pass the git commit as a version etc, or have different releases specify the runner ID based on the deployment.
Using this approach makes it easier to maintain and control the specific values since we can easily place them inside the CI/CD. We can do this by leveraging the Linker flags.
If you would rather watch this tutorial as a video, you can find it below.
Linker & Ldflags
go build tool allows us to pass options to the Linker, which is the component responsible for assembling the binary.
We can pass options to the Linker by using the
--ldflags flag to the build tool. There are very many options you can pass, but in this article, we will focus on only one of them.
--ldflags accepts a string of configurations, so the input will be enclosed by
You can view all the options by running the build with the
go build --ldflags="--help"
All the linker options available to use in the build tool
The option we are interested in using is the
-X option. This is the definition from the help print.
-X definition add string value definition of the form importpath.name=value
As you can see, we can use the
-X flag and target the variable we want to modify by using the import path (the path you use to import a package) and using
.variableName to target a certain variable using its name, and then the value we want.
Let us try this out with a simple example project.
-X, Setting The Variable Value
Begin by creating a new project, I use the module name
programmingpercy.tech/buildflags. This is important to note because it will later be used as an import path.
mkdir buildflags go mod init programmingpercy.tech/buildflags touch main.go
We will fill the
main.go with a simple function that prints the version and client name.
main.go - The simple program that prints the variabel values.
You can try viewing the output by building and running the program.
go build -o main && ./main // Outputs 2022/03/08 19:23:28 Starting runner client-1.0.0 version 0.0.1
Let us try rebuilding the binary, but this time we will use the
--ldflags and apply the
-X command to set the runner into
client-2.0.0. The package we work in is
main so that will be the import path.
go build -o main --ldflags="-X 'main.runner=client-2.0.0'"
Try running the program again, and view the output change the runner name.
./main // Outputs 2022/03/08 19:27:21 Starting runner client-2.0.0 version 0.0.1
You can modify multiple flags, all you need to do is add a new
-X flag inside the command.
go build -o main --ldflags="-X 'main.runner=client-2.0.0' -X 'main.version=0.0.2'"
You should try running the binary, no surprise, the version is also changed.
Even better, you can also target sub-packages if you want to. To try this, create a subfolder named
version, and add a file named
version.go which contains the
Version variable instead of
version/version.go- The subpackage that we will be modifying
Make sure we update the
main.go to use the amazing new version package.
I don’t actually recommend you having a version package, but you get the idea
main.go - The main package that now uses the build package.
Now we can try to rebuild the binary, and make sure to update the version number using the
-X flag. This time, you can pass the full import path, which is the module name.
go build -o main --ldflags="-X 'programmingpercy.tech/buildflags/version.Version=0.0.2' -X 'main.runner=client-1.0.1'"
Upon running that binary, you should see the values change.
Adding Git Commit Tag As Version
One cool thing is that now when we can modify values during the build, you can automate version handling by using the git commit, etc.
In the folder of the project, we will init a git repository and commit the current files so we can get a commit hash.
git init git add . git commit -m "test"
To print a short commit hash, you can use the following git command.
git rev-parse --short HEAD
We can simply inject that into the
-X flag, and set the value of that output.
go build -o main --ldflags="-X 'programmingpercy.tech/buildflags/version.Version=$(git rev-parse --short HEAD)' -X 'main.runner=client-1.0.1'"
Running the program with this binary for me now prints the following result
2022/03/08 20:54:35 Starting runner client-1.0.1 version f525917
The example we use is very simple, but you can do many things with this. I have seen versions being injected, feature flags, and more. One easy solution for feature flags that should differ between clients can be passing a true value for the enabled functions. This is probably the easiest feature flag solution to use, no need for frameworks to manage it.
The best place to have these flags is in the CI/CD according to me, to control different releases, versions, and client ids.
I hope you found this interesting, I found it very useful when I learned this.
Are you using ldflags already, if so, for what? And what are the use cases you can think of? I would love to hear from you regarding your thoughts about these flags.
If you like my writing, feel free to Buy Me Coffee.
Email : email@example.com
Website : https://programmingpercy.tech