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 <jayconrod@google.com>
Run-TryBot: Jay Conrod <jayconrod@google.com>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
diff --git a/api/next.txt b/api/next.txt
index 1192fc9..cb729ea 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 a3a8de8..a7428ed 100644
--- a/src/cmd/go/internal/load/pkg.go
+++ b/src/cmd/go/internal/load/pkg.go
@@ -21,6 +21,7 @@
 	pathpkg "path"
 	"path/filepath"
 	"runtime"
+	"runtime/debug"
 	"sort"
 	"strconv"
 	"strings"
@@ -1921,9 +1922,8 @@
 	}
 	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 @@
 	}
 }
 
+// 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 8a97920..da50743 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 @@
 	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 05cad61..11f995b 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 @@
 	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