package binfo import ( "fmt" "runtime/debug" "strconv" "strings" "time" "github.com/hashicorp/go-multierror" ) type Binfo struct { // Module information. Module struct { // The module path, e.g. "golang.org/x/tools/cmd/stringer". Path string // The version of this module. Version string // The checksum of this module. Sum string } // Information about the build process and target. Build struct { // The version of the Go toolchain used to build the binary, e.g. "go1.19.2". GoVersion string // The buildmode flag value, e.g. "exe". Mode string // The compiler toolchain, e.g. "gc" or "gccgo". Compiler string // The architecture target, e.g. "amd64". Arch string // The operating system target, e.g. "linux". OS string } // Information about CGO. CGO struct { Enabled bool Flags struct { C string CPP string CXX string LD string } } // Information about version control at the time of building. VCS struct { // The name of the version control system, e.g. "git". Name string // The current revision, e.g. "6cb6d5fa113f26aa2bc139539eab8939632f0693". Revision string // The modification time for the current revision. Time time.Time // Whether or not the source tree had local modifications. Modified bool } // The original data source for build information. Orig *debug.BuildInfo } func Get() (Binfo, error) { var merr *multierror.Error b := Binfo{} if o, ok := debug.ReadBuildInfo(); ok { b.Orig = o b.Module.Version = o.Main.Version b.Module.Path = o.Main.Path b.Module.Sum = o.Main.Sum for _, setting := range o.Settings { switch setting.Key { case "-buildmode": b.Build.Mode = setting.Value case "-compiler": b.Build.Compiler = setting.Value case "GOARCH": b.Build.Arch = setting.Value case "GOOS": b.Build.OS = setting.Value case "CGO_ENABLED": switch setting.Value { case "1": b.CGO.Enabled = true case "0": b.CGO.Enabled = false default: merr = multierror.Append(merr, fmt.Errorf("failed to parse %s", setting.Key)) } case "CGO_CFLAGS": b.CGO.Flags.C = setting.Value case "CGO_CPPFLAGS": b.CGO.Flags.CPP = setting.Value case "CGO_CXXFLAGS": b.CGO.Flags.CXX = setting.Value case "CGO_LDFLAGS": b.CGO.Flags.LD = setting.Value case "vcs": b.VCS.Name = setting.Value case "vcs.revision": b.VCS.Revision = setting.Value case "vcs.time": v, err := time.Parse(time.RFC3339, setting.Value) if err != nil { merr = multierror.Append(merr, fmt.Errorf("unable to parse VCS time: %w", err)) } b.VCS.Time = v case "vcs.modified": v, err := strconv.ParseBool(setting.Value) if err != nil { merr = multierror.Append(merr, fmt.Errorf("unable to parse VCS modified: %w", err)) } b.VCS.Modified = v } } } return b, merr.ErrorOrNil() } type SummaryMode uint const ( ModeModule SummaryMode = 1 << iota ModeBuild ModeCGO ModeVCS ModeMultiline ModeNamed ) func (b Binfo) Summarize(program string, mode SummaryMode) string { wants := func(test SummaryMode) bool { return mode&test == test } sep := ", " if wants(ModeMultiline) { sep = "\n" } parts := make([]string, 4) if wants(ModeModule) { parts = append( parts, fmt.Sprintf("module %s (%s) (sum %s)", b.Module.Path, b.Module.Version, b.Module.Sum), ) } if wants(ModeBuild) { parts = append( parts, fmt.Sprintf("built with %s (%s) (mode %s)", b.Build.Compiler, b.Build.GoVersion, b.Build.Mode), ) } if wants(ModeCGO) { if b.CGO.Enabled { parts = append( parts, fmt.Sprintf("with cgo (c %q) (cpp %q) (cxx %q) (ld %q)", b.CGO.Flags.C, b.CGO.Flags.CPP, b.CGO.Flags.CXX, b.CGO.Flags.LD), ) } else { parts = append( parts, "without cgo", ) } } if wants(ModeVCS) { parts = append( parts, fmt.Sprintf("via %s (rev %s) (at %s)", b.VCS.Name, b.VCS.Revision, b.VCS.Time.Format("2006-01-02 15:04:05")), ) } j := strings.Join(parts, sep) if program == "" { return j } else { return fmt.Sprintf("%s:%s%s", program, sep, j) } }