Skip to content

imjasonh/version

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

version

Simply get the version information from Go BuildInfo.

Usage:

import "github.com/imjasonh/version"

func main() {
    fmt.Println(version.Get())
}

This code is not maintained, and I will not accept PRs to add features. Fork it if you want it. It's like 50 lines, just copy it into your codebase and go to town.

Please stop using ldflags to embed this information.


Rant

Since the early days of Go, if you wanted to embed something like the Git commit that was built in your binary, you had to do something janky like

package main

const commit = "unknown"

and

go build -ldflags="-X 'main.commit=$(git rev-parse HEAD)'"

This was kinda gross, but it worked, so people did it. And boy did they do it. This little trick got cargo-culted all over the place! Anecdotally, basically any Go application you use has its version embedded this way.

Don't believe me? Here's about 76,000 hits on GitHub for ldflags and golang in Makefiles. Not all of these are embedding version information in ldflags, but most of them are.

Goreleaser also documents support for this, which has another five thousand hits.

And what's worse, they're all doing the same thing in a very non-standard way, not just writing main.commit, but all kinds of stuff like

And, because ldflags doesn't care if those values are set, and doesn't care if the value you set doesn't exist, a lot of people think they're doing something when they're not.

Aaaanyway, sometimes folks also wanted a build date embedded, and okay, sure, we can do that too:

go build -ldflags="-X 'main.buildDate=$(date +%Y-%m-%dT%H:%M:%SZ)'"

Boom, done. Except...

That's going to cause your build to be non-reproducible. If you build that right now, wait ten seconds, and build it again, you'll get a new binary. You probably don't care that they were built 10 seconds apart from the same source. What you wanted was

go build -ldflags="-X 'main.buildDate=$(date -d@$SOURCE_DATE_EPOCH +%Y-%m-%dT%H:%M:%SZ)'"

and some sane static value of SOURCE_DATE_EPOCH to make that reproducible. Most folks would opt for the date of the commit that it's built from as a sane approach.

As an aside, k8s.io/client-go had a fun bug where the way they were embedding the abbreviated Git commit would sometimes make the dependency non-reproducible, leading to non-reproducible builds for anything that depended on that package, of which there are ...many.

But you don't need to do any of this nonsense at all, so stop!

Since Go 1.12, back in 2019, the Go compiler has embedded this information for you, without you needing to know what an ldflag is. You can just call debug.ReadBuildInfo and it'll pop right out, along with a bunch of other stuff. In Go 1.24 (Feb 2025), more VCS information was made available.

The vcs.revision and vcs.time are exactly the same as git rev-parse HEAD and "the date of that commit". So stop mucking with ldflags, and especially stop embedding a non-reproducible time. You're working too hard.

This package demonstrates embedding vcs.revision, vcs.time and vcs.clean (whether there are non-committed changes), and the main package version, and encapsulates it in version.Get.

If you go run ./cmd/example there will be no embedded version info. If you go build and then run it, you'll get version info. If you go install github.com/imjasonh/version/cmd/example@latest you'll get the specific tag, for the latest semver tag.


If you want to change anything about the behavior, copy the code and go for it. I wrapped it in a sync.OnceFunc so it didn't have to read the info each time, in case you call it multiple times. You're welcome.

About

Stop using ldflags to embed build information

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages