cmd/coordinator, internal/buildgo: add RunMake

This moves RunMake from coordinator.go, allowing other packages to
build Go on a buildlet.

Updates golang/go#19871

Change-Id: Ic0e7c056020ef434f0620ae816f52b9112ea1c8d
Reviewed-on: https://go-review.googlesource.com/44176
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/cmd/coordinator/benchmarks.go b/cmd/coordinator/benchmarks.go
index eae63ba..dddb207 100644
--- a/cmd/coordinator/benchmarks.go
+++ b/cmd/coordinator/benchmarks.go
@@ -242,7 +242,7 @@
 	return
 }
 
-func (st *buildStatus) buildRev(sl spanlog.Logger, conf dashboard.BuildConfig, bc *buildlet.Client, w io.Writer, goroot string, br buildgo.BuilderRev) error {
+func buildRev(sl spanlog.Logger, conf dashboard.BuildConfig, bc *buildlet.Client, w io.Writer, goroot string, br buildgo.BuilderRev) error {
 	if br.SnapshotExists(context.TODO(), buildEnv) {
 		return bc.PutTarFromURL(br.SnapshotURL(buildEnv), goroot)
 	}
@@ -256,7 +256,13 @@
 	if err := bc.PutTar(srcTar, goroot); err != nil {
 		return err
 	}
-	remoteErr, err := st.runMake(bc, goroot, w)
+	builder := buildgo.GoBuilder{
+		Logger:     sl,
+		BuilderRev: br,
+		Conf:       conf,
+		Goroot:     goroot,
+	}
+	remoteErr, err := builder.RunMake(bc, w)
 	if err != nil {
 		return err
 	}
@@ -274,7 +280,7 @@
 			return nil, err
 		}
 		sp := st.CreateSpan("bench_build_parent", bc.Name())
-		err = st.buildRev(st, st.conf, bc, w, "go-parent", pbr)
+		err = buildRev(st, st.conf, bc, w, "go-parent", pbr)
 		sp.Done(err)
 		if err != nil {
 			return nil, err
diff --git a/cmd/coordinator/coordinator.go b/cmd/coordinator/coordinator.go
index a8c22c7..092b738 100644
--- a/cmd/coordinator/coordinator.go
+++ b/cmd/coordinator/coordinator.go
@@ -1698,7 +1698,13 @@
 	st.getHelpersReadySoon()
 
 	if !st.useSnapshot() {
-		remoteErr, err = st.runMake(st.bc, "go", st)
+		builder := buildgo.GoBuilder{
+			Logger:     st,
+			BuilderRev: st.BuilderRev,
+			Conf:       st.conf,
+			Goroot:     "go",
+		}
+		remoteErr, err = builder.RunMake(st.bc, st)
 		if err != nil {
 			return nil, err
 		}
@@ -1816,82 +1822,6 @@
 	return nil
 }
 
-// runMake builds the tool chain.
-// goroot is relative to the workdir with forward slashes.
-// w is the Writer to send build output to.
-// remoteErr and err are as described at the top of this file.
-func (st *buildStatus) runMake(bc *buildlet.Client, goroot string, w io.Writer) (remoteErr, err error) {
-	// Build the source code.
-	makeSpan := st.CreateSpan("make", st.conf.MakeScript())
-	remoteErr, err = bc.Exec(path.Join(goroot, st.conf.MakeScript()), buildlet.ExecOpts{
-		Output:   w,
-		ExtraEnv: append(st.conf.Env(), "GOBIN="),
-		Debug:    true,
-		Args:     st.conf.MakeScriptArgs(),
-	})
-	if err != nil {
-		makeSpan.Done(err)
-		return nil, err
-	}
-	if remoteErr != nil {
-		makeSpan.Done(remoteErr)
-		return fmt.Errorf("make script failed: %v", remoteErr), nil
-	}
-	makeSpan.Done(nil)
-
-	// Need to run "go install -race std" before the snapshot + tests.
-	if pkgs := st.conf.GoInstallRacePackages(); len(pkgs) > 0 {
-		sp := st.CreateSpan("install_race_std")
-		remoteErr, err = bc.Exec(path.Join(goroot, "bin/go"), buildlet.ExecOpts{
-			Output:   w,
-			ExtraEnv: append(st.conf.Env(), "GOBIN="),
-			Debug:    true,
-			Args:     append([]string{"install", "-race"}, pkgs...),
-		})
-		if err != nil {
-			sp.Done(err)
-			return nil, err
-		}
-		if remoteErr != nil {
-			sp.Done(err)
-			return fmt.Errorf("go install -race std failed: %v", remoteErr), nil
-		}
-		sp.Done(nil)
-	}
-
-	if st.Name == "linux-amd64-racecompile" {
-		return st.runConcurrentGoBuildStdCmd(bc)
-	}
-
-	return nil, nil
-}
-
-// runConcurrentGoBuildStdCmd is a step specific only to the
-// "linux-amd64-racecompile" builder to exercise the Go 1.9's new
-// concurrent compilation. It re-builds the standard library and tools
-// with -gcflags=-c=8 using a race-enabled cmd/compile (built by
-// caller, runMake, per builder config).
-// The idea is that this might find data races in cmd/compile.
-func (st *buildStatus) runConcurrentGoBuildStdCmd(bc *buildlet.Client) (remoteErr, err error) {
-	span := st.CreateSpan("go_build_c128_std_cmd")
-	remoteErr, err = bc.Exec("go/bin/go", buildlet.ExecOpts{
-		Output:   st,
-		ExtraEnv: append(st.conf.Env(), "GOBIN="),
-		Debug:    true,
-		Args:     []string{"build", "-a", "-gcflags=-c=8", "std", "cmd"},
-	})
-	if err != nil {
-		span.Done(err)
-		return nil, err
-	}
-	if remoteErr != nil {
-		span.Done(remoteErr)
-		return fmt.Errorf("go build failed: %v", remoteErr), nil
-	}
-	span.Done(nil)
-	return nil, nil
-}
-
 // runAllLegacy executes all.bash (or .bat, or whatever) in the traditional way.
 // remoteErr and err are as described at the top of this file.
 //
diff --git a/internal/buildgo/buildgo.go b/internal/buildgo/buildgo.go
index 41d591f..bf1ae43 100644
--- a/internal/buildgo/buildgo.go
+++ b/internal/buildgo/buildgo.go
@@ -9,11 +9,16 @@
 import (
 	"context"
 	"fmt"
+	"io"
 	"log"
 	"net/http"
+	"path"
 	"time"
 
 	"golang.org/x/build/buildenv"
+	"golang.org/x/build/buildlet"
+	"golang.org/x/build/cmd/coordinator/spanlog"
+	"golang.org/x/build/dashboard"
 )
 
 // BuilderRev is a build configuration type and a revision.
@@ -73,3 +78,88 @@
 	}
 	return res.StatusCode == http.StatusOK
 }
