GoBroke/version/funcs.go
2024-12-17 17:39:10 -05:00

145 lines
3.8 KiB
Go

package version
import (
"fmt"
"strconv"
"strings"
"time"
"golang.org/x/mod/semver"
)
// Version returns the build information. See build.sh.
func Version() (b *BuildInfo, err error) {
var n int
var s string
var sb strings.Builder
var ok bool
var canonical string
var build strings.Builder
// Why a map?
// I forget but I had a reason for it once upon a time.
var raw map[string]string = map[string]string{
"sourceControl": sourceControl,
"tag": version,
"hash": commitHash,
"shortHash": commitShort,
"postTagCommits": numCommitsAfterTag,
"dirty": isDirty,
"time": buildTime,
"user": buildUser,
"sudoUser": buildSudoUser,
"host": buildHost,
}
var i BuildInfo = BuildInfo{
SourceControl: raw["sourceControl"],
TagVersion: raw["tag"],
// PostTagCommits: 0,
CommitHash: raw["hash"],
CommitId: raw["shortHash"],
BuildUser: raw["user"],
RealBuildUser: raw["sudoUser"],
// BuildTime: time.Time{},
BuildHost: raw["host"],
Dirty: false,
isDefined: false,
raw: raw,
}
if s, ok = raw["postTagCommits"]; ok && strings.TrimSpace(s) != "" {
if n, err = strconv.Atoi(s); err == nil {
i.PostTagCommits = uint(n)
}
}
if s, ok = raw["time"]; ok && strings.TrimSpace(s) != "" {
if n, err = strconv.Atoi(s); err == nil {
i.BuildTime = time.Unix(int64(n), 0).UTC()
}
}
switch strings.ToLower(raw["dirty"]) {
case "1":
i.Dirty = true
case "0", "":
i.Dirty = false
}
// Build the short form. We use this for both BuildInfo.short and BuildInfo.verSplit.
if i.TagVersion == "" {
sb.WriteString(i.SourceControl)
} else {
sb.WriteString(i.TagVersion)
}
/*
Now the mess. In order to conform to SemVer 2.0 (the spec this code targets):
1.) MAJOR.
2.) MINOR.
3.) PATCH
4.) -PRERELEASE (OPTIONAL)
(git commit, if building against a commit made past 1-3. Always included if untagged.)
5.) +BUILDINFO (OPTIONAL)
("+x[.y]", where x is # of commits past 4, or tag commit if 4 is empty. 0 is valid.
y is optional, and is the string "dirty" if it is a "dirty" build - that is, uncommitted/unstaged changes.
if x and y would be 0 and empty, respectively, then 5 is not included.)
1-3 are already written, or the source control software used if not.
Technically 4 and 5 are only included if 3 is present. We force patch to 0 if it's a tagged release and patch isn't present --
so this is not relevant.
*/
// PRERELEASE
if i.TagVersion == "" || i.PostTagCommits > 0 {
// We use the full commit hash for git versions, short identifier for tagged releases.
if i.TagVersion == "" {
i.Pre = i.CommitHash
} else {
i.Pre = i.CommitId
}
sb.WriteString(fmt.Sprintf("-%v", i.Pre))
}
// BUILD
if i.PostTagCommits > 0 || i.Dirty {
build.WriteString(strconv.Itoa(int(i.PostTagCommits)))
if i.Dirty {
build.WriteString(".dirty")
}
i.Build = build.String()
sb.WriteString(fmt.Sprintf("+%v", i.Build))
}
i.short = sb.String()
if semver.IsValid(i.short) {
// DON'T DO THIS. It strips the prerelease and build info.
// i.short = semver.Canonical(i.short)
// Do this instead.
canonical = semver.Canonical(i.short)
// Numeric versions...
if n, err = strconv.Atoi(strings.TrimPrefix(semver.Major(canonical), "v")); err != nil {
err = nil
} else {
i.Major = uint(n)
}
if n, err = strconv.Atoi(strings.Split(semver.MajorMinor(canonical), ".")[1]); err != nil {
err = nil
} else {
i.Minor = uint(n)
}
if n, err = strconv.Atoi(patchReIsolated.FindStringSubmatch(strings.Split(canonical, ".")[2])[1]); err != nil {
err = nil
} else {
i.Patch = uint(n)
}
// The other tag assignments were performed above.
}
// The default is 0 for the numerics, so no big deal.
i.isDefined = true
b = &i
return
}