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

     1  // Copyright 2013 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  	"fmt"
    10  	"io/ioutil"
    11  	"os"
    12  	"os/exec"
    13  	"path/filepath"
    14  	"regexp"
    15  	"runtime"
    16  	"strings"
    17  
    18  	"golang.org/x/tools/go/vcs"
    19  )
    20  
    21  // builderEnv represents the environment that a Builder will run tests in.
    22  type builderEnv interface {
    23  	// setup sets up the builder environment and returns the directory to run the buildCmd in.
    24  	setup(repo *Repo, workpath, hash string, envv []string) (string, error)
    25  }
    26  
    27  // goEnv represents the builderEnv for the main Go repo.
    28  type goEnv struct {
    29  	goos, goarch string
    30  }
    31  
    32  func (b *Builder) envv() []string {
    33  	if runtime.GOOS == "windows" {
    34  		return b.envvWindows()
    35  	}
    36  
    37  	var e []string
    38  	if *buildTool == "go" {
    39  		e = []string{
    40  			"GOOS=" + b.goos,
    41  			"GOARCH=" + b.goarch,
    42  			"GOROOT_FINAL=/usr/local/go",
    43  		}
    44  		switch b.goos {
    45  		case "android", "nacl":
    46  			// Cross compile.
    47  		default:
    48  			// If we are building, for example, linux/386 on a linux/amd64 machine we want to
    49  			// make sure that the whole build is done as a if this were compiled on a real
    50  			// linux/386 machine. In other words, we want to not do a cross compilation build.
    51  			// To do this we set GOHOSTOS and GOHOSTARCH to override the detection in make.bash.
    52  			//
    53  			// The exception to this rule is when we are doing nacl/android builds. These are by
    54  			// definition always cross compilation, and we have support built into cmd/go to be
    55  			// able to handle this case.
    56  			e = append(e, "GOHOSTOS="+b.goos, "GOHOSTARCH="+b.goarch)
    57  		}
    58  	}
    59  
    60  	for _, k := range extraEnv() {
    61  		if s, ok := getenvOk(k); ok {
    62  			e = append(e, k+"="+s)
    63  		}
    64  	}
    65  	return e
    66  }
    67  
    68  func (b *Builder) envvWindows() []string {
    69  	var start map[string]string
    70  	if *buildTool == "go" {
    71  		start = map[string]string{
    72  			"GOOS":         b.goos,
    73  			"GOHOSTOS":     b.goos,
    74  			"GOARCH":       b.goarch,
    75  			"GOHOSTARCH":   b.goarch,
    76  			"GOROOT_FINAL": `c:\go`,
    77  			"GOBUILDEXIT":  "1", // exit all.bat with completion status.
    78  		}
    79  	}
    80  
    81  	for _, name := range extraEnv() {
    82  		if s, ok := getenvOk(name); ok {
    83  			start[name] = s
    84  		}
    85  	}
    86  	if b.goos == "windows" {
    87  		switch b.goarch {
    88  		case "amd64":
    89  			start["PATH"] = `c:\TDM-GCC-64\bin;` + start["PATH"]
    90  		case "386":
    91  			start["PATH"] = `c:\TDM-GCC-32\bin;` + start["PATH"]
    92  		}
    93  	}
    94  	skip := map[string]bool{
    95  		"GOBIN":   true,
    96  		"GOPATH":  true,
    97  		"GOROOT":  true,
    98  		"INCLUDE": true,
    99  		"LIB":     true,
   100  	}
   101  	var e []string
   102  	for name, v := range start {
   103  		e = append(e, name+"="+v)
   104  		skip[name] = true
   105  	}
   106  	for _, kv := range os.Environ() {
   107  		s := strings.SplitN(kv, "=", 2)
   108  		name := strings.ToUpper(s[0])
   109  		switch {
   110  		case name == "":
   111  			// variables, like "=C:=C:\", just copy them
   112  			e = append(e, kv)
   113  		case !skip[name]:
   114  			e = append(e, kv)
   115  			skip[name] = true
   116  		}
   117  	}
   118  	return e
   119  }
   120  
   121  // setup for a goEnv clones the main go repo to workpath/go at the provided hash
   122  // and returns the path workpath/go/src, the location of all go build scripts.
   123  func (env *goEnv) setup(repo *Repo, workpath, hash string, envv []string) (string, error) {
   124  	goworkpath := filepath.Join(workpath, "go")
   125  	if err := repo.Export(goworkpath, hash); err != nil {
   126  		return "", fmt.Errorf("error exporting repository: %s", err)
   127  	}
   128  	// Write out VERSION file if it does not already exist.
   129  	vFile := filepath.Join(goworkpath, "VERSION")
   130  	if _, err := os.Stat(vFile); os.IsNotExist(err) {
   131  		if err := ioutil.WriteFile(vFile, []byte(hash), 0644); err != nil {
   132  			return "", fmt.Errorf("error writing VERSION file: %s", err)
   133  		}
   134  	}
   135  	return filepath.Join(goworkpath, "src"), nil
   136  }
   137  
   138  // gccgoEnv represents the builderEnv for the gccgo compiler.
   139  type gccgoEnv struct{}
   140  
   141  // setup for a gccgoEnv clones the gofrontend repo to workpath/go at the hash
   142  // and clones the latest GCC branch to repo.Path/gcc. The gccgo sources are
   143  // replaced with the updated sources in the gofrontend repo and gcc gets
   144  // gets configured and built in workpath/gcc-objdir. The path to
   145  // workpath/gcc-objdir is returned.
   146  func (env *gccgoEnv) setup(repo *Repo, workpath, hash string, envv []string) (string, error) {
   147  	gccpath := filepath.Join(repo.Path, "gcc")
   148  
   149  	// get a handle to Git vcs.Cmd for pulling down GCC from the mirror.
   150  	git := vcs.ByCmd("git")
   151  
   152  	// only pull down gcc if we don't have a local copy.
   153  	if _, err := os.Stat(gccpath); err != nil {
   154  		if err := timeout(*cmdTimeout, func() error {
   155  			// pull down a working copy of GCC.
   156  
   157  			cloneCmd := []string{
   158  				"clone",
   159  				// This is just a guess since there are ~6000 commits to
   160  				// GCC per year. It's likely there will be enough history
   161  				// to cross-reference the Gofrontend commit against GCC.
   162  				// The disadvantage would be if the commit being built is more than
   163  				// a year old; in this case, the user should make a clone that has
   164  				// the full history.
   165  				"--depth", "6000",
   166  				// We only care about the master branch.
   167  				"--branch", "master", "--single-branch",
   168  				*gccPath,
   169  			}
   170  
   171  			// Clone Kind			Clone Time(Dry run)	Clone Size
   172  			// ---------------------------------------------------------------
   173  			// Full Clone			10 - 15 min		2.2 GiB
   174  			// Master Branch		2 - 3 min		1.5 GiB
   175  			// Full Clone(shallow)		1 min			900 MiB
   176  			// Master Branch(shallow)	40 sec			900 MiB
   177  			//
   178  			// The shallow clones have the same size, which is expected,
   179  			// but the full shallow clone will only have 6000 commits
   180  			// spread across all branches.  There are ~50 branches.
   181  			return run(exec.Command("git", cloneCmd...), runEnv(envv), allOutput(os.Stdout), runDir(repo.Path))
   182  		}); err != nil {
   183  			return "", err
   184  		}
   185  	}
   186  
   187  	if err := git.Download(gccpath); err != nil {
   188  		return "", err
   189  	}
   190  
   191  	// get the modified files for this commit.
   192  
   193  	var buf bytes.Buffer
   194  	if err := run(exec.Command("hg", "status", "--no-status", "--change", hash),
   195  		allOutput(&buf), runDir(repo.Path), runEnv(envv)); err != nil {
   196  		return "", fmt.Errorf("Failed to find the modified files for %s: %s", hash, err)
   197  	}
   198  	modifiedFiles := strings.Split(buf.String(), "\n")
   199  	var isMirrored bool
   200  	for _, f := range modifiedFiles {
   201  		if strings.HasPrefix(f, "go/") || strings.HasPrefix(f, "libgo/") {
   202  			isMirrored = true
   203  			break
   204  		}
   205  	}
   206  
   207  	// use git log to find the corresponding commit to sync to in the gcc mirror.
   208  	// If the files modified in the gofrontend are mirrored to gcc, we expect a
   209  	// commit with a similar description in the gcc mirror. If the files modified are
   210  	// not mirrored, e.g. in support/, we can sync to the most recent gcc commit that
   211  	// occurred before those files were modified to verify gccgo's status at that point.
   212  	logCmd := []string{
   213  		"log",
   214  		"-1",
   215  		"--format=%H",
   216  	}
   217  	var errMsg string
   218  	if isMirrored {
   219  		commitDesc, err := repo.Master.VCS.LogAtRev(repo.Path, hash, "{desc|firstline|escape}")
   220  		if err != nil {
   221  			return "", err
   222  		}
   223  
   224  		quotedDesc := regexp.QuoteMeta(string(commitDesc))
   225  		logCmd = append(logCmd, "--grep", quotedDesc, "--regexp-ignore-case", "--extended-regexp")
   226  		errMsg = fmt.Sprintf("Failed to find a commit with a similar description to '%s'", string(commitDesc))
   227  	} else {
   228  		commitDate, err := repo.Master.VCS.LogAtRev(repo.Path, hash, "{date|rfc3339date}")
   229  		if err != nil {
   230  			return "", err
   231  		}
   232  
   233  		logCmd = append(logCmd, "--before", string(commitDate))
   234  		errMsg = fmt.Sprintf("Failed to find a commit before '%s'", string(commitDate))
   235  	}
   236  
   237  	buf.Reset()
   238  	if err := run(exec.Command("git", logCmd...), runEnv(envv), allOutput(&buf), runDir(gccpath)); err != nil {
   239  		return "", fmt.Errorf("%s: %s", errMsg, err)
   240  	}
   241  	gccRev := buf.String()
   242  	if gccRev == "" {
   243  		return "", fmt.Errorf(errMsg)
   244  	}
   245  
   246  	// checkout gccRev
   247  	// TODO(cmang): Fix this to work in parallel mode.
   248  	if err := run(exec.Command("git", "reset", "--hard", strings.TrimSpace(gccRev)), runEnv(envv), runDir(gccpath)); err != nil {
   249  		return "", fmt.Errorf("Failed to checkout commit at revision %s: %s", gccRev, err)
   250  	}
   251  
   252  	// make objdir to work in
   253  	gccobjdir := filepath.Join(workpath, "gcc-objdir")
   254  	if err := os.Mkdir(gccobjdir, mkdirPerm); err != nil {
   255  		return "", err
   256  	}
   257  
   258  	// configure GCC with substituted gofrontend and libgo
   259  	if err := run(exec.Command(filepath.Join(gccpath, "configure"),
   260  		"--enable-languages=c,c++,go",
   261  		"--disable-bootstrap",
   262  		"--disable-multilib",
   263  	), runEnv(envv), runDir(gccobjdir)); err != nil {
   264  		return "", fmt.Errorf("Failed to configure GCC: %v", err)
   265  	}
   266  
   267  	// build gcc
   268  	if err := run(exec.Command("make", *gccOpts), runTimeout(*buildTimeout), runEnv(envv), runDir(gccobjdir)); err != nil {
   269  		return "", fmt.Errorf("Failed to build GCC: %s", err)
   270  	}
   271  
   272  	return gccobjdir, nil
   273  }
   274  
   275  func getenvOk(k string) (v string, ok bool) {
   276  	v = os.Getenv(k)
   277  	if v != "" {
   278  		return v, true
   279  	}
   280  	keq := k + "="
   281  	for _, kv := range os.Environ() {
   282  		if kv == keq {
   283  			return "", true
   284  		}
   285  	}
   286  	return "", false
   287  }
   288  
   289  // extraEnv returns environment variables that need to be copied from
   290  // the gobuilder's environment to the envv of its subprocesses.
   291  func extraEnv() []string {
   292  	extra := []string{
   293  		"GOARM",
   294  		"GO386",
   295  		"CGO_ENABLED",
   296  		"CC",
   297  		"CC_FOR_TARGET",
   298  		"PATH",
   299  		"TMPDIR",
   300  		"USER",
   301  	}
   302  	if runtime.GOOS == "plan9" {
   303  		extra = append(extra, "objtype", "cputype", "path")
   304  	}
   305  	return extra
   306  }