github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/dashboard/builder/main.go (about)

     1  // Copyright 2011 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"bytes"
     9  	"flag"
    10  	"fmt"
    11  	"io"
    12  	"io/ioutil"
    13  	"log"
    14  	"net"
    15  	"net/http"
    16  	"os"
    17  	"os/exec"
    18  	"path/filepath"
    19  	"regexp"
    20  	"runtime"
    21  	"strconv"
    22  	"strings"
    23  	"time"
    24  
    25  	"golang.org/x/tools/go/vcs"
    26  )
    27  
    28  const (
    29  	codeProject          = "go"
    30  	codePyScript         = "misc/dashboard/googlecode_upload.py"
    31  	gofrontendImportPath = "code.google.com/p/gofrontend"
    32  	mkdirPerm            = 0750
    33  	waitInterval         = 30 * time.Second // time to wait before checking for new revs
    34  	pkgBuildInterval     = 24 * time.Hour   // rebuild packages every 24 hours
    35  )
    36  
    37  type Builder struct {
    38  	goroot       *Repo
    39  	name         string
    40  	goos, goarch string
    41  	key          string
    42  	env          builderEnv
    43  	// Last benchmarking workpath. We reuse it, if do successive benchmarks on the same commit.
    44  	lastWorkpath string
    45  }
    46  
    47  var (
    48  	doBuild       = flag.Bool("build", true, "Build and test packages")
    49  	doBench       = flag.Bool("bench", false, "Run benchmarks")
    50  	buildroot     = flag.String("buildroot", defaultBuildRoot(), "Directory under which to build")
    51  	dashboard     = flag.String("dashboard", "https://build.golang.org", "Dashboard app base path")
    52  	buildRelease  = flag.Bool("release", false, "Build and upload binary release archives")
    53  	buildRevision = flag.String("rev", "", "Build specified revision and exit")
    54  	buildCmd      = flag.String("cmd", filepath.Join(".", allCmd), "Build command (specify relative to go/src/)")
    55  	buildTool     = flag.String("tool", "go", "Tool to build.")
    56  	gcPath        = flag.String("gcpath", "go.googlesource.com/go", "Path to download gc from")
    57  	gccPath       = flag.String("gccpath", "https://github.com/mirrors/gcc.git", "Path to download gcc from")
    58  	gccOpts       = flag.String("gccopts", "", "Command-line options to pass to `make` when building gccgo")
    59  	benchPath     = flag.String("benchpath", "golang.org/x/benchmarks/bench", "Path to download benchmarks from")
    60  	failAll       = flag.Bool("fail", false, "fail all builds")
    61  	parallel      = flag.Bool("parallel", false, "Build multiple targets in parallel")
    62  	buildTimeout  = flag.Duration("buildTimeout", 60*time.Minute, "Maximum time to wait for builds and tests")
    63  	cmdTimeout    = flag.Duration("cmdTimeout", 10*time.Minute, "Maximum time to wait for an external command")
    64  	benchNum      = flag.Int("benchnum", 5, "Run each benchmark that many times")
    65  	benchTime     = flag.Duration("benchtime", 5*time.Second, "Benchmarking time for a single benchmark run")
    66  	benchMem      = flag.Int("benchmem", 64, "Approx RSS value to aim at in benchmarks, in MB")
    67  	fileLock      = flag.String("filelock", "", "File to lock around benchmaring (synchronizes several builders)")
    68  	verbose       = flag.Bool("v", false, "verbose")
    69  	report        = flag.Bool("report", true, "whether to report results to the dashboard")
    70  )
    71  
    72  var (
    73  	binaryTagRe = regexp.MustCompile(`^(release\.r|weekly\.)[0-9\-.]+`)
    74  	releaseRe   = regexp.MustCompile(`^release\.r[0-9\-.]+`)
    75  	allCmd      = "all" + suffix
    76  	makeCmd     = "make" + suffix
    77  	raceCmd     = "race" + suffix
    78  	cleanCmd    = "clean" + suffix
    79  	suffix      = defaultSuffix()
    80  	exeExt      = defaultExeExt()
    81  
    82  	benchCPU      = CpuList([]int{1})
    83  	benchAffinity = CpuList([]int{})
    84  	benchMutex    *FileMutex // Isolates benchmarks from other activities
    85  )
    86  
    87  // CpuList is used as flag.Value for -benchcpu flag.
    88  type CpuList []int
    89  
    90  func (cl *CpuList) String() string {
    91  	str := ""
    92  	for _, cpu := range *cl {
    93  		if str == "" {
    94  			str = strconv.Itoa(cpu)
    95  		} else {
    96  			str += fmt.Sprintf(",%v", cpu)
    97  		}
    98  	}
    99  	return str
   100  }
   101  
   102  func (cl *CpuList) Set(str string) error {
   103  	*cl = []int{}
   104  	for _, val := range strings.Split(str, ",") {
   105  		val = strings.TrimSpace(val)
   106  		if val == "" {
   107  			continue
   108  		}
   109  		cpu, err := strconv.Atoi(val)
   110  		if err != nil || cpu <= 0 {
   111  			return fmt.Errorf("%v is a bad value for GOMAXPROCS", val)
   112  		}
   113  		*cl = append(*cl, cpu)
   114  	}
   115  	if len(*cl) == 0 {
   116  		*cl = append(*cl, 1)
   117  	}
   118  	return nil
   119  }
   120  
   121  func main() {
   122  	flag.Var(&benchCPU, "benchcpu", "Comma-delimited list of GOMAXPROCS values for benchmarking")
   123  	flag.Var(&benchAffinity, "benchaffinity", "Comma-delimited list of affinity values for benchmarking")
   124  	flag.Usage = func() {
   125  		fmt.Fprintf(os.Stderr, "usage: %s goos-goarch...\n", os.Args[0])
   126  		flag.PrintDefaults()
   127  		os.Exit(2)
   128  	}
   129  	flag.Parse()
   130  	if len(flag.Args()) == 0 {
   131  		flag.Usage()
   132  	}
   133  
   134  	vcs.ShowCmd = *verbose
   135  	vcs.Verbose = *verbose
   136  
   137  	benchMutex = MakeFileMutex(*fileLock)
   138  
   139  	rr, err := repoForTool()
   140  	if err != nil {
   141  		log.Fatal("Error finding repository:", err)
   142  	}
   143  	rootPath := filepath.Join(*buildroot, "goroot")
   144  	goroot := &Repo{
   145  		Path:   rootPath,
   146  		Master: rr,
   147  	}
   148  
   149  	// set up work environment, use existing environment if possible
   150  	if goroot.Exists() || *failAll {
   151  		log.Print("Found old workspace, will use it")
   152  	} else {
   153  		if err := os.RemoveAll(*buildroot); err != nil {
   154  			log.Fatalf("Error removing build root (%s): %s", *buildroot, err)
   155  		}
   156  		if err := os.Mkdir(*buildroot, mkdirPerm); err != nil {
   157  			log.Fatalf("Error making build root (%s): %s", *buildroot, err)
   158  		}
   159  		var err error
   160  		goroot, err = RemoteRepo(goroot.Master.Root, rootPath)
   161  		if err != nil {
   162  			log.Fatalf("Error creating repository with url (%s): %s", goroot.Master.Root, err)
   163  		}
   164  
   165  		goroot, err = goroot.Clone(goroot.Path, "")
   166  		if err != nil {
   167  			log.Fatal("Error cloning repository:", err)
   168  		}
   169  	}
   170  
   171  	// set up builders
   172  	builders := make([]*Builder, len(flag.Args()))
   173  	for i, name := range flag.Args() {
   174  		b, err := NewBuilder(goroot, name)
   175  		if err != nil {
   176  			log.Fatal(err)
   177  		}
   178  		builders[i] = b
   179  	}
   180  
   181  	if *failAll {
   182  		failMode(builders)
   183  		return
   184  	}
   185  
   186  	// if specified, build revision and return
   187  	if *buildRevision != "" {
   188  		hash, err := goroot.FullHash(*buildRevision)
   189  		if err != nil {
   190  			log.Fatal("Error finding revision: ", err)
   191  		}
   192  		var exitErr error
   193  		for _, b := range builders {
   194  			if err := b.buildHash(hash); err != nil {
   195  				log.Println(err)
   196  				exitErr = err
   197  			}
   198  		}
   199  		if exitErr != nil && !*report {
   200  			// This mode (-report=false) is used for
   201  			// testing Docker images, making sure the
   202  			// environment is correctly configured. For
   203  			// testing, we want a non-zero exit status, as
   204  			// returned by log.Fatal:
   205  			log.Fatal("Build error.")
   206  		}
   207  		return
   208  	}
   209  
   210  	if !*doBuild && !*doBench {
   211  		fmt.Fprintf(os.Stderr, "Nothing to do, exiting (specify either -build or -bench or both)\n")
   212  		os.Exit(2)
   213  	}
   214  
   215  	// go continuous build mode
   216  	// check for new commits and build them
   217  	benchMutex.RLock()
   218  	for {
   219  		built := false
   220  		t := time.Now()
   221  		if *parallel {
   222  			done := make(chan bool)
   223  			for _, b := range builders {
   224  				go func(b *Builder) {
   225  					done <- b.buildOrBench()
   226  				}(b)
   227  			}
   228  			for _ = range builders {
   229  				built = <-done || built
   230  			}
   231  		} else {
   232  			for _, b := range builders {
   233  				built = b.buildOrBench() || built
   234  			}
   235  		}
   236  		// sleep if there was nothing to build
   237  		benchMutex.RUnlock()
   238  		if !built {
   239  			time.Sleep(waitInterval)
   240  		}
   241  		benchMutex.RLock()
   242  		// sleep if we're looping too fast.
   243  		dt := time.Now().Sub(t)
   244  		if dt < waitInterval {
   245  			time.Sleep(waitInterval - dt)
   246  		}
   247  	}
   248  }
   249  
   250  // go continuous fail mode
   251  // check for new commits and FAIL them
   252  func failMode(builders []*Builder) {
   253  	for {
   254  		built := false
   255  		for _, b := range builders {
   256  			built = b.failBuild() || built
   257  		}
   258  		// stop if there was nothing to fail
   259  		if !built {
   260  			break
   261  		}
   262  	}
   263  }
   264  
   265  func NewBuilder(goroot *Repo, name string) (*Builder, error) {
   266  	b := &Builder{
   267  		goroot: goroot,
   268  		name:   name,
   269  	}
   270  
   271  	// get builderEnv for this tool
   272  	var err error
   273  	if b.env, err = b.builderEnv(name); err != nil {
   274  		return nil, err
   275  	}
   276  	if *report {
   277  		err = b.setKey()
   278  	}
   279  	return b, err
   280  }
   281  
   282  func (b *Builder) setKey() error {
   283  	// read keys from keyfile
   284  	fn := ""
   285  	switch runtime.GOOS {
   286  	case "plan9":
   287  		fn = os.Getenv("home")
   288  	case "windows":
   289  		fn = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
   290  	default:
   291  		fn = os.Getenv("HOME")
   292  	}
   293  	fn = filepath.Join(fn, ".gobuildkey")
   294  	if s := fn + "-" + b.name; isFile(s) { // builder-specific file
   295  		fn = s
   296  	}
   297  	c, err := ioutil.ReadFile(fn)
   298  	if err != nil {
   299  		// If the on-disk file doesn't exist, also try the
   300  		// Google Compute Engine metadata.
   301  		if v := gceProjectMetadata("buildkey-" + b.name); v != "" {
   302  			b.key = v
   303  			return nil
   304  		}
   305  		return fmt.Errorf("readKeys %s (%s): %s", b.name, fn, err)
   306  	}
   307  	b.key = string(bytes.TrimSpace(bytes.SplitN(c, []byte("\n"), 2)[0]))
   308  	return nil
   309  }
   310  
   311  func gceProjectMetadata(attr string) string {
   312  	client := &http.Client{
   313  		Transport: &http.Transport{
   314  			Dial: (&net.Dialer{
   315  				Timeout:   750 * time.Millisecond,
   316  				KeepAlive: 30 * time.Second,
   317  			}).Dial,
   318  			ResponseHeaderTimeout: 750 * time.Millisecond,
   319  		},
   320  	}
   321  	req, _ := http.NewRequest("GET", "http://metadata.google.internal/computeMetadata/v1/project/attributes/"+attr, nil)
   322  	req.Header.Set("Metadata-Flavor", "Google")
   323  	res, err := client.Do(req)
   324  	if err != nil {
   325  		return ""
   326  	}
   327  	defer res.Body.Close()
   328  	if res.StatusCode != 200 {
   329  		return ""
   330  	}
   331  	slurp, err := ioutil.ReadAll(res.Body)
   332  	if err != nil {
   333  		return ""
   334  	}
   335  	return string(bytes.TrimSpace(slurp))
   336  }
   337  
   338  // builderEnv returns the builderEnv for this buildTool.
   339  func (b *Builder) builderEnv(name string) (builderEnv, error) {
   340  	// get goos/goarch from builder string
   341  	s := strings.SplitN(b.name, "-", 3)
   342  	if len(s) < 2 {
   343  		return nil, fmt.Errorf("unsupported builder form: %s", name)
   344  	}
   345  	b.goos = s[0]
   346  	b.goarch = s[1]
   347  
   348  	switch *buildTool {
   349  	case "go":
   350  		return &goEnv{
   351  			goos:   s[0],
   352  			goarch: s[1],
   353  		}, nil
   354  	case "gccgo":
   355  		return &gccgoEnv{}, nil
   356  	default:
   357  		return nil, fmt.Errorf("unsupported build tool: %s", *buildTool)
   358  	}
   359  }
   360  
   361  // buildCmd returns the build command to invoke.
   362  // Builders which contain the string '-race' in their
   363  // name will override *buildCmd and return raceCmd.
   364  func (b *Builder) buildCmd() string {
   365  	if strings.Contains(b.name, "-race") {
   366  		return raceCmd
   367  	}
   368  	return *buildCmd
   369  }
   370  
   371  // buildOrBench checks for a new commit for this builder
   372  // and builds or benchmarks it if one is found.
   373  // It returns true if a build/benchmark was attempted.
   374  func (b *Builder) buildOrBench() bool {
   375  	var kinds []string
   376  	if *doBuild {
   377  		kinds = append(kinds, "build-go-commit")
   378  	}
   379  	if *doBench {
   380  		kinds = append(kinds, "benchmark-go-commit")
   381  	}
   382  	kind, hash, benchs, err := b.todo(kinds, "", "")
   383  	if err != nil {
   384  		log.Println(err)
   385  		return false
   386  	}
   387  	if hash == "" {
   388  		return false
   389  	}
   390  	switch kind {
   391  	case "build-go-commit":
   392  		if err := b.buildHash(hash); err != nil {
   393  			log.Println(err)
   394  		}
   395  		return true
   396  	case "benchmark-go-commit":
   397  		if err := b.benchHash(hash, benchs); err != nil {
   398  			log.Println(err)
   399  		}
   400  		return true
   401  	default:
   402  		log.Printf("Unknown todo kind %v", kind)
   403  		return false
   404  	}
   405  }
   406  
   407  func (b *Builder) buildHash(hash string) error {
   408  	log.Println(b.name, "building", hash)
   409  
   410  	// create place in which to do work
   411  	workpath := filepath.Join(*buildroot, b.name+"-"+hash[:12])
   412  	if err := os.Mkdir(workpath, mkdirPerm); err != nil {
   413  		if err2 := removePath(workpath); err2 != nil {
   414  			return err
   415  		}
   416  		if err := os.Mkdir(workpath, mkdirPerm); err != nil {
   417  			return err
   418  		}
   419  	}
   420  	defer removePath(workpath)
   421  
   422  	buildLog, runTime, err := b.buildRepoOnHash(workpath, hash, b.buildCmd())
   423  	if err != nil {
   424  		// record failure
   425  		return b.recordResult(false, "", hash, "", buildLog, runTime)
   426  	}
   427  
   428  	// record success
   429  	if err = b.recordResult(true, "", hash, "", "", runTime); err != nil {
   430  		return fmt.Errorf("recordResult: %s", err)
   431  	}
   432  
   433  	if *buildTool == "go" {
   434  		// build sub-repositories
   435  		goRoot := filepath.Join(workpath, *buildTool)
   436  		goPath := workpath
   437  		b.buildSubrepos(goRoot, goPath, hash)
   438  	}
   439  
   440  	return nil
   441  }
   442  
   443  // buildRepoOnHash clones repo into workpath and builds it.
   444  func (b *Builder) buildRepoOnHash(workpath, hash, cmd string) (buildLog string, runTime time.Duration, err error) {
   445  	// Delete the previous workdir, if necessary
   446  	// (benchmarking code can execute several benchmarks in the same workpath).
   447  	if b.lastWorkpath != "" {
   448  		if b.lastWorkpath == workpath {
   449  			panic("workpath already exists: " + workpath)
   450  		}
   451  		removePath(b.lastWorkpath)
   452  		b.lastWorkpath = ""
   453  	}
   454  
   455  	// pull before cloning to ensure we have the revision
   456  	if err = b.goroot.Pull(); err != nil {
   457  		buildLog = err.Error()
   458  		return
   459  	}
   460  
   461  	// set up builder's environment.
   462  	srcDir, err := b.env.setup(b.goroot, workpath, hash, b.envv())
   463  	if err != nil {
   464  		buildLog = err.Error()
   465  		return
   466  	}
   467  
   468  	// build
   469  	var buildbuf bytes.Buffer
   470  	logfile := filepath.Join(workpath, "build.log")
   471  	f, err := os.Create(logfile)
   472  	if err != nil {
   473  		return err.Error(), 0, err
   474  	}
   475  	defer f.Close()
   476  	w := io.MultiWriter(f, &buildbuf)
   477  
   478  	// go's build command is a script relative to the srcDir, whereas
   479  	// gccgo's build command is usually "make check-go" in the srcDir.
   480  	if *buildTool == "go" {
   481  		if !filepath.IsAbs(cmd) {
   482  			cmd = filepath.Join(srcDir, cmd)
   483  		}
   484  	}
   485  
   486  	// naive splitting of command from its arguments:
   487  	args := strings.Split(cmd, " ")
   488  	c := exec.Command(args[0], args[1:]...)
   489  	c.Dir = srcDir
   490  	c.Env = b.envv()
   491  	if *verbose {
   492  		c.Stdout = io.MultiWriter(os.Stdout, w)
   493  		c.Stderr = io.MultiWriter(os.Stderr, w)
   494  	} else {
   495  		c.Stdout = w
   496  		c.Stderr = w
   497  	}
   498  
   499  	startTime := time.Now()
   500  	err = run(c, runTimeout(*buildTimeout))
   501  	runTime = time.Since(startTime)
   502  	if err != nil {
   503  		fmt.Fprintf(w, "Build complete, duration %v. Result: error: %v\n", runTime, err)
   504  	} else {
   505  		fmt.Fprintf(w, "Build complete, duration %v. Result: success\n", runTime)
   506  	}
   507  	return buildbuf.String(), runTime, err
   508  }
   509  
   510  // failBuild checks for a new commit for this builder
   511  // and fails it if one is found.
   512  // It returns true if a build was "attempted".
   513  func (b *Builder) failBuild() bool {
   514  	_, hash, _, err := b.todo([]string{"build-go-commit"}, "", "")
   515  	if err != nil {
   516  		log.Println(err)
   517  		return false
   518  	}
   519  	if hash == "" {
   520  		return false
   521  	}
   522  
   523  	log.Printf("fail %s %s\n", b.name, hash)
   524  
   525  	if err := b.recordResult(false, "", hash, "", "auto-fail mode run by "+os.Getenv("USER"), 0); err != nil {
   526  		log.Print(err)
   527  	}
   528  	return true
   529  }
   530  
   531  func (b *Builder) buildSubrepos(goRoot, goPath, goHash string) {
   532  	for _, pkg := range dashboardPackages("subrepo") {
   533  		// get the latest todo for this package
   534  		_, hash, _, err := b.todo([]string{"build-package"}, pkg, goHash)
   535  		if err != nil {
   536  			log.Printf("buildSubrepos %s: %v", pkg, err)
   537  			continue
   538  		}
   539  		if hash == "" {
   540  			continue
   541  		}
   542  
   543  		// build the package
   544  		if *verbose {
   545  			log.Printf("buildSubrepos %s: building %q", pkg, hash)
   546  		}
   547  		buildLog, err := b.buildSubrepo(goRoot, goPath, pkg, hash)
   548  		if err != nil {
   549  			if buildLog == "" {
   550  				buildLog = err.Error()
   551  			}
   552  			log.Printf("buildSubrepos %s: %v", pkg, err)
   553  		}
   554  
   555  		// record the result
   556  		err = b.recordResult(err == nil, pkg, hash, goHash, buildLog, 0)
   557  		if err != nil {
   558  			log.Printf("buildSubrepos %s: %v", pkg, err)
   559  		}
   560  	}
   561  }
   562  
   563  // buildSubrepo fetches the given package, updates it to the specified hash,
   564  // and runs 'go test -short pkg/...'. It returns the build log and any error.
   565  func (b *Builder) buildSubrepo(goRoot, goPath, pkg, hash string) (string, error) {
   566  	goTool := filepath.Join(goRoot, "bin", "go") + exeExt
   567  	env := append(b.envv(), "GOROOT="+goRoot, "GOPATH="+goPath)
   568  
   569  	// add $GOROOT/bin and $GOPATH/bin to PATH
   570  	for i, e := range env {
   571  		const p = "PATH="
   572  		if !strings.HasPrefix(e, p) {
   573  			continue
   574  		}
   575  		sep := string(os.PathListSeparator)
   576  		env[i] = p + filepath.Join(goRoot, "bin") + sep + filepath.Join(goPath, "bin") + sep + e[len(p):]
   577  	}
   578  
   579  	// HACK: check out to new sub-repo location instead of old location.
   580  	pkg = strings.Replace(pkg, "code.google.com/p/go.", "golang.org/x/", 1)
   581  
   582  	// fetch package and dependencies
   583  	var outbuf bytes.Buffer
   584  	err := run(exec.Command(goTool, "get", "-d", pkg+"/..."), runEnv(env), allOutput(&outbuf), runDir(goPath))
   585  	if err != nil {
   586  		return outbuf.String(), err
   587  	}
   588  	outbuf.Reset()
   589  
   590  	// hg update to the specified hash
   591  	pkgmaster, err := vcs.RepoRootForImportPath(pkg, *verbose)
   592  	if err != nil {
   593  		return "", fmt.Errorf("Error finding subrepo (%s): %s", pkg, err)
   594  	}
   595  	repo := &Repo{
   596  		Path:   filepath.Join(goPath, "src", pkg),
   597  		Master: pkgmaster,
   598  	}
   599  	if err := repo.UpdateTo(hash); err != nil {
   600  		return "", err
   601  	}
   602  
   603  	// test the package
   604  	err = run(exec.Command(goTool, "test", "-short", pkg+"/..."),
   605  		runTimeout(*buildTimeout), runEnv(env), allOutput(&outbuf), runDir(goPath))
   606  	return outbuf.String(), err
   607  }
   608  
   609  // repoForTool returns the correct RepoRoot for the buildTool, or an error if
   610  // the tool is unknown.
   611  func repoForTool() (*vcs.RepoRoot, error) {
   612  	switch *buildTool {
   613  	case "go":
   614  		return vcs.RepoRootForImportPath(*gcPath, *verbose)
   615  	case "gccgo":
   616  		return vcs.RepoRootForImportPath(gofrontendImportPath, *verbose)
   617  	default:
   618  		return nil, fmt.Errorf("unknown build tool: %s", *buildTool)
   619  	}
   620  }
   621  
   622  func isDirectory(name string) bool {
   623  	s, err := os.Stat(name)
   624  	return err == nil && s.IsDir()
   625  }
   626  
   627  func isFile(name string) bool {
   628  	s, err := os.Stat(name)
   629  	return err == nil && !s.IsDir()
   630  }
   631  
   632  // defaultSuffix returns file extension used for command files in
   633  // current os environment.
   634  func defaultSuffix() string {
   635  	switch runtime.GOOS {
   636  	case "windows":
   637  		return ".bat"
   638  	case "plan9":
   639  		return ".rc"
   640  	default:
   641  		return ".bash"
   642  	}
   643  }
   644  
   645  func defaultExeExt() string {
   646  	switch runtime.GOOS {
   647  	case "windows":
   648  		return ".exe"
   649  	default:
   650  		return ""
   651  	}
   652  }
   653  
   654  // defaultBuildRoot returns default buildroot directory.
   655  func defaultBuildRoot() string {
   656  	var d string
   657  	if runtime.GOOS == "windows" {
   658  		// will use c:\, otherwise absolute paths become too long
   659  		// during builder run, see http://golang.org/issue/3358.
   660  		d = `c:\`
   661  	} else {
   662  		d = os.TempDir()
   663  	}
   664  	return filepath.Join(d, "gobuilder")
   665  }
   666  
   667  // removePath is a more robust version of os.RemoveAll.
   668  // On windows, if remove fails (which can happen if test/benchmark timeouts
   669  // and keeps some files open) it tries to rename the dir.
   670  func removePath(path string) error {
   671  	if err := os.RemoveAll(path); err != nil {
   672  		if runtime.GOOS == "windows" {
   673  			err = os.Rename(path, filepath.Clean(path)+"_remove_me")
   674  		}
   675  		return err
   676  	}
   677  	return nil
   678  }