in sandbox.go [407:525]
func sandboxBuild(ctx context.Context, tmpDir string, in []byte, vet bool) (br *buildResult, err error) {
start := time.Now()
defer func() {
status := "success"
if err != nil {
status = "error"
}
// Ignore error. The only error can be invalid tag key or value
// length, which we know are safe.
stats.RecordWithTags(ctx, []tag.Mutator{tag.Upsert(kGoBuildSuccess, status)},
mGoBuildLatency.M(float64(time.Since(start))/float64(time.Millisecond)))
}()
files, err := splitFiles(in)
if err != nil {
return &buildResult{errorMessage: err.Error()}, nil
}
br = new(buildResult)
defer br.cleanup()
var buildPkgArg = "."
if files.Num() == 1 && len(files.Data(progName)) > 0 {
buildPkgArg = progName
src := files.Data(progName)
if code := getTestProg(src); code != nil {
br.testParam = "-test.v"
files.AddFile(progName, code)
}
}
if !files.Contains("go.mod") {
files.AddFile("go.mod", []byte("module play\n"))
}
for f, src := range files.m {
// Before multi-file support we required that the
// program be in package main, so continue to do that
// for now. But permit anything in subdirectories to have other
// packages.
if !strings.Contains(f, "/") {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, f, src, parser.PackageClauseOnly)
if err == nil && f.Name.Name != "main" {
return &buildResult{errorMessage: "package name must be main"}, nil
}
}
in := filepath.Join(tmpDir, f)
if strings.Contains(f, "/") {
if err := os.MkdirAll(filepath.Dir(in), 0755); err != nil {
return nil, err
}
}
if err := ioutil.WriteFile(in, src, 0644); err != nil {
return nil, fmt.Errorf("error creating temp file %q: %v", in, err)
}
}
br.exePath = filepath.Join(tmpDir, "a.out")
goCache := filepath.Join(tmpDir, "gocache")
cmd := exec.Command("/usr/local/go-faketime/bin/go", "build", "-o", br.exePath, "-tags=faketime")
cmd.Dir = tmpDir
cmd.Env = []string{"GOOS=linux", "GOARCH=amd64", "GOROOT=/usr/local/go-faketime"}
cmd.Env = append(cmd.Env, "GOCACHE="+goCache)
cmd.Env = append(cmd.Env, "CGO_ENABLED=0")
// Create a GOPATH just for modules to be downloaded
// into GOPATH/pkg/mod.
cmd.Args = append(cmd.Args, "-modcacherw")
cmd.Args = append(cmd.Args, "-mod=mod")
br.goPath, err = ioutil.TempDir("", "gopath")
if err != nil {
log.Printf("error creating temp directory: %v", err)
return nil, fmt.Errorf("error creating temp directory: %v", err)
}
cmd.Env = append(cmd.Env, "GO111MODULE=on", "GOPROXY="+playgroundGoproxy())
cmd.Args = append(cmd.Args, buildPkgArg)
cmd.Env = append(cmd.Env, "GOPATH="+br.goPath)
out := &bytes.Buffer{}
cmd.Stderr, cmd.Stdout = out, out
if err := cmd.Start(); err != nil {
return nil, fmt.Errorf("error starting go build: %v", err)
}
ctx, cancel := context.WithTimeout(ctx, maxBuildTime)
defer cancel()
if err := internal.WaitOrStop(ctx, cmd, os.Interrupt, 250*time.Millisecond); err != nil {
if errors.Is(err, context.DeadlineExceeded) {
br.errorMessage = fmt.Sprintln(goBuildTimeoutError)
} else if ee := (*exec.ExitError)(nil); !errors.As(err, &ee) {
log.Printf("error building program: %v", err)
return nil, fmt.Errorf("error building go source: %v", err)
}
// Return compile errors to the user.
// Rewrite compiler errors to strip the tmpDir name.
br.errorMessage = br.errorMessage + strings.Replace(string(out.Bytes()), tmpDir+"/", "", -1)
// "go build", invoked with a file name, puts this odd
// message before any compile errors; strip it.
br.errorMessage = strings.Replace(br.errorMessage, "# command-line-arguments\n", "", 1)
return br, nil
}
const maxBinarySize = 100 << 20 // copied from sandbox backend; TODO: unify?
if fi, err := os.Stat(br.exePath); err != nil || fi.Size() == 0 || fi.Size() > maxBinarySize {
if err != nil {
return nil, fmt.Errorf("failed to stat binary: %v", err)
}
return nil, fmt.Errorf("invalid binary size %d", fi.Size())
}
if vet {
// TODO: do this concurrently with the execution to reduce latency.
br.vetOut, err = vetCheckInDir(ctx, tmpDir, br.goPath)
if err != nil {
return nil, fmt.Errorf("running vet: %v", err)
}
}
return br, nil
}