// +build tools package main import ( "bytes" "fmt" "go/format" "os" "os/exec" "regexp" "strconv" "strings" "text/template" "time" ) var exactVer *regexp.Regexp var gitVer *regexp.Regexp var verFile = "generated-version.go" func init() { // exactly vX.Y.Z (go-compatible semver) exactVer = regexp.MustCompile(`^v\d+\.\d+\.\d+$`) // vX.Y.Z-n-g0000000 git post-release, semver prerelease gitVer = regexp.MustCompile(`^(v\d+\.\d+)\.(\d+)-(\d+)-g`) } // Goal: Either use an exact version like v1.0.0 // or translate the git version like v1.0.0-4-g0000000 // to a semver like v1.0.1-pre4+g0000000 // Don't fail when git repo isn't available. func main() { desc := gitDesc() rev := gitRev() ver := semVer(desc) ts := time.Now().Format(time.RFC3339) v := struct { Timestamp string Version string GitRev string }{ Timestamp: ts, Version: ver, GitRev: rev, } // Create or overwrite the go file from template var buf bytes.Buffer if err := versionTpl.Execute(&buf, v); nil != err { panic(err) } // Format src, err := format.Source(buf.Bytes()) if nil != err { panic(err) } // Write to disk (in the Current Working Directory) f, err := os.Create(verFile) if nil != err { panic(err) } if _, err := f.Write(src); nil != err { panic(err) } if err := f.Close(); nil != err { panic(err) } } func gitDesc() string { args := strings.Split("git describe --tags --dirty --always", " ") cmd := exec.Command(args[0], args[1:]...) out, err := cmd.CombinedOutput() if nil != err { // Don't panic, just carry on out = []byte("v0.0.0-0-g0000000") } return strings.TrimSpace(string(out)) } func gitRev() string { args := strings.Split("git rev-parse HEAD", " ") cmd := exec.Command(args[0], args[1:]...) out, err := cmd.CombinedOutput() if nil != err { // Don't panic, just carry on out = []byte("00000000000000000") } return strings.TrimSpace(string(out)) } func semVer(desc string) string { var ver string if exactVer.MatchString(desc) { // v1.0.0 ver = desc } else if gitVer.MatchString(desc) { // ((v1.0).(0)-(1)) vers := gitVer.FindStringSubmatch(desc) patch, err := strconv.Atoi(vers[2]) if nil != err { panic(err) } // v1.0.1-pre1 ver = fmt.Sprintf("%s.%d-pre%s", vers[1], patch+1, vers[3]) } return ver } var versionTpl = template.Must(template.New("").Parse(`// Code generated by go generate; DO NOT EDIT. package main func init() { Timestamp = "{{ .Timestamp }}" Version = "{{ .Version }}" GitRev = "{{ .GitRev }}" } `))