blob: 60f8de1b802ffe24b11d489989b295ad1a0ac426 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// 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 in the out/ build directory
// using sources from third_party/go.
//
// When the GOROOT is first built, this script also calls make.bash.
// On subsquent modifications to source files in third_party/go, the
// source files are copied into the out/ directory but the toolchain
// is not rebuilt as the content-based hashing in the Go tool will
// rebuild any stale packages.
package main
import (
"bytes"
"crypto/sha256"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
)
var (
flagFuchsiaGoroot = flag.String("fuchsia-goroot", "", "Path to Fuchsia's source GOROOT")
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")
flagOutGoroot = flag.String("out-goroot", "", "Path to the output GOROOT")
)
func relGenDir() string {
return (*flagRootGenDir)[len(*flagRootOutDir)+1:]
}
func mkdir(path ...string) {
if err := os.MkdirAll(join(path...), 0755); err != nil {
log.Fatal(err)
}
}
func join(path ...string) string { return filepath.Join(path...) }
func rm(path string) {
if err := os.RemoveAll(path); err != nil {
log.Fatal(err)
}
}
func symlink(src, dst string) {
if err := os.Symlink(src, dst); err != nil {
log.Fatal(err)
}
}
func hashfile(path string) string {
h := sha256.New()
f, err := os.Open(path)
if err != nil {
if os.IsNotExist(err) {
return "does_not_exist(" + path + ")"
}
log.Fatal(err)
}
defer f.Close()
if _, err := io.Copy(h, f); err != nil {
log.Fatalf("hash %s: %v", path, err)
}
return string(h.Sum(nil))
}
var didSomething = false
// copyfile copies src to dst if the two files have different contents.
func copyfile(src, dst string) {
if hashfile(src) == hashfile(dst) {
return
}
didSomething = true
rm(dst)
srcfile, err := os.Open(src)
if err != nil {
log.Fatal(err)
}
defer srcfile.Close()
f, err := os.Create(dst)
if err != nil {
log.Fatal(err)
}
if _, err := io.Copy(f, srcfile); err != nil {
log.Fatalf("cp %s %s: %v", src, dst, err)
}
if err := f.Close(); err != nil {
log.Fatalf("cp %s %s: close: %v", src, dst, err)
}
fi, err := os.Stat(src)
if err != nil {
log.Fatal(err)
}
if err := os.Chmod(dst, fi.Mode()); err != nil {
log.Fatal(err)
}
}
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
}
fmt.Fprintf(w, " %s", path)
return err
}
srcGoroot := join(*flagFuchsiaGoroot, "src")
return filepath.Walk(srcGoroot, fn)
}
func genDepfileStdlib() {
// Change anything under src/*, and we copy those changes into out/.
path := *flagDepfile
if path == "" {
return
}
os.Remove(path)
buf := new(bytes.Buffer)
buf.WriteString(relGenDir() + "/goroot.done:")
if err := collectDeps(buf); err != nil {
log.Fatal("gen depfile stdlib: %v", err)
}
depbytes := buf.Bytes()
h := sha256.New()
h.Write(depbytes)
newhash := string(h.Sum(nil))
if hashfile(path) == newhash {
return // don't overwrite an accurate file
}
if err := ioutil.WriteFile(path, depbytes, 0664); err != nil {
log.Fatal("gen depfile stdlib: %v", err)
}
}
func convCPU(cpu string) (arch string) {
switch cpu {
case "x64":
return "amd64"
case "arm64":
return "arm64"
}
return ""
}
func main() {
log.SetFlags(0)
log.SetPrefix("makefuchsia.go: ")
flag.Parse()
if *flagRootGenDir == "" {
log.Fatal("must specify -root-gen-dir")
}
if *flagFuchsiaGoroot == "" {
log.Fatal("must specify -fuchsia-goroot when building toolchain")
}
goarchTarget := convCPU(*flagTargetCPU)
if goarchTarget == "" {
log.Fatalf("unsupported target CPU: %s", *flagTargetCPU)
}
genDepfileStdlib()
srcGoroot := *flagFuchsiaGoroot
dstGoroot := *flagOutGoroot
src := join(srcGoroot, "src")
dst := join(dstGoroot, "src")
mkdir(dst)
mkdir(join(dstGoroot, "misc"))
mkdir(join(dstGoroot, "pkg"))
mkdir(join(dstGoroot, "bin"))
mkdir(join(dstGoroot, "lib"))
mkdir(join(dstGoroot, "misc/fuchsia"))
miscFiles := []string{
"clangwrap.sh",
"gccwrap.sh",
"go_stdlib_tests.json",
}
for _, file := range miscFiles {
copyfile(join(srcGoroot, "misc/fuchsia", file), join(dstGoroot, "misc/fuchsia", file))
}
copyfile(join(srcGoroot, "VERSION.cache"), join(dstGoroot, "VERSION.cache"))
copyfile(join(srcGoroot, "VERSION.cache"), join(dstGoroot, "VERSION"))
srcFiles, err := filepath.Glob(join(src, "*"))
if err != nil {
log.Fatalf("could not glob source files: %v", err)
}
var wg sync.WaitGroup
for _, file := range srcFiles {
file := file
deepCopy := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if strings.HasSuffix(path, "_test.go") && !strings.Contains(path, "os/") {
return nil
}
if filepath.Base(path) == "testdata" {
return filepath.SkipDir
}
if info.IsDir() {
return nil
}
dstPath := join(dst, path[len(src)+1:])
mkdir(filepath.Dir(dstPath))
copyfile(path, dstPath)
return nil
}
wg.Add(1)
go func() {
if err := filepath.Walk(file, deepCopy); err != nil {
log.Fatalf("goroot deep copy failed: %v", err)
}
wg.Done()
}()
}
wg.Wait()
if _, err := os.Stat(join(dstGoroot, "bin", "go")); err != nil {
env := os.Environ()
env = append(env, "CC_FOR_fuchsia_"+goarchTarget+"="+dstGoroot+"/misc/fuchsia/gccwrap.sh")
// Fresh copy, we need to build the compiler.
// On subsequent copies, we can rely on "go build" to
// rebuild any standard library changes.
cmd := exec.Command(join(dst, "make.bash"))
cmd.Dir = dst
cmd.Env = env
buf := new(bytes.Buffer)
cmd.Stdout = buf
cmd.Stderr = buf
if err := cmd.Run(); err != nil {
log.Fatalf("make.bash failed: %v\n%s", err, buf.String())
}
}
if didSomething {
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)
}
}
}