makefuchsia.go: use prebuilt compiler

Just rebuild cmd/go and cmd/cgo with new defaults.

Pulls 60s out of the build process for reduced networking builds.

Change-Id: Idd8e42c35e8f549e64b0fd1f07941e1759fb5cfe
diff --git a/BUILD.gn b/BUILD.gn
index 91ae57b..fefb1f5 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -5,27 +5,42 @@
 # The go_runtime action wraps go_toolchain, rebuilding just the standard
 # if it has changed.
 action("go_runtime") {
-  outputs = [ "$root_gen_dir/goroot/stdlib.done" ]
+  outputs = [ "$root_gen_dir/gostdlib.done" ]
   depfile = "$root_gen_dir/gostdlib.d"
   script = rebase_path("//buildtools/go")
   args = [
     "run",
     rebase_path("makestdlib.go", root_build_dir),
+    "-fuchsia-root",
+    rebase_path("//."),
+    "-depfile",
+    rebase_path(depfile),
     "-root-out-dir",
     rebase_path("$root_out_dir"),
     "-root-gen-dir",
     rebase_path("$root_gen_dir"),
-    "-depfile",
-    rebase_path(depfile),
+    "-target-cpu",
+    target_cpu,
   ]
   deps = [ ":go_toolchain" ]
+
+  if (host_os == "linux") {
+    args += [
+      "--bootstrap-path",
+      rebase_path("//buildtools/linux64/go"),
+    ]
+  } else if (host_os == "mac") {
+    args += [
+      "--bootstrap-path",
+      rebase_path("//buildtools/mac/go"),
+    ]
+  } else {
+    assert(false, "Go cross compiler currently only supported on Linux and Mac")
+  }
 }
 
 action("go_toolchain") {
-  outputs = [ "$root_gen_dir/goroot/bin/go" ]
-  depfile_path = "$root_gen_dir/goroot.d"
-
-  depfile = depfile_path
+  outputs = [ "$root_gen_dir/goroot.done" ]
 
   script = rebase_path("//buildtools/go")
   args = [
@@ -33,18 +48,8 @@
     rebase_path("makefuchsia.go", root_build_dir),
     "-fuchsia-root",
     rebase_path("//."),
-    "-root-out-dir",
-    rebase_path("$root_out_dir"),
     "-root-gen-dir",
     rebase_path("$root_gen_dir"),
-    "-depfile",
-    rebase_path(depfile_path),
-    "-host-os",
-    host_os,
-    "-host-cpu",
-    host_cpu,
-    "-target-cpu",
-    target_cpu,
   ]
 
   if (host_os == "linux") {
@@ -61,3 +66,7 @@
     assert(false, "Go cross compiler currently only supported on Linux and Mac")
   }
 }
