diff --git a/buildinfo.go b/buildinfo.go new file mode 100644 index 0000000..9f92e87 --- /dev/null +++ b/buildinfo.go @@ -0,0 +1,101 @@ +package main + +import ( + "crypto/sha256" + "encoding/base64" + "encoding/binary" + "fmt" + "io" + "runtime/debug" + "sort" + "strconv" + "time" +) + +var version = "dev" // has to be set by ldflags + +const ( + buildInfoModuleStart byte = iota + buildInfoModuleFinish + buildInfoModuleDelimeter +) + +func getVersion() string { + goVersion, date, commit, modulesChecksum, dirty := getVersionData() + + dirtySuffix := "" + if dirty { + dirtySuffix = " [dirty]" + } + + return fmt.Sprintf("%s (%s: %s on %s%s, modules checksum %s)", + version, + goVersion, + date.Format(time.RFC3339), + commit, + dirtySuffix, + modulesChecksum) +} + +func getVersionData() (goVersion string, date time.Time, commit string, modulesChecksum string, dirty bool) { + date = time.Now() + + buildInfo, ok := debug.ReadBuildInfo() + if !ok { + return + } + + goVersion = buildInfo.GoVersion + + for _, setting := range buildInfo.Settings { + switch setting.Key { + case "vcs.time": + date, _ = time.Parse(time.RFC3339, setting.Value) + case "vcs.revision": + commit = setting.Value + case "vcs.modified": + dirty, _ = strconv.ParseBool(setting.Value) + } + } + + hasher := sha256.New() + if _, err := io.WriteString(hasher, buildInfo.Path); err != nil { + panic(err) + } + binary.Write(hasher, binary.LittleEndian, uint64(1+len(buildInfo.Deps))) + + sort.Slice(buildInfo.Deps, func(i, j int) bool { + return buildInfo.Deps[i].Path > buildInfo.Deps[j].Path + }) + + buildInfoCheckSumModule(hasher, &buildInfo.Main) + for _, module := range buildInfo.Deps { + buildInfoCheckSumModule(hasher, module) + } + + modulesChecksum = base64.StdEncoding.EncodeToString(hasher.Sum(nil)) + + return +} + +func buildInfoCheckSumModule(w io.Writer, module *debug.Module) { + w.Write([]byte{buildInfoModuleStart}) + + if _, err := io.WriteString(w, module.Path); err != nil { + panic(err) + } + + w.Write([]byte{buildInfoModuleDelimeter}) + + if _, err := io.WriteString(w, module.Version); err != nil { + panic(err) + } + + w.Write([]byte{buildInfoModuleDelimeter}) + + if _, err := io.WriteString(w, module.Sum); err != nil { + panic(err) + } + + w.Write([]byte{buildInfoModuleFinish}) +} diff --git a/main.go b/main.go index dd795c7..fc1e756 100644 --- a/main.go +++ b/main.go @@ -9,10 +9,7 @@ package main import ( - "fmt" "math/rand" - "runtime/debug" - "strconv" "time" "github.com/9seconds/mtg/v2/internal/cli" @@ -20,8 +17,6 @@ import ( "github.com/alecthomas/kong" ) -var version = "dev" // has to be set by ldflags - func main() { rand.Seed(time.Now().UTC().UnixNano()) @@ -29,35 +24,9 @@ func main() { panic(err) } - if buildInfo, ok := debug.ReadBuildInfo(); ok { - vcsCommit := "" - vcsDate := time.Now() - vcsDirty := "" - - for _, setting := range buildInfo.Settings { - switch setting.Key { - case "vcs.time": - vcsDate, _ = time.Parse(time.RFC3339, setting.Value) - case "vcs.revision": - vcsCommit = setting.Value - case "vcs.modified": - if isDirty, _ := strconv.ParseBool(setting.Value); isDirty { - vcsDirty = " [dirty]" - } - } - } - - version = fmt.Sprintf("%s (%s: %s on %s%s)", - version, - buildInfo.GoVersion, - vcsDate.Format(time.RFC3339), - vcsCommit, - vcsDirty) - } - cli := &cli.CLI{} ctx := kong.Parse(cli, kong.Vars{ - "version": version, + "version": getVersion(), }) ctx.FatalIfErrorf(ctx.Run(cli, version))