From 765c9116be44641854f580c19e3589d7b86a3d28 Mon Sep 17 00:00:00 2001 From: Jay Conrod Date: Thu, 30 Sep 2021 14:37:30 -0700 Subject: [PATCH] cmd/go: move module build info formatting into runtime/debug Previously, modload.PackageBuildInfo returned a string containing information about modules used to build an executable. This string is embedded in the binary and can be read with debug.ReadBuildInfo or 'go version -m'. With this change, debug.BuildInfo now has a MarshalText method that returns a string in the same format as modload.PackageBuildInfo. Package.load now calls Package.setBuildInfo, which constructs a debug.BuildInfo, formats it with MarshalText, then sets Package.Internal.BuildInfo. This is equivalent to what modload.PackageBuildInfo did. modload.PackageBuildInfo is deleted, since it's no longer used. For #37475 Change-Id: I5875a98cb64737637fec2a450ab2ffa7f1805707 Reviewed-on: https://go-review.googlesource.com/c/go/+/353886 Trust: Jay Conrod Run-TryBot: Jay Conrod Reviewed-by: Bryan C. Mills --- api/next.txt | 1 + src/cmd/go/internal/load/pkg.go | 83 +++++++++++++++++++++++++++- src/cmd/go/internal/modload/build.go | 48 ---------------- src/runtime/debug/mod.go | 37 +++++++++++++ 4 files changed, 118 insertions(+), 51 deletions(-) diff --git a/api/next.txt b/api/next.txt index 1192fc90696..cb729ea72f9 100644 --- a/api/next.txt +++ b/api/next.txt @@ -1,3 +1,4 @@ +pkg runtime/debug, method (*BuildInfo) MarshalText() ([]byte, error) pkg syscall (darwin-amd64), func RecvfromInet4(int, []uint8, int, *SockaddrInet4) (int, error) pkg syscall (darwin-amd64), func RecvfromInet6(int, []uint8, int, *SockaddrInet6) (int, error) pkg syscall (darwin-amd64), func SendtoInet4(int, []uint8, int, SockaddrInet4) error diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go index a3a8de81c92..a7428ed4201 100644 --- a/src/cmd/go/internal/load/pkg.go +++ b/src/cmd/go/internal/load/pkg.go @@ -21,6 +21,7 @@ import ( pathpkg "path" "path/filepath" "runtime" + "runtime/debug" "sort" "strconv" "strings" @@ -1921,9 +1922,8 @@ func (p *Package) load(ctx context.Context, opts PackageOpts, path string, stk * } p.Internal.Imports = imports p.collectDeps() - - if cfg.ModulesEnabled && p.Error == nil && p.Name == "main" && len(p.DepsErrors) == 0 { - p.Internal.BuildInfo = modload.PackageBuildInfo(pkgPath, p.Deps) + if p.Error == nil && p.Name == "main" && len(p.DepsErrors) == 0 { + p.setBuildInfo() } // unsafe is a fake package. @@ -2195,6 +2195,83 @@ func (p *Package) collectDeps() { } } +// setBuildInfo gathers build information, formats it as a string to be +// embedded in the binary, then sets p.Internal.BuildInfo to that string. +// setBuildInfo should only be called on a main package with no errors. +// +// This information can be retrieved using debug.ReadBuildInfo. +func (p *Package) setBuildInfo() { + setPkgErrorf := func(format string, args ...interface{}) { + if p.Error == nil { + p.Error = &PackageError{Err: fmt.Errorf(format, args...)} + } + } + + var debugModFromModinfo func(*modinfo.ModulePublic) *debug.Module + debugModFromModinfo = func(mi *modinfo.ModulePublic) *debug.Module { + dm := &debug.Module{ + Path: mi.Path, + Version: mi.Version, + } + if mi.Replace != nil { + dm.Replace = debugModFromModinfo(mi.Replace) + } else { + dm.Sum = modfetch.Sum(module.Version{Path: mi.Path, Version: mi.Version}) + } + return dm + } + + var main debug.Module + if p.Module != nil { + main = *debugModFromModinfo(p.Module) + } + + visited := make(map[*Package]bool) + mdeps := make(map[module.Version]*debug.Module) + var q []*Package + q = append(q, p.Internal.Imports...) + for len(q) > 0 { + p1 := q[0] + q = q[1:] + if visited[p1] { + continue + } + visited[p1] = true + if p1.Module != nil { + m := module.Version{Path: p1.Module.Path, Version: p1.Module.Version} + if p1.Module.Path != main.Path && mdeps[m] == nil { + mdeps[m] = debugModFromModinfo(p1.Module) + } + } + q = append(q, p1.Internal.Imports...) + } + sortedMods := make([]module.Version, 0, len(mdeps)) + for mod := range mdeps { + sortedMods = append(sortedMods, mod) + } + module.Sort(sortedMods) + deps := make([]*debug.Module, len(sortedMods)) + for i, mod := range sortedMods { + deps[i] = mdeps[mod] + } + + pkgPath := p.ImportPath + if p.Internal.CmdlineFiles { + pkgPath = "command-line-arguments" + } + info := &debug.BuildInfo{ + Path: pkgPath, + Main: main, + Deps: deps, + } + text, err := info.MarshalText() + if err != nil { + setPkgErrorf("error formatting build info: %v", err) + return + } + p.Internal.BuildInfo = string(text) +} + // SafeArg reports whether arg is a "safe" command-line argument, // meaning that when it appears in a command-line, it probably // doesn't have some special meaning other than its own name. diff --git a/src/cmd/go/internal/modload/build.go b/src/cmd/go/internal/modload/build.go index 8a9792089b9..da507431389 100644 --- a/src/cmd/go/internal/modload/build.go +++ b/src/cmd/go/internal/modload/build.go @@ -5,7 +5,6 @@ package modload import ( - "bytes" "context" "encoding/hex" "errors" @@ -336,53 +335,6 @@ func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode Li return info } -// PackageBuildInfo returns a string containing module version information -// for modules providing packages named by path and deps. path and deps must -// name packages that were resolved successfully with LoadPackages. -func PackageBuildInfo(path string, deps []string) string { - if !Enabled() { - return "" - } - target, _ := findModule(loaded, path) - mdeps := make(map[module.Version]bool) - for _, dep := range deps { - if m, ok := findModule(loaded, dep); ok { - mdeps[m] = true - } - } - var mods []module.Version - delete(mdeps, target) - for mod := range mdeps { - mods = append(mods, mod) - } - module.Sort(mods) - - var buf bytes.Buffer - fmt.Fprintf(&buf, "path\t%s\n", path) - - writeEntry := func(token string, m module.Version) { - mv := m.Version - if mv == "" { - mv = "(devel)" - } - fmt.Fprintf(&buf, "%s\t%s\t%s", token, m.Path, mv) - if r, _ := Replacement(m); r.Path == "" { - fmt.Fprintf(&buf, "\t%s\n", modfetch.Sum(m)) - } else { - fmt.Fprintf(&buf, "\n=>\t%s\t%s\t%s\n", r.Path, r.Version, modfetch.Sum(r)) - } - } - - if target.Path != "" { - writeEntry("mod", target) - } - for _, mod := range mods { - writeEntry("dep", mod) - } - - return buf.String() -} - // findModule searches for the module that contains the package at path. // If the package was loaded, its containing module and true are returned. // Otherwise, module.Version{} and false are returned. diff --git a/src/runtime/debug/mod.go b/src/runtime/debug/mod.go index 05cad61155c..11f995ba758 100644 --- a/src/runtime/debug/mod.go +++ b/src/runtime/debug/mod.go @@ -5,6 +5,8 @@ package debug import ( + "bytes" + "fmt" "strings" ) @@ -34,6 +36,41 @@ type Module struct { Replace *Module // replaced by this module } +func (bi *BuildInfo) MarshalText() ([]byte, error) { + buf := &bytes.Buffer{} + if bi.Path != "" { + fmt.Fprintf(buf, "path\t%s\n", bi.Path) + } + var formatMod func(string, Module) + formatMod = func(word string, m Module) { + buf.WriteString(word) + buf.WriteByte('\t') + buf.WriteString(m.Path) + mv := m.Version + if mv == "" { + mv = "(devel)" + } + buf.WriteByte('\t') + buf.WriteString(mv) + if m.Replace == nil { + buf.WriteByte('\t') + buf.WriteString(m.Sum) + } else { + buf.WriteByte('\n') + formatMod("=>", *m.Replace) + } + buf.WriteByte('\n') + } + if bi.Main.Path != "" { + formatMod("mod", bi.Main) + } + for _, dep := range bi.Deps { + formatMod("dep", *dep) + } + + return buf.Bytes(), nil +} + func readBuildInfo(data string) (*BuildInfo, bool) { if len(data) < 32 { return nil, false