Modify Variables In Go Binary During Build
You can set the variabel values during build, such as version number and more with the Build command.
by Percy Bolmér, March 18, 2022
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.
The 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 –help option.
go build --ldflags="--help"
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.
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.
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 main.go.
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
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.
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 on any of my social media.
If you enjoyed my writing, please support future articles by buying me an Coffee