+
+pool("gobuild_pool") {
+  depth = 1
+}
diff --git a/makefuchsia.go b/makefuchsia.go
index eb0510b..581bc6a 100644
--- a/makefuchsia.go
+++ b/makefuchsia.go
@@ -2,19 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// The makefuchsia.go script builds a GOROOT for cross compiling Fuchsia
-// binaries from the host. It is used as part of the GN+ninja build.
+// The makefuchsia.go script symlinks the src contents from third_party/go
+// into the active GOROOT in ./buildtools/{ARCH}/go.
 package main
 
 import (
-	"bytes"
 	"flag"
-	"fmt"
-	"io"
 	"io/ioutil"
 	"log"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"strings"
 	"sync"
@@ -22,149 +18,28 @@
 
 var (
 	flagFuchsiaRoot   = flag.String("fuchsia-root", "", "Path to root of the Fuchsia project")
-	flagDepfile       = flag.String("depfile", "", "Toolchain depfile")
-	flagRootOutDir    = flag.String("root-out-dir", "", "GN variable")
 	flagRootGenDir    = flag.String("root-gen-dir", "", "GN variable")
-	flagHostOS        = flag.String("host-os", "", "GN variable")
-	flagHostCPU       = flag.String("host-cpu", "", "GN variable")
-	flagTargetCPU     = flag.String("target-cpu", "", "GN variable")
 	flagBootstrapPath = flag.String("bootstrap-path", "", "Path to bootstrap Go installation")
 )
 
-func genDepfileToolchain() {
-	// Change any of the src/cmd/* files, and we rebuild the entire toolchain.
-	// This is not perfect. The toolchain does depend on other stdlib packages.
-	// But in practice it works well, changes to the rest of the stdlib don't
-	// have big semantic effects on the compiler/linker/assembler, because
-	// these programs are designed to be built with old versions of the stdlib
-	// for bootstrapping.
-	path := *flagDepfile
-	if path == "" {
-		return
-	}
-	os.Remove(path)
-	buf := new(bytes.Buffer)
-	buf.WriteString(relGenDir() + "/goroot/bin/go:")
-	if err := collectDeps(buf, "goroot/src/cmd"); err != nil {
-		log.Fatal("gen depfile toolchain: %v", err)
-	}
-	if err := ioutil.WriteFile(path, buf.Bytes(), 0664); err != nil {
-		log.Fatal("gen depfile toolchain: %v", err)
-	}
-}
-
-func relGenDir() string {
-	return (*flagRootGenDir)[len(*flagRootOutDir)+1:]
-}
-
-func collectDeps(w io.Writer, path string) error {
-	fn := func(path string, info os.FileInfo, err error) error {
-		if info.IsDir() || strings.HasSuffix(path, "_test.go") {
-			return err
-		}
-		fmt.Fprintf(w, " %s", path)
-		return err
-	}
-	return filepath.Walk(filepath.Join(*flagRootGenDir, path), fn)
-}
-
-func genGoroot() {
-	if *flagFuchsiaRoot == "" {
-		log.Fatal("must specify -fuchsia-root when building toolchain")
-	}
-
-	// Setup a minimal GOROOT in a target-specific generated files
-	// directory. This ensures that the ninja build for a target
-	// does not touch the source directories, an important invariant
-	// for building against multiple architectures.
-	dstGoroot := filepath.Join(*flagRootGenDir, "goroot")
-	if err := os.RemoveAll(dstGoroot); err != nil {
+func mkdir(path ...string) {
+	if err := os.MkdirAll(join(path...), 0755); err != nil {
 		log.Fatal(err)
 	}
-	dst := filepath.Join(dstGoroot, "src")
-	if err := os.MkdirAll(dst, 0755); err != nil {
-		log.Fatal(err)
-	}
-	if err := os.MkdirAll(filepath.Join(dstGoroot, "misc"), 0755); err != nil {
-		log.Fatal(err)
-	}
-	if err := os.Symlink(filepath.Join(*flagFuchsiaRoot, "third_party/go/misc/fuchsia"), filepath.Join(dstGoroot, "misc/fuchsia")); err != nil {
-		log.Fatalf("goroot misc symlink failed: %v", err)
-	}
-	src := filepath.Join(*flagFuchsiaRoot, "third_party/go/src")
-	cmd := exec.Command("git", "log", "-n", "1", "--format=format:fuchsia:%h %cd", "HEAD")
-	cmd.Dir = src
-	verData, err := cmd.Output()
-	if err != nil {
-		log.Fatalf("could not get third_party/go git version: %v", err)
-	}
-	if err := ioutil.WriteFile(filepath.Join(dstGoroot, "VERSION"), verData, 0664); err != nil {
-		log.Fatalf("could not write VERSION: %v", err)
-	}
-
-	// The make.bash script adds files to the packages:
-	//
-	//	src/cmd
-	//	src/go/build
-	//	src/runtime
-	//
-	// So we deep symlink these subtrees in the minimal GOROOT.
-	// For the rest of the packages, symlinking the top-level
-	// directories is enough.
-	deepSymlink := map[string]bool{"cmd": true, "go": true, "runtime": true}
-	srcFiles, err := filepath.Glob(filepath.Join(src, "*"))
-	if err != nil {
-		log.Fatalf("could not glob source files: %v", err)
-	}
-
-	var wg sync.WaitGroup
-	for _, file := range srcFiles {
-		base := filepath.Base(file)
-		if !deepSymlink[base] {
-			//log.Printf("shallow link %s (base=%s)", file, base)
-			if err := os.Symlink(file, filepath.Join(dst, base)); err != nil {
-				log.Fatalf("goroot symlink failed: %v", err)
-			}
-			continue
-		}
-		file := file
-		deepSymlink := func(path string, info os.FileInfo, err error) error {
-			if err != nil {
-				return err
-			}
-			if strings.HasSuffix(path, "_test.go") {
-				return nil
-			}
-			if filepath.Base(path) == "testdata" {
-				return filepath.SkipDir
-			}
-			if info.IsDir() {
-				return nil
-			}
-			dstPath := filepath.Join(dst, path[len(src)+1:])
-			os.MkdirAll(filepath.Dir(dstPath), 0775)
-			//log.Printf("deepSymlink path=%s -> dst=%s", path, dstPath)
-			return os.Symlink(path, dstPath)
-		}
-		wg.Add(1)
-		go func() {
-			if err := filepath.Walk(file, deepSymlink); err != nil {
-				log.Fatalf("goroot deep symlink failed: %v", err)
-			}
-			wg.Done()
-		}()
-	}
-	wg.Wait()
 }
 
-func convCPU(cpu string) (arch string) {
-	switch cpu {
-	case "x64":
-		return "amd64"
-	case "arm64":
-		return "arm64"
+func join(path ...string) string { return filepath.Join(path...) }
+
+func rm(path string) {
+	if err := os.RemoveAll(path); err != nil {
+		log.Fatal(err)
 	}
-	return ""
+}
+
+func symlink(src, dst string) {
+	if err := os.Symlink(src, dst); err != nil {
+		log.Fatal(err)
+	}
 }
 
 func main() {
@@ -176,46 +51,98 @@
 		log.Fatal("must specify -root-gen-dir")
 	}
 
-	genGoroot()
-	genDepfileToolchain()
-
-	var gohostos string
-	switch *flagHostOS {
-	case "linux":
-		gohostos = "linux"
-	case "mac":
-		gohostos = "darwin"
-	default:
-		log.Fatalf("unsupported host OS: %s", *flagHostOS)
+	if *flagFuchsiaRoot == "" {
+		log.Fatal("must specify -fuchsia-root when building toolchain")
 	}
 
-	gohostarch := convCPU(*flagHostCPU)
-	if gohostarch == "" {
-		log.Fatalf("unsupported host CPU: %s", *flagHostCPU)
+	srcGoroot := join(*flagFuchsiaRoot, "third_party/go")
+	dstGoroot := *flagBootstrapPath
+	src := join(srcGoroot, "src")
+	dst := join(dstGoroot, "src")
+
+	// The make.bash script adds these files to the tree.
+	// We make a copy of them now, blow away the src tree,
+	// shallow symlink what we can, and deep-symlink any directory
+	// containing one of these generated files.
+	genFiles := []string{
+		"cmd/cgo/zdefaultcc.go",
+		"cmd/go/zdefaultcc.go",
+		"cmd/go/zosarch.go",
+		"cmd/internal/obj/zbootstrap.go",
+		"go/build/zcgo.go",
+		"runtime/internal/sys/zversion.go",
+	}
+	deepSymlink := map[string]bool{}
+	for _, file := range genFiles {
+		deepSymlink[file[:strings.Index(file, "/")]] = true
+	}
+	genFilesContents := map[string][]byte{}
+	for _, file := range genFiles {
+		b, err := ioutil.ReadFile(join(dstGoroot, "src", file))
+		if err != nil {
+			log.Fatal(err)
+		}
+		genFilesContents[file] = b
 	}
 
-	goarch := convCPU(*flagTargetCPU)
-	if gohostarch == "" {
-		log.Fatalf("unsupported target CPU: %s", *flagTargetCPU)
-	}
+	rm(dst)
+	mkdir(dst)
+	rm(join(dstGoroot, "misc/fuchsia"))
+	symlink(join(srcGoroot, "misc/fuchsia"), join(dstGoroot, "misc/fuchsia"))
 
-	cmd := exec.Command(filepath.Join(*flagRootGenDir, "goroot/src/make.bash"))
-	cmd.Dir = filepath.Join(*flagRootGenDir, "goroot/src")
-	cmd.Env = []string{
-		"PATH=" + os.Getenv("PATH"),
-		"GOROOT_BOOTSTRAP=" + *flagBootstrapPath,
-		"CGO_ENABLED=1",
-		"FUCHSIA=" + *flagFuchsiaRoot,
-		"MAGENTA=" + filepath.Join(*flagFuchsiaRoot, "magenta"),
-		"CC_FOR_TARGET=" + filepath.Join(*flagRootGenDir, "goroot/misc/fuchsia/gccwrap.sh"),
-		"GOHOSTOS=" + gohostos,
-		"GOHOSTARCH=" + gohostarch,
-		"GOOS=fuchsia",
-		"GOARCH=" + goarch,
-	}
-	out, err := cmd.CombinedOutput()
+	srcFiles, err := filepath.Glob(join(src, "*"))
 	if err != nil {
-		log.Printf("make.bash failed: %s", string(out))
-		os.Exit(1)
+		log.Fatalf("could not glob source files: %v", err)
+	}
+
+	var wg sync.WaitGroup
+	for _, file := range srcFiles {
+		base := filepath.Base(file)
+		if !deepSymlink[base] {
+			symlink(file, join(dst, base))
+			continue
+		}
+		file := file
+		deepSymlink := func(path string, info os.FileInfo, err error) error {
+			if err != nil {
+				return err
+			}
+			if strings.HasSuffix(path, "_test.go") {
+				return nil
+			}
+			if strings.HasSuffix(path, "go/zdefaultcc.go") {
+				return nil // created below
+			}
+			if filepath.Base(path) == "testdata" {
+				return filepath.SkipDir
+			}
+			if info.IsDir() {
+				return nil
+			}
+			dstPath := join(dst, path[len(src)+1:])
+			mkdir(filepath.Dir(dstPath))
+			symlink(path, dstPath)
+			return nil
+		}
+		wg.Add(1)
+		go func() {
+			if err := filepath.Walk(file, deepSymlink); err != nil {
+				log.Fatalf("goroot deep symlink failed: %v", err)
+			}
+			wg.Done()
+		}()
+	}
+	wg.Wait()
+
+	for file, contents := range genFilesContents {
+		if err := ioutil.WriteFile(join(dstGoroot, "src", file), contents, 0664); err != nil {
+			log.Fatal(err)
+		}
+	}
+
+	donePath := join(*flagRootGenDir, "goroot.done")
+	rm(donePath)
+	if err := ioutil.WriteFile(donePath, []byte("done"), 0664); err != nil {
+		log.Fatalf("writing goroot.done failed: %v", err)
 	}
 }
diff --git a/makestdlib.go b/makestdlib.go
index 767a8e1..2d4f9a7 100644
--- a/makestdlib.go
+++ b/makestdlib.go
@@ -17,12 +17,16 @@
 	"os/exec"
 	"path/filepath"
 	"strings"
+	"sync"
 )
 
 var (
-	flagDepfile    = flag.String("depfile", "", "Stdlib depfile")
-	flagRootOutDir = flag.String("root-out-dir", "", "GN variable")
-	flagRootGenDir = flag.String("root-gen-dir", "", "GN variable")
+	flagFuchsiaRoot   = flag.String("fuchsia-root", "", "Path to root of the Fuchsia project")
+	flagDepfile       = flag.String("depfile", "", "Stdlib depfile")
+	flagRootOutDir    = flag.String("root-out-dir", "", "GN variable")
+	flagRootGenDir    = flag.String("root-gen-dir", "", "GN variable")
+	flagTargetCPU     = flag.String("target-cpu", "", "GN variable")
+	flagBootstrapPath = flag.String("bootstrap-path", "", "Path to bootstrap Go installation")
 )
 
 func genDepfileStdlib() {
@@ -35,8 +39,8 @@
 	}
 	os.Remove(path)
 	buf := new(bytes.Buffer)
-	buf.WriteString(relGenDir() + "/goroot/stdlib.done:")
-	if err := collectDeps(buf, "goroot/src"); err != nil {
+	buf.WriteString(relGenDir() + "/gostdlib.done:")
+	if err := collectDeps(buf); err != nil {
 		log.Fatal("gen depfile stdlib: %v", err)
 	}
 	if err := ioutil.WriteFile(path, buf.Bytes(), 0664); err != nil {
@@ -48,7 +52,7 @@
 	return (*flagRootGenDir)[len(*flagRootOutDir)+1:]
 }
 
-func collectDeps(w io.Writer, path string) error {
+func collectDeps(w io.Writer) error {
 	fn := func(path string, info os.FileInfo, err error) error {
 		if info.IsDir() || strings.HasSuffix(path, "_test.go") {
 			return err
@@ -56,7 +60,17 @@
 		fmt.Fprintf(w, " %s", path)
 		return err
 	}
-	return filepath.Walk(filepath.Join(*flagRootGenDir, path), fn)
+	return filepath.Walk(filepath.Join(*flagBootstrapPath, "src"), fn)
+}
+
+func convCPU(cpu string) (arch string) {
+	switch cpu {
+	case "x64":
+		return "amd64"
+	case "arm64":
+		return "arm64"
+	}
+	return ""
 }
 
 func main() {
@@ -68,15 +82,56 @@
 		log.Fatal("must specify -root-gen-dir")
 	}
 
+	goarchTarget := convCPU(*flagTargetCPU)
+	if goarchTarget == "" {
+		log.Fatalf("unsupported target CPU: %s", *flagTargetCPU)
+	}
+
 	genDepfileStdlib()
 
-	cmd := exec.Command(filepath.Join(*flagRootGenDir, "goroot/bin/go"), "install", "std")
-	cmd.Stdout = os.Stdout
-	cmd.Stderr = os.Stderr
-	if err := cmd.Run(); err != nil {
-		log.Fatal("go install std failed")
-	}
-	donePath := filepath.Join(*flagRootGenDir, "goroot/stdlib.done")
+	var wg sync.WaitGroup
+
+	wg.Add(1)
+	go func() {
+		// Generate the stdlib for the host.
+		// It is changed by third_party/go srcs being symlinked into buildtools.
+		cmd := exec.Command(filepath.Join(*flagBootstrapPath, "bin/go"), "install", "std")
+		cmd.Env = []string{
+			"PATH=" + os.Getenv("PATH"),
+			"GOROOT=" + *flagBootstrapPath,
+		}
+		cmd.Stdout = os.Stdout
+		cmd.Stderr = os.Stderr
+		if err := cmd.Run(); err != nil {
+			log.Fatal("go install std for host failed")
+		}
+		wg.Done()
+	}()
+
+	wg.Add(1)
+	go func() {
+		// Now generate the stdlib for the Fuchsia target.
+		cmd := exec.Command(filepath.Join(*flagBootstrapPath, "bin/go"), "install", "std")
+		cmd.Env = []string{
+			"PATH=" + os.Getenv("PATH"),
+			"GOOS=fuchsia",
+			"GOARCH=" + goarchTarget,
+			"GOROOT=" + *flagBootstrapPath,
+			"CGO_ENABLED=1",
+			"CC=" + filepath.Join(*flagBootstrapPath, "misc/fuchsia/gccwrap.sh"),
+			"MAGENTA=" + filepath.Join(*flagFuchsiaRoot, "magenta"), // used by gccwrap.sh
+		}
+		cmd.Stdout = os.Stdout
+		cmd.Stderr = os.Stderr
+		if err := cmd.Run(); err != nil {
+			log.Fatal("go install std for target failed")
+		}
+		wg.Done()
+	}()
+
+	wg.Wait()
+
+	donePath := filepath.Join(*flagRootGenDir, "gostdlib.done")
 	os.Remove(donePath)
 	if err := ioutil.WriteFile(donePath, []byte("done"), 0664); err != nil {
 		log.Fatalf("writing done file failed: %v", err)