+
+// A GoBuilder knows how to build a revision of Go with the given configuration.
+type GoBuilder struct {
+	spanlog.Logger
+	BuilderRev
+	Conf dashboard.BuildConfig
+	// Goroot is a Unix-style path relative to the work directory of the builder (e.g. "go").
+	Goroot string
+}
+
+// RunMake builds the tool chain.
+// goroot is relative to the workdir with forward slashes.
+// w is the Writer to send build output to.
+// remoteErr and err are as described at the top of this file.
+func (gb GoBuilder) RunMake(bc *buildlet.Client, w io.Writer) (remoteErr, err error) {
+	// Build the source code.
+	makeSpan := gb.CreateSpan("make", gb.Conf.MakeScript())
+	remoteErr, err = bc.Exec(path.Join(gb.Goroot, gb.Conf.MakeScript()), buildlet.ExecOpts{
+		Output:   w,
+		ExtraEnv: append(gb.Conf.Env(), "GOBIN="),
+		Debug:    true,
+		Args:     gb.Conf.MakeScriptArgs(),
+	})
+	if err != nil {
+		makeSpan.Done(err)
+		return nil, err
+	}
+	if remoteErr != nil {
+		makeSpan.Done(remoteErr)
+		return fmt.Errorf("make script failed: %v", remoteErr), nil
+	}
+	makeSpan.Done(nil)
+
+	// Need to run "go install -race std" before the snapshot + tests.
+	if pkgs := gb.Conf.GoInstallRacePackages(); len(pkgs) > 0 {
+		sp := gb.CreateSpan("install_race_std")
+		remoteErr, err = bc.Exec(path.Join(gb.Goroot, "bin/go"), buildlet.ExecOpts{
+			Output:   w,
+			ExtraEnv: append(gb.Conf.Env(), "GOBIN="),
+			Debug:    true,
+			Args:     append([]string{"install", "-race"}, pkgs...),
+		})
+		if err != nil {
+			sp.Done(err)
+			return nil, err
+		}
+		if remoteErr != nil {
+			sp.Done(err)
+			return fmt.Errorf("go install -race std failed: %v", remoteErr), nil
+		}
+		sp.Done(nil)
+	}
+
+	if gb.Name == "linux-amd64-racecompile" {
+		return gb.runConcurrentGoBuildStdCmd(bc, w)
+	}
+
+	return nil, nil
+}
+
+// runConcurrentGoBuildStdCmd is a step specific only to the
+// "linux-amd64-racecompile" builder to exercise the Go 1.9's new
+// concurrent compilation. It re-builds the standard library and tools
+// with -gcflags=-c=8 using a race-enabled cmd/compile (built by
+// caller, runMake, per builder config).
+// The idea is that this might find data races in cmd/compile.
+func (gb GoBuilder) runConcurrentGoBuildStdCmd(bc *buildlet.Client, w io.Writer) (remoteErr, err error) {
+	span := gb.CreateSpan("go_build_c128_std_cmd")
+	remoteErr, err = bc.Exec(path.Join(gb.Goroot, "bin/go"), buildlet.ExecOpts{
+		Output:   w,
+		ExtraEnv: append(gb.Conf.Env(), "GOBIN="),
+		Debug:    true,
+		Args:     []string{"build", "-a", "-gcflags=-c=8", "std", "cmd"},
+	})
+	if err != nil {
+		span.Done(err)
+		return nil, err
+	}
+	if remoteErr != nil {
+		span.Done(remoteErr)
+		return fmt.Errorf("go build failed: %v", remoteErr), nil
+	}
+	span.Done(nil)
+	return nil, nil
+}