cmd/bench: compute distribution size

Compute the distribution size as part of cmd/bench.

Fixes golang/go#68706

Change-Id: I73e22c53f6341fcce130a80817265b9e6a6a6964
Reviewed-on: https://go-review.googlesource.com/c/benchmarks/+/758120
Reviewed-by: Michael Matloob <matloob@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
Auto-Submit: Michael Matloob <matloob@google.com>
diff --git a/cmd/bench/distsize.go b/cmd/bench/distsize.go
new file mode 100644
index 0000000..cc5a621
--- /dev/null
+++ b/cmd/bench/distsize.go
@@ -0,0 +1,83 @@
+package main
+
+import (
+	"fmt"
+	"go/version"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"runtime"
+	"strings"
+)
+
+func distsizes(tcs []*toolchain) error {
+	tmpdir, err := os.MkdirTemp("", "go-distsize")
+	if err != nil {
+		return err
+	}
+	defer os.RemoveAll(tmpdir)
+
+	for _, tc := range tcs {
+		fmt.Printf("toolchain: %s\n", tc.Name)
+		if err := distsize(tmpdir, tc); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func distsize(tmpdir string, tc *toolchain) error {
+	goroot := filepath.Join(tmpdir, tc.Name)
+	if err := os.Mkdir(goroot, 0o777); err != nil {
+		return err
+	}
+	defer os.RemoveAll(goroot)
+
+	// tc.GOROOT() is the GOROOT we are measuring. We're going to make a copy
+	// of it from which we'll run make.(bash|bat|rc) -distpack. That will keep us from
+	// modifying the original GOROOT. We will run distpack from h.goroot.
+	if err := os.CopyFS(goroot, os.DirFS(tc.GOROOT())); err != nil {
+		return fmt.Errorf("error copying GOROOT: %v", err)
+	}
+	goversion, err := tc.Go.Version()
+	if err != nil {
+		return err
+	}
+	goversion, _, _ = strings.Cut(goversion, " ") // remove date
+	if !version.IsValid(goversion) {
+		return fmt.Errorf("could not parse go version: %v", goversion)
+	}
+	versionFile := filepath.Join(goroot, "VERSION")
+	if err := os.WriteFile(versionFile, []byte(goversion+"\n"), 0666); err != nil {
+		return err
+	}
+
+	// Run make.(bash|bat|rc) -distpack in the temp GOROOT to produce the zip.
+	var makeScript string
+	switch runtime.GOOS {
+	case "windows":
+		makeScript = "make.bat"
+	case "plan9":
+		makeScript = "make.rc"
+	default:
+		makeScript = "make.bash"
+	}
+	cmd := exec.Command(filepath.Join(goroot, "src", makeScript), "-distpack")
+	cmd.Dir = filepath.Join(goroot, "src")
+	cmd.Env = tc.Env.MustSet("GOROOT_BOOTSTRAP=" + tc.GOROOT()).Collapse()
+	if err := cmd.Run(); err != nil {
+		return fmt.Errorf("error running %s -distpack: %v", makeScript, err)
+	}
+
+	zipBaseName := fmt.Sprintf("v0.0.1-%s.%s-%s.zip", goversion, runtime.GOOS, runtime.GOARCH)
+	zipFile := filepath.Join(goroot, "pkg", "distpack", zipBaseName)
+	fi, err := os.Stat(zipFile)
+	if err != nil {
+		return fmt.Errorf("could not find module distribution zip file: %v", err)
+	}
+	size := fi.Size()
+
+	fmt.Println("Unit total-bytes assume=exact")
+	fmt.Printf("BenchmarkGoDistribution 1 %d total-bytes\n", size)
+	return nil
+}
diff --git a/cmd/bench/main.go b/cmd/bench/main.go
index cdb9b7c..8508ff9 100644
--- a/cmd/bench/main.go
+++ b/cmd/bench/main.go
@@ -76,6 +76,10 @@
 		pass = false
 		log.Printf("Error running Go tests: %v", err)
 	}
+	if err := distsizes(tcs); err != nil {
+		pass = false
+		log.Printf("Error determining distribution size: %v", err)
+	}
 	if err := bent(tcs, pgo); err != nil {
 		pass = false
 		log.Printf("Error running bent: %v", err)
diff --git a/sweet/common/gotool.go b/sweet/common/gotool.go
index c78b456..086f439 100644
--- a/sweet/common/gotool.go
+++ b/sweet/common/gotool.go
@@ -5,6 +5,7 @@
 package common
 
 import (
+	"bytes"
 	"fmt"
 	"io"
 	"os"
@@ -78,14 +79,14 @@
 }
 
 func (g *Go) Version() (string, error) {
-	cmd := exec.Command(g.Tool, "version")
+	cmd := exec.Command(g.Tool, "env", "GOVERSION")
 	cmd.Env = g.Env.Collapse()
 	log.TraceCommand(cmd, false)
 	out, err := cmd.Output()
 	if err != nil {
-		return "", fmt.Errorf("error running 'go version': %w", err)
+		return "", fmt.Errorf("error running 'go env GOVERSION': %w", err)
 	}
-	return string(out), nil
+	return string(bytes.TrimSpace(out)), nil
 }
 
 func (g *Go) BuildPath(path, out string, args ...string) error {
diff --git a/sweet/harnesses/cockroachdb.go b/sweet/harnesses/cockroachdb.go
index 85de788..8e51990 100644
--- a/sweet/harnesses/cockroachdb.go
+++ b/sweet/harnesses/cockroachdb.go
@@ -6,10 +6,10 @@
 
 import (
 	"fmt"
+	"go/version"
 	"os/exec"
 	"path/filepath"
 	"runtime"
-	"strings"
 	"time"
 
 	"golang.org/x/benchmarks/sweet/common"
@@ -120,10 +120,10 @@
 		return fmt.Errorf("getting go version for toolchain: %v", err)
 	}
 	var goBuildArgs []string
-	if v := strings.TrimPrefix(ver, "go version "); strings.HasPrefix(v, "devel ") || v >= "go1.23" {
+	if version.Compare(ver, "go1.23") >= 0 {
 		goBuildArgs = append(goBuildArgs, "-ldflags=-checklinkname=0")
 	}
-	if v := strings.TrimPrefix(ver, "go version "); strings.HasPrefix(v, "devel ") || v >= "go1.24" {
+	if version.Compare(ver, "go1.24") >= 0 {
 		goBuildArgs = append(goBuildArgs, "-tags=untested_go_version")
 	}
 	if err := cfg.GoTool(bcfg.BuildLog).BuildPath(filepath.Join(bcfg.SrcDir, "pkg/cmd/cockroach-short"), bcfg.BinDir, goBuildArgs...); err != nil {