2013-07-31 21:23:51 -06:00
|
|
|
// Copyright 2011 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
2014-09-04 14:46:24 -06:00
|
|
|
"net"
|
|
|
|
"net/http"
|
2013-07-31 21:23:51 -06:00
|
|
|
"os"
|
2014-08-28 15:58:15 -06:00
|
|
|
"os/exec"
|
2013-07-31 21:23:51 -06:00
|
|
|
"path/filepath"
|
|
|
|
"regexp"
|
|
|
|
"runtime"
|
2014-05-13 01:00:11 -06:00
|
|
|
"strconv"
|
2013-07-31 21:23:51 -06:00
|
|
|
"strings"
|
|
|
|
"time"
|
2013-08-27 22:52:18 -06:00
|
|
|
|
2014-11-09 14:50:40 -07:00
|
|
|
"golang.org/x/tools/go/vcs"
|
2013-07-31 21:23:51 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2013-09-11 17:45:24 -06:00
|
|
|
codeProject = "go"
|
|
|
|
codePyScript = "misc/dashboard/googlecode_upload.py"
|
|
|
|
gofrontendImportPath = "code.google.com/p/gofrontend"
|
|
|
|
mkdirPerm = 0750
|
|
|
|
waitInterval = 30 * time.Second // time to wait before checking for new revs
|
|
|
|
pkgBuildInterval = 24 * time.Hour // rebuild packages every 24 hours
|
2013-07-31 21:23:51 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
type Builder struct {
|
|
|
|
goroot *Repo
|
|
|
|
name string
|
|
|
|
goos, goarch string
|
|
|
|
key string
|
2013-09-11 17:45:24 -06:00
|
|
|
env builderEnv
|
2014-05-13 01:00:11 -06:00
|
|
|
// Last benchmarking workpath. We reuse it, if do successive benchmarks on the same commit.
|
|
|
|
lastWorkpath string
|
2013-07-31 21:23:51 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
2014-12-03 16:43:53 -07:00
|
|
|
doBuild = flag.Bool("build", true, "Build and test packages")
|
|
|
|
doBench = flag.Bool("bench", false, "Run benchmarks")
|
|
|
|
buildroot = flag.String("buildroot", defaultBuildRoot(), "Directory under which to build")
|
|
|
|
dashboard = flag.String("dashboard", "https://build.golang.org/git", "Dashboard app base path")
|
|
|
|
buildRelease = flag.Bool("release", false, "Build and upload binary release archives")
|
|
|
|
buildRevision = flag.String("rev", "", "Build specified revision and exit")
|
|
|
|
buildCmd = flag.String("cmd", filepath.Join(".", allCmd), "Build command (specify relative to go/src/)")
|
|
|
|
buildTool = flag.String("tool", "go", "Tool to build.")
|
2014-12-08 22:49:59 -07:00
|
|
|
gcPath = flag.String("gcpath", "code.google.com/p/go", "Path to download gc from")
|
2014-12-03 16:43:53 -07:00
|
|
|
gccPath = flag.String("gccpath", "https://github.com/mirrors/gcc.git", "Path to download gcc from")
|
|
|
|
benchPath = flag.String("benchpath", "golang.org/x/benchmarks/bench", "Path to download benchmarks from")
|
|
|
|
failAll = flag.Bool("fail", false, "fail all builds")
|
|
|
|
parallel = flag.Bool("parallel", false, "Build multiple targets in parallel")
|
|
|
|
buildTimeout = flag.Duration("buildTimeout", 60*time.Minute, "Maximum time to wait for builds and tests")
|
|
|
|
cmdTimeout = flag.Duration("cmdTimeout", 10*time.Minute, "Maximum time to wait for an external command")
|
|
|
|
benchNum = flag.Int("benchnum", 5, "Run each benchmark that many times")
|
|
|
|
benchTime = flag.Duration("benchtime", 5*time.Second, "Benchmarking time for a single benchmark run")
|
|
|
|
benchMem = flag.Int("benchmem", 64, "Approx RSS value to aim at in benchmarks, in MB")
|
|
|
|
fileLock = flag.String("filelock", "", "File to lock around benchmaring (synchronizes several builders)")
|
|
|
|
verbose = flag.Bool("v", false, "verbose")
|
|
|
|
report = flag.Bool("report", true, "whether to report results to the dashboard")
|
2013-07-31 21:23:51 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
binaryTagRe = regexp.MustCompile(`^(release\.r|weekly\.)[0-9\-.]+`)
|
|
|
|
releaseRe = regexp.MustCompile(`^release\.r[0-9\-.]+`)
|
|
|
|
allCmd = "all" + suffix
|
2014-05-13 01:00:11 -06:00
|
|
|
makeCmd = "make" + suffix
|
2013-07-31 21:23:51 -06:00
|
|
|
raceCmd = "race" + suffix
|
|
|
|
cleanCmd = "clean" + suffix
|
|
|
|
suffix = defaultSuffix()
|
2014-05-13 01:00:11 -06:00
|
|
|
exeExt = defaultExeExt()
|
|
|
|
|
|
|
|
benchCPU = CpuList([]int{1})
|
|
|
|
benchAffinity = CpuList([]int{})
|
|
|
|
benchMutex *FileMutex // Isolates benchmarks from other activities
|
2013-07-31 21:23:51 -06:00
|
|
|
)
|
|
|
|
|
2014-05-13 01:00:11 -06:00
|
|
|
// CpuList is used as flag.Value for -benchcpu flag.
|
|
|
|
type CpuList []int
|
|
|
|
|
|
|
|
func (cl *CpuList) String() string {
|
|
|
|
str := ""
|
|
|
|
for _, cpu := range *cl {
|
|
|
|
if str == "" {
|
|
|
|
str = strconv.Itoa(cpu)
|
|
|
|
} else {
|
|
|
|
str += fmt.Sprintf(",%v", cpu)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return str
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cl *CpuList) Set(str string) error {
|
|
|
|
*cl = []int{}
|
|
|
|
for _, val := range strings.Split(str, ",") {
|
|
|
|
val = strings.TrimSpace(val)
|
|
|
|
if val == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
cpu, err := strconv.Atoi(val)
|
|
|
|
if err != nil || cpu <= 0 {
|
|
|
|
return fmt.Errorf("%v is a bad value for GOMAXPROCS", val)
|
|
|
|
}
|
|
|
|
*cl = append(*cl, cpu)
|
|
|
|
}
|
|
|
|
if len(*cl) == 0 {
|
|
|
|
*cl = append(*cl, 1)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-07-31 21:23:51 -06:00
|
|
|
func main() {
|
2014-05-13 01:00:11 -06:00
|
|
|
flag.Var(&benchCPU, "benchcpu", "Comma-delimited list of GOMAXPROCS values for benchmarking")
|
|
|
|
flag.Var(&benchAffinity, "benchaffinity", "Comma-delimited list of affinity values for benchmarking")
|
2013-07-31 21:23:51 -06:00
|
|
|
flag.Usage = func() {
|
|
|
|
fmt.Fprintf(os.Stderr, "usage: %s goos-goarch...\n", os.Args[0])
|
|
|
|
flag.PrintDefaults()
|
|
|
|
os.Exit(2)
|
|
|
|
}
|
|
|
|
flag.Parse()
|
2014-12-03 16:43:53 -07:00
|
|
|
if len(flag.Args()) == 0 {
|
2013-07-31 21:23:51 -06:00
|
|
|
flag.Usage()
|
|
|
|
}
|
2013-08-27 22:52:18 -06:00
|
|
|
|
|
|
|
vcs.ShowCmd = *verbose
|
|
|
|
vcs.Verbose = *verbose
|
|
|
|
|
2014-05-13 01:00:11 -06:00
|
|
|
benchMutex = MakeFileMutex(*fileLock)
|
|
|
|
|
2013-09-11 17:45:24 -06:00
|
|
|
rr, err := repoForTool()
|
2013-08-27 22:52:18 -06:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal("Error finding repository:", err)
|
|
|
|
}
|
|
|
|
rootPath := filepath.Join(*buildroot, "goroot")
|
2013-07-31 21:23:51 -06:00
|
|
|
goroot := &Repo{
|
2013-08-27 22:52:18 -06:00
|
|
|
Path: rootPath,
|
|
|
|
Master: rr,
|
2013-07-31 21:23:51 -06:00
|
|
|
}
|
|
|
|
|
2014-05-02 15:38:08 -06:00
|
|
|
// set up work environment, use existing environment if possible
|
2013-07-31 21:23:51 -06:00
|
|
|
if goroot.Exists() || *failAll {
|
|
|
|
log.Print("Found old workspace, will use it")
|
|
|
|
} else {
|
|
|
|
if err := os.RemoveAll(*buildroot); err != nil {
|
|
|
|
log.Fatalf("Error removing build root (%s): %s", *buildroot, err)
|
|
|
|
}
|
|
|
|
if err := os.Mkdir(*buildroot, mkdirPerm); err != nil {
|
|
|
|
log.Fatalf("Error making build root (%s): %s", *buildroot, err)
|
|
|
|
}
|
|
|
|
var err error
|
2013-09-11 17:45:24 -06:00
|
|
|
goroot, err = RemoteRepo(goroot.Master.Root, rootPath)
|
2013-08-27 22:52:18 -06:00
|
|
|
if err != nil {
|
2013-09-11 17:45:24 -06:00
|
|
|
log.Fatalf("Error creating repository with url (%s): %s", goroot.Master.Root, err)
|
2013-08-27 22:52:18 -06:00
|
|
|
}
|
|
|
|
|
2014-12-03 16:43:53 -07:00
|
|
|
goroot, err = goroot.Clone(goroot.Path, "")
|
2013-07-31 21:23:51 -06:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal("Error cloning repository:", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// set up builders
|
|
|
|
builders := make([]*Builder, len(flag.Args()))
|
|
|
|
for i, name := range flag.Args() {
|
|
|
|
b, err := NewBuilder(goroot, name)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
builders[i] = b
|
|
|
|
}
|
|
|
|
|
|
|
|
if *failAll {
|
|
|
|
failMode(builders)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// if specified, build revision and return
|
|
|
|
if *buildRevision != "" {
|
|
|
|
hash, err := goroot.FullHash(*buildRevision)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal("Error finding revision: ", err)
|
|
|
|
}
|
2014-09-05 14:00:51 -06:00
|
|
|
var exitErr error
|
2013-07-31 21:23:51 -06:00
|
|
|
for _, b := range builders {
|
|
|
|
if err := b.buildHash(hash); err != nil {
|
|
|
|
log.Println(err)
|
2014-09-05 14:00:51 -06:00
|
|
|
exitErr = err
|
2013-07-31 21:23:51 -06:00
|
|
|
}
|
|
|
|
}
|
2014-09-05 14:00:51 -06:00
|
|
|
if exitErr != nil && !*report {
|
|
|
|
// This mode (-report=false) is used for
|
|
|
|
// testing Docker images, making sure the
|
|
|
|
// environment is correctly configured. For
|
|
|
|
// testing, we want a non-zero exit status, as
|
|
|
|
// returned by log.Fatal:
|
|
|
|
log.Fatal("Build error.")
|
|
|
|
}
|
2013-07-31 21:23:51 -06:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2014-05-13 01:00:11 -06:00
|
|
|
if !*doBuild && !*doBench {
|
|
|
|
fmt.Fprintf(os.Stderr, "Nothing to do, exiting (specify either -build or -bench or both)\n")
|
|
|
|
os.Exit(2)
|
|
|
|
}
|
|
|
|
|
2013-07-31 21:23:51 -06:00
|
|
|
// go continuous build mode
|
|
|
|
// check for new commits and build them
|
2014-05-13 01:00:11 -06:00
|
|
|
benchMutex.RLock()
|
2013-07-31 21:23:51 -06:00
|
|
|
for {
|
|
|
|
built := false
|
|
|
|
t := time.Now()
|
|
|
|
if *parallel {
|
|
|
|
done := make(chan bool)
|
|
|
|
for _, b := range builders {
|
|
|
|
go func(b *Builder) {
|
2014-05-13 01:00:11 -06:00
|
|
|
done <- b.buildOrBench()
|
2013-07-31 21:23:51 -06:00
|
|
|
}(b)
|
|
|
|
}
|
|
|
|
for _ = range builders {
|
|
|
|
built = <-done || built
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for _, b := range builders {
|
2014-05-13 01:00:11 -06:00
|
|
|
built = b.buildOrBench() || built
|
2013-07-31 21:23:51 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// sleep if there was nothing to build
|
2014-05-13 01:00:11 -06:00
|
|
|
benchMutex.RUnlock()
|
2013-07-31 21:23:51 -06:00
|
|
|
if !built {
|
|
|
|
time.Sleep(waitInterval)
|
|
|
|
}
|
2014-05-13 01:00:11 -06:00
|
|
|
benchMutex.RLock()
|
2013-07-31 21:23:51 -06:00
|
|
|
// sleep if we're looping too fast.
|
|
|
|
dt := time.Now().Sub(t)
|
|
|
|
if dt < waitInterval {
|
|
|
|
time.Sleep(waitInterval - dt)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// go continuous fail mode
|
|
|
|
// check for new commits and FAIL them
|
|
|
|
func failMode(builders []*Builder) {
|
|
|
|
for {
|
|
|
|
built := false
|
|
|
|
for _, b := range builders {
|
|
|
|
built = b.failBuild() || built
|
|
|
|
}
|
|
|
|
// stop if there was nothing to fail
|
|
|
|
if !built {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewBuilder(goroot *Repo, name string) (*Builder, error) {
|
|
|
|
b := &Builder{
|
|
|
|
goroot: goroot,
|
|
|
|
name: name,
|
|
|
|
}
|
|
|
|
|
2013-09-11 17:45:24 -06:00
|
|
|
// get builderEnv for this tool
|
|
|
|
var err error
|
|
|
|
if b.env, err = b.builderEnv(name); err != nil {
|
|
|
|
return nil, err
|
2013-07-31 21:23:51 -06:00
|
|
|
}
|
2014-09-05 14:00:51 -06:00
|
|
|
if *report {
|
|
|
|
err = b.setKey()
|
|
|
|
}
|
|
|
|
return b, err
|
|
|
|
}
|
2013-07-31 21:23:51 -06:00
|
|
|
|
2014-09-05 14:00:51 -06:00
|
|
|
func (b *Builder) setKey() error {
|
2013-07-31 21:23:51 -06:00
|
|
|
// read keys from keyfile
|
|
|
|
fn := ""
|
2014-02-16 09:40:50 -07:00
|
|
|
switch runtime.GOOS {
|
|
|
|
case "plan9":
|
|
|
|
fn = os.Getenv("home")
|
|
|
|
case "windows":
|
2013-07-31 21:23:51 -06:00
|
|
|
fn = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
|
2014-02-16 09:40:50 -07:00
|
|
|
default:
|
2013-07-31 21:23:51 -06:00
|
|
|
fn = os.Getenv("HOME")
|
|
|
|
}
|
|
|
|
fn = filepath.Join(fn, ".gobuildkey")
|
|
|
|
if s := fn + "-" + b.name; isFile(s) { // builder-specific file
|
|
|
|
fn = s
|
|
|
|
}
|
|
|
|
c, err := ioutil.ReadFile(fn)
|
|
|
|
if err != nil {
|
2014-09-04 14:46:24 -06:00
|
|
|
// If the on-disk file doesn't exist, also try the
|
|
|
|
// Google Compute Engine metadata.
|
2014-09-05 14:00:51 -06:00
|
|
|
if v := gceProjectMetadata("buildkey-" + b.name); v != "" {
|
2014-09-04 14:46:24 -06:00
|
|
|
b.key = v
|
2014-09-05 14:00:51 -06:00
|
|
|
return nil
|
2014-09-04 14:46:24 -06:00
|
|
|
}
|
2014-09-05 14:00:51 -06:00
|
|
|
return fmt.Errorf("readKeys %s (%s): %s", b.name, fn, err)
|
2013-07-31 21:23:51 -06:00
|
|
|
}
|
|
|
|
b.key = string(bytes.TrimSpace(bytes.SplitN(c, []byte("\n"), 2)[0]))
|
2014-09-05 14:00:51 -06:00
|
|
|
return nil
|
2013-07-31 21:23:51 -06:00
|
|
|
}
|
|
|
|
|
2014-09-04 14:46:24 -06:00
|
|
|
func gceProjectMetadata(attr string) string {
|
|
|
|
client := &http.Client{
|
|
|
|
Transport: &http.Transport{
|
|
|
|
Dial: (&net.Dialer{
|
|
|
|
Timeout: 750 * time.Millisecond,
|
|
|
|
KeepAlive: 30 * time.Second,
|
|
|
|
}).Dial,
|
|
|
|
ResponseHeaderTimeout: 750 * time.Millisecond,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
req, _ := http.NewRequest("GET", "http://metadata.google.internal/computeMetadata/v1/project/attributes/"+attr, nil)
|
|
|
|
req.Header.Set("Metadata-Flavor", "Google")
|
|
|
|
res, err := client.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
defer res.Body.Close()
|
|
|
|
if res.StatusCode != 200 {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
slurp, err := ioutil.ReadAll(res.Body)
|
|
|
|
if err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return string(bytes.TrimSpace(slurp))
|
|
|
|
}
|
|
|
|
|
2013-09-11 17:45:24 -06:00
|
|
|
// builderEnv returns the builderEnv for this buildTool.
|
|
|
|
func (b *Builder) builderEnv(name string) (builderEnv, error) {
|
|
|
|
// get goos/goarch from builder string
|
|
|
|
s := strings.SplitN(b.name, "-", 3)
|
|
|
|
if len(s) < 2 {
|
|
|
|
return nil, fmt.Errorf("unsupported builder form: %s", name)
|
|
|
|
}
|
|
|
|
b.goos = s[0]
|
|
|
|
b.goarch = s[1]
|
|
|
|
|
|
|
|
switch *buildTool {
|
|
|
|
case "go":
|
|
|
|
return &goEnv{
|
|
|
|
goos: s[0],
|
|
|
|
goarch: s[1],
|
|
|
|
}, nil
|
|
|
|
case "gccgo":
|
|
|
|
return &gccgoEnv{}, nil
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("unsupported build tool: %s", *buildTool)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-31 21:23:51 -06:00
|
|
|
// buildCmd returns the build command to invoke.
|
|
|
|
// Builders which contain the string '-race' in their
|
|
|
|
// name will override *buildCmd and return raceCmd.
|
|
|
|
func (b *Builder) buildCmd() string {
|
|
|
|
if strings.Contains(b.name, "-race") {
|
|
|
|
return raceCmd
|
|
|
|
}
|
|
|
|
return *buildCmd
|
|
|
|
}
|
|
|
|
|
2014-05-13 01:00:11 -06:00
|
|
|
// buildOrBench checks for a new commit for this builder
|
|
|
|
// and builds or benchmarks it if one is found.
|
|
|
|
// It returns true if a build/benchmark was attempted.
|
|
|
|
func (b *Builder) buildOrBench() bool {
|
|
|
|
var kinds []string
|
|
|
|
if *doBuild {
|
|
|
|
kinds = append(kinds, "build-go-commit")
|
|
|
|
}
|
|
|
|
if *doBench {
|
|
|
|
kinds = append(kinds, "benchmark-go-commit")
|
|
|
|
}
|
|
|
|
kind, hash, benchs, err := b.todo(kinds, "", "")
|
2013-07-31 21:23:51 -06:00
|
|
|
if err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if hash == "" {
|
|
|
|
return false
|
|
|
|
}
|
2014-05-13 01:00:11 -06:00
|
|
|
switch kind {
|
|
|
|
case "build-go-commit":
|
|
|
|
if err := b.buildHash(hash); err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
case "benchmark-go-commit":
|
|
|
|
if err := b.benchHash(hash, benchs); err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
default:
|
2014-05-16 15:27:32 -06:00
|
|
|
log.Printf("Unknown todo kind %v", kind)
|
2014-05-13 01:00:11 -06:00
|
|
|
return false
|
2013-07-31 21:23:51 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *Builder) buildHash(hash string) error {
|
|
|
|
log.Println(b.name, "building", hash)
|
|
|
|
|
|
|
|
// create place in which to do work
|
|
|
|
workpath := filepath.Join(*buildroot, b.name+"-"+hash[:12])
|
|
|
|
if err := os.Mkdir(workpath, mkdirPerm); err != nil {
|
2014-09-23 20:59:42 -06:00
|
|
|
if err2 := removePath(workpath); err2 != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := os.Mkdir(workpath, mkdirPerm); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-07-31 21:23:51 -06:00
|
|
|
}
|
2014-05-13 01:00:11 -06:00
|
|
|
defer removePath(workpath)
|
|
|
|
|
|
|
|
buildLog, runTime, err := b.buildRepoOnHash(workpath, hash, b.buildCmd())
|
|
|
|
if err != nil {
|
|
|
|
// record failure
|
|
|
|
return b.recordResult(false, "", hash, "", buildLog, runTime)
|
|
|
|
}
|
|
|
|
|
|
|
|
// record success
|
|
|
|
if err = b.recordResult(true, "", hash, "", "", runTime); err != nil {
|
|
|
|
return fmt.Errorf("recordResult: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// build sub-repositories
|
|
|
|
goRoot := filepath.Join(workpath, *buildTool)
|
|
|
|
goPath := workpath
|
|
|
|
b.buildSubrepos(goRoot, goPath, hash)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// buildRepoOnHash clones repo into workpath and builds it.
|
|
|
|
func (b *Builder) buildRepoOnHash(workpath, hash, cmd string) (buildLog string, runTime time.Duration, err error) {
|
|
|
|
// Delete the previous workdir, if necessary
|
|
|
|
// (benchmarking code can execute several benchmarks in the same workpath).
|
|
|
|
if b.lastWorkpath != "" {
|
|
|
|
if b.lastWorkpath == workpath {
|
|
|
|
panic("workpath already exists: " + workpath)
|
|
|
|
}
|
|
|
|
removePath(b.lastWorkpath)
|
|
|
|
b.lastWorkpath = ""
|
|
|
|
}
|
2013-07-31 21:23:51 -06:00
|
|
|
|
|
|
|
// pull before cloning to ensure we have the revision
|
2014-05-13 01:00:11 -06:00
|
|
|
if err = b.goroot.Pull(); err != nil {
|
|
|
|
buildLog = err.Error()
|
|
|
|
return
|
2013-07-31 21:23:51 -06:00
|
|
|
}
|
|
|
|
|
2013-09-11 17:45:24 -06:00
|
|
|
// set up builder's environment.
|
|
|
|
srcDir, err := b.env.setup(b.goroot, workpath, hash, b.envv())
|
|
|
|
if err != nil {
|
2014-05-13 01:00:11 -06:00
|
|
|
buildLog = err.Error()
|
|
|
|
return
|
2013-07-31 21:23:51 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// build
|
2014-08-28 15:58:15 -06:00
|
|
|
var buildbuf bytes.Buffer
|
2013-07-31 21:23:51 -06:00
|
|
|
logfile := filepath.Join(workpath, "build.log")
|
|
|
|
f, err := os.Create(logfile)
|
|
|
|
if err != nil {
|
2014-08-28 15:58:15 -06:00
|
|
|
return err.Error(), 0, err
|
2013-07-31 21:23:51 -06:00
|
|
|
}
|
|
|
|
defer f.Close()
|
2014-08-28 15:58:15 -06:00
|
|
|
w := io.MultiWriter(f, &buildbuf)
|
2013-07-31 21:23:51 -06:00
|
|
|
|
2013-09-11 17:45:24 -06:00
|
|
|
// go's build command is a script relative to the srcDir, whereas
|
|
|
|
// gccgo's build command is usually "make check-go" in the srcDir.
|
|
|
|
if *buildTool == "go" {
|
|
|
|
if !filepath.IsAbs(cmd) {
|
|
|
|
cmd = filepath.Join(srcDir, cmd)
|
|
|
|
}
|
2013-07-31 21:23:51 -06:00
|
|
|
}
|
2013-09-11 17:45:24 -06:00
|
|
|
|
2014-08-28 15:58:15 -06:00
|
|
|
// naive splitting of command from its arguments:
|
|
|
|
args := strings.Split(cmd, " ")
|
|
|
|
c := exec.Command(args[0], args[1:]...)
|
|
|
|
c.Dir = srcDir
|
|
|
|
c.Env = b.envv()
|
|
|
|
if *verbose {
|
|
|
|
c.Stdout = io.MultiWriter(os.Stdout, w)
|
|
|
|
c.Stderr = io.MultiWriter(os.Stderr, w)
|
|
|
|
} else {
|
|
|
|
c.Stdout = w
|
|
|
|
c.Stderr = w
|
2014-05-13 01:00:11 -06:00
|
|
|
}
|
2014-08-28 15:58:15 -06:00
|
|
|
|
|
|
|
startTime := time.Now()
|
|
|
|
err = run(c, runTimeout(*buildTimeout))
|
|
|
|
runTime = time.Since(startTime)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(w, "Build complete, duration %v. Result: error: %v\n", runTime, err)
|
|
|
|
} else {
|
|
|
|
fmt.Fprintf(w, "Build complete, duration %v. Result: success\n", runTime)
|
2013-07-31 21:23:51 -06:00
|
|
|
}
|
2014-08-28 15:58:15 -06:00
|
|
|
return buildbuf.String(), runTime, err
|
2013-07-31 21:23:51 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// failBuild checks for a new commit for this builder
|
|
|
|
// and fails it if one is found.
|
|
|
|
// It returns true if a build was "attempted".
|
|
|
|
func (b *Builder) failBuild() bool {
|
2014-05-13 01:00:11 -06:00
|
|
|
_, hash, _, err := b.todo([]string{"build-go-commit"}, "", "")
|
2013-07-31 21:23:51 -06:00
|
|
|
if err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if hash == "" {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("fail %s %s\n", b.name, hash)
|
|
|
|
|
|
|
|
if err := b.recordResult(false, "", hash, "", "auto-fail mode run by "+os.Getenv("USER"), 0); err != nil {
|
|
|
|
log.Print(err)
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *Builder) buildSubrepos(goRoot, goPath, goHash string) {
|
|
|
|
for _, pkg := range dashboardPackages("subrepo") {
|
|
|
|
// get the latest todo for this package
|
2014-05-13 01:00:11 -06:00
|
|
|
_, hash, _, err := b.todo([]string{"build-package"}, pkg, goHash)
|
2013-07-31 21:23:51 -06:00
|
|
|
if err != nil {
|
|
|
|
log.Printf("buildSubrepos %s: %v", pkg, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if hash == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// build the package
|
|
|
|
if *verbose {
|
|
|
|
log.Printf("buildSubrepos %s: building %q", pkg, hash)
|
|
|
|
}
|
|
|
|
buildLog, err := b.buildSubrepo(goRoot, goPath, pkg, hash)
|
|
|
|
if err != nil {
|
|
|
|
if buildLog == "" {
|
|
|
|
buildLog = err.Error()
|
|
|
|
}
|
|
|
|
log.Printf("buildSubrepos %s: %v", pkg, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// record the result
|
|
|
|
err = b.recordResult(err == nil, pkg, hash, goHash, buildLog, 0)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("buildSubrepos %s: %v", pkg, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// buildSubrepo fetches the given package, updates it to the specified hash,
|
|
|
|
// and runs 'go test -short pkg/...'. It returns the build log and any error.
|
|
|
|
func (b *Builder) buildSubrepo(goRoot, goPath, pkg, hash string) (string, error) {
|
2014-05-13 01:00:11 -06:00
|
|
|
goTool := filepath.Join(goRoot, "bin", "go") + exeExt
|
2013-07-31 21:23:51 -06:00
|
|
|
env := append(b.envv(), "GOROOT="+goRoot, "GOPATH="+goPath)
|
|
|
|
|
|
|
|
// add $GOROOT/bin and $GOPATH/bin to PATH
|
|
|
|
for i, e := range env {
|
|
|
|
const p = "PATH="
|
|
|
|
if !strings.HasPrefix(e, p) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
sep := string(os.PathListSeparator)
|
|
|
|
env[i] = p + filepath.Join(goRoot, "bin") + sep + filepath.Join(goPath, "bin") + sep + e[len(p):]
|
|
|
|
}
|
|
|
|
|
2014-11-09 16:42:45 -07:00
|
|
|
// HACK: check out to new sub-repo location instead of old location.
|
|
|
|
pkg = strings.Replace(pkg, "code.google.com/p/go.", "golang.org/x/", 1)
|
|
|
|
|
2013-07-31 21:23:51 -06:00
|
|
|
// fetch package and dependencies
|
2014-08-28 15:58:15 -06:00
|
|
|
var outbuf bytes.Buffer
|
|
|
|
err := run(exec.Command(goTool, "get", "-d", pkg+"/..."), runEnv(env), allOutput(&outbuf), runDir(goPath))
|
2013-07-31 21:23:51 -06:00
|
|
|
if err != nil {
|
2014-08-28 15:58:15 -06:00
|
|
|
return outbuf.String(), err
|
2013-07-31 21:23:51 -06:00
|
|
|
}
|
2014-08-28 15:58:15 -06:00
|
|
|
outbuf.Reset()
|
2013-07-31 21:23:51 -06:00
|
|
|
|
|
|
|
// hg update to the specified hash
|
2013-09-23 18:18:31 -06:00
|
|
|
pkgmaster, err := vcs.RepoRootForImportPath(pkg, *verbose)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("Error finding subrepo (%s): %s", pkg, err)
|
|
|
|
}
|
|
|
|
repo := &Repo{
|
|
|
|
Path: filepath.Join(goPath, "src", pkg),
|
|
|
|
Master: pkgmaster,
|
|
|
|
}
|
2013-07-31 21:23:51 -06:00
|
|
|
if err := repo.UpdateTo(hash); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
// test the package
|
2014-08-28 15:58:15 -06:00
|
|
|
err = run(exec.Command(goTool, "test", "-short", pkg+"/..."),
|
|
|
|
runTimeout(*buildTimeout), runEnv(env), allOutput(&outbuf), runDir(goPath))
|
|
|
|
return outbuf.String(), err
|
2013-07-31 21:23:51 -06:00
|
|
|
}
|
|
|
|
|
2013-09-11 17:45:24 -06:00
|
|
|
// repoForTool returns the correct RepoRoot for the buildTool, or an error if
|
|
|
|
// the tool is unknown.
|
|
|
|
func repoForTool() (*vcs.RepoRoot, error) {
|
|
|
|
switch *buildTool {
|
|
|
|
case "go":
|
2013-11-21 23:12:51 -07:00
|
|
|
return vcs.RepoRootForImportPath(*gcPath, *verbose)
|
2013-09-11 17:45:24 -06:00
|
|
|
case "gccgo":
|
|
|
|
return vcs.RepoRootForImportPath(gofrontendImportPath, *verbose)
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("unknown build tool: %s", *buildTool)
|
2013-07-31 21:23:51 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func isDirectory(name string) bool {
|
|
|
|
s, err := os.Stat(name)
|
|
|
|
return err == nil && s.IsDir()
|
|
|
|
}
|
|
|
|
|
|
|
|
func isFile(name string) bool {
|
|
|
|
s, err := os.Stat(name)
|
|
|
|
return err == nil && !s.IsDir()
|
|
|
|
}
|
|
|
|
|
|
|
|
// defaultSuffix returns file extension used for command files in
|
|
|
|
// current os environment.
|
|
|
|
func defaultSuffix() string {
|
|
|
|
switch runtime.GOOS {
|
|
|
|
case "windows":
|
|
|
|
return ".bat"
|
|
|
|
case "plan9":
|
|
|
|
return ".rc"
|
|
|
|
default:
|
|
|
|
return ".bash"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-13 01:00:11 -06:00
|
|
|
func defaultExeExt() string {
|
|
|
|
switch runtime.GOOS {
|
|
|
|
case "windows":
|
|
|
|
return ".exe"
|
|
|
|
default:
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-31 21:23:51 -06:00
|
|
|
// defaultBuildRoot returns default buildroot directory.
|
|
|
|
func defaultBuildRoot() string {
|
|
|
|
var d string
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
// will use c:\, otherwise absolute paths become too long
|
|
|
|
// during builder run, see http://golang.org/issue/3358.
|
|
|
|
d = `c:\`
|
|
|
|
} else {
|
|
|
|
d = os.TempDir()
|
|
|
|
}
|
|
|
|
return filepath.Join(d, "gobuilder")
|
|
|
|
}
|
2014-05-13 01:00:11 -06:00
|
|
|
|
|
|
|
// removePath is a more robust version of os.RemoveAll.
|
|
|
|
// On windows, if remove fails (which can happen if test/benchmark timeouts
|
|
|
|
// and keeps some files open) it tries to rename the dir.
|
|
|
|
func removePath(path string) error {
|
|
|
|
if err := os.RemoveAll(path); err != nil {
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
err = os.Rename(path, filepath.Clean(path)+"_remove_me")
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|