Use git tags to add (GoReleaser-compatible) semver to your go package in under 150 lines of code.
Goals:
1. Use an exact `git tag` version, like v1.0.0, when clean
2. Translate the `git describe` version (v1.0.0-4-g0000000)
to semver (1.0.1-pre4+g0000000) in between releases
3. Note when `dirty` (and have build timestamp)
Fail gracefully when git repo isn't available.See https://pkg.go.dev/git.rootprojects.org/root/go-gitver/v2.
- You define the fallback version and version printing in
main.go:
//go:generate go run git.rootprojects.org/root/go-gitver/v2
package main
import (
"fmt"
"strings"
)
var (
commit = "0000000"
version = "0.0.0-pre0+0000000"
date = "0000-00-00T00:00:00+0000"
)
func main() {
if (len(os.Args) > 1 && "version" == strings.TrimLeft(os.Args[1], "-")) {
fmt.Printf("Foobar v%s (%s) %s\n", version, commit[:7], date)
}
// ...
}- You
go generateorgo run git.rootprojects.org/root/go-gitver/v2to generatexversion.go:
package main
func init() {
commit = "0921ed1e"
version = "1.1.2"
date = "2019-07-01T02:32:58-06:00"
}Generate an xversion.go file:
go run git.rootprojects.org/root/go-gitver/v2
cat xversion.go// Code generated by go generate; DO NOT EDIT.
package main
func init() {
commit = "6dace8255b52e123297a44629bc32c015add310a"
version = "1.1.4-pre2+g6dace82"
date = "2020-07-16T20:48:15-06:00"
}Note: The file is named xversion.go by default so that the
generated file's init() will come later, and thus take priority, over
most other files.
See go-gitvers self-generated version:
go run git.rootprojects.org/root/go-gitver/v2 version6dace8255b52e123297a44629bc32c015add310a
v1.1.4-pre2+g6dace82
2020-07-16T20:48:15-06:00Add this to the top of your main file, so that it runs with go generate:
//go:generate go run -mod=vendor git.rootprojects.org/root/go-gitver/v2Add a file that imports go-gitver (for versioning)
// +build tools
package example
import _ "git.rootprojects.org/root/go-gitver/v2"Change you build instructions to be something like this:
go mod vendor
go generate -mod=vendor ./...
go build -mod=vendor -o example cmd/example/*.goYou don't have to use -mod=vendor, but I highly recommend it (just go mod tidy; go mod vendor to start).
version print version and exit
--fail exit with non-zero status code on failure
--package <name> will set the package name
--outfile <name> will replace `xversion.go` with the given file pathENVs
# Alias for --fail
GITVER_FAIL=trueFor example:
//go:generate go run -mod=vendor git.rootprojects.org/root/go-gitver/v2 --failgo run -mod=vendor git.rootprojects.org/root/go-gitver/v2 versionSee examples/basic
- Create a
toolspackage in your project - Guard it against regular builds with
// +build tools - Include
_ "git.rootprojects.org/root/go-gitver/v2"in the imports - Declare
var commit, version, date stringin yourpackage main - Include
//go:generate go run -mod=vendor git.rootprojects.org/root/go-gitver/v2as well
tools/tools.go:
// +build tools
// This is a dummy package for build tooling
package tools
import (
_ "git.rootprojects.org/root/go-gitver/v2"
)main.go:
//go:generate go run git.rootprojects.org/root/go-gitver/v2 --fail
package main
import "fmt"
var (
commit = "0000000"
version = "0.0.0-pre0+0000000"
date = "0000-00-00T00:00:00+0000"
)
func main() {
fmt.Println(commit)
fmt.Println(version)
fmt.Println(date)
}If you're using go mod vendor (which I highly recommend that you do),
you'd modify the go:generate ever so slightly:
//go:generate go run -mod=vendor git.rootprojects.org/root/go-gitver/v2 --failThe only reason I didn't do that in the example is that I'd be included the repository in itself and that would be... weird.
import "git.rootprojects.org/root/go-gitver/v2" is a program, not an importable package
Having a tools package with a build tag that you don't use is a nice way to add exact
versions of a command package used for tooling to your go.mod with go mod tidy,
without getting the error above.
These are the commands that are used under the hood to produce the versions.
Shows the git tag + description. Assumes that you're using the semver format v1.0.0 for your base tags.
git describe --tags --dirty --always
# v1.0.0
# v1.0.0-1-g0000000
# v1.0.0-dirtyShow the commit date (when the commit made it into the current tree). Internally we use the current date when the working tree is dirty.
git show v1.0.0-1-g0000000 --format=%cd --date=format:%Y-%m-%dT%H:%M:%SZ%z --no-patch
# 2010-01-01T20:30:00Z-0600
# fatal: ambiguous argument 'v1.0.0-1-g0000000-dirty': unknown revision or path not in the working tree.Shows the most recent commit.
git rev-parse HEAD
# 0000000000000000000000000000000000000000package git.rootprojects.org/root/go-gitver/v2: cannot find package "." in:
/Users/me/go-example/vendor/git.rootprojects.org/root/go-gitver/v2
cmd/example/example.go:1: running "go": exit status 1You forgot to update deps and re-vendor:
go mod tidy
go mod vendor