bent: write out go.mod by hand and always pass go 1.21

Starting with Go 1.21, if a "too new" language version is discovered in
a go.mod, the go command will fail. This is a problem in bent, because
bent may use a newer dev toolchain to create a go.mod that can't be used
by older Go toolchains.

Since Go 1.21 is where this change happened (and older toolchains will
happily ignore "too new" language versions) use that as the default
minimum go.mod version and write out the go.mod directly.

Change-Id: If510968c62c5155eb692b5e03ffbd34afd8959d4
Reviewed-on: https://go-review.googlesource.com/c/benchmarks/+/521820
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
Reviewed-by: David Chase <drchase@google.com>
diff --git a/cmd/bent/bent.go b/cmd/bent/bent.go
index 1b17810..5565871 100644
--- a/cmd/bent/bent.go
+++ b/cmd/bent/bent.go
@@ -19,6 +19,7 @@
 	"os"
 	"os/exec"
 	"path"
+	"path/filepath"
 	"runtime"
 	"strconv"
 	"strings"
@@ -79,7 +80,8 @@
 var shuffle = 2             // Dimensionality of (build) shuffling; 0 = none, 1 = per-benchmark, configuration ordering, 2 = bench, config pairs, 3 = across repetitions.
 var haveRsync = true
 var reportBuildTime = true
-var experiment = false // Don't reset go.mod, for testing purposes
+var experiment = false    // Don't reset go.mod, for testing purposes
+var minGoVersion = "1.21" // This is the release the toolchain started caring about versions of Go that are too new.
 
 //go:embed scripts/*
 var scripts embed.FS
@@ -156,6 +158,8 @@
 
 	flag.Var(&verbose, "v", "print commands and other information (more -v = print more details)")
 
+	flag.StringVar(&minGoVersion, "m", minGoVersion, "minimum Go version across all toolchains used for benchmarking")
+
 	flag.Usage = func() {
 		fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
 		flag.PrintDefaults()
@@ -549,23 +553,30 @@
 				}
 			}
 			if getFiles {
-				cmd := exec.Command("go", "mod", "init", "build")
-				cmd.Env = DefaultEnv()
-				cmd.Dir = bench.BuildDir
-
+				goModPath := filepath.Join(bench.BuildDir, "go.mod")
+				f, err := os.Create(goModPath)
+				if err != nil {
+					fmt.Printf("Error creating go.mod: %v", err)
+					os.Exit(2)
+				}
+				goMod := fmt.Sprintf(goMod, minGoVersion)
+				_, err = fmt.Fprintln(f, goMod)
+				if err != nil {
+					fmt.Printf("Error writing go.mod: %v", err)
+					f.Close()
+					os.Exit(2)
+				}
+				if err := f.Close(); err != nil {
+					fmt.Printf("Error closing go.mod: %v", err)
+					os.Exit(2)
+				}
 				if verbose > 0 {
-					fmt.Println(asCommandLine(dirs.wd, cmd))
+					fmt.Printf("(cd %s; cat <<EOF > %s\n%s\nEOF)\n", bench.BuildDir, "go.mod", goMod)
 				} else {
 					fmt.Print(".")
 				}
-				_, err := cmd.Output()
-				if err != nil {
-					ee := err.(*exec.ExitError)
-					fmt.Printf("There was an error running 'go mod init', stderr = %s", ee.Stderr)
-					os.Exit(2)
-				}
 
-				cmd = exec.Command("go", "get", "-d", "-t", "-v", bench.Repo+bench.Version)
+				cmd := exec.Command("go", "get", "-d", "-t", "-v", bench.Repo+bench.Version)
 				cmd.Env = DefaultEnv()
 				cmd.Dir = bench.BuildDir
 
@@ -1080,6 +1091,11 @@
 	}
 }
 
+var goMod = `module build
+
+go %s
+`
+
 func escape(s string) string {
 	s = strings.Replace(s, "\\", "\\\\", -1)
 	s = strings.Replace(s, "'", "\\'", -1)