mirror of
https://github.com/golang/go
synced 2024-11-19 02:54:42 -07:00
f8bfb4ee30
Seems like os.Environ() is necessary after all. My bad for not testing my earlier change on Windows. I'm not able to reproduce the behavior in my test, so it's not really testing the crash correctly. Fixes golang/go#38062 Change-Id: Ied45bbf572023a9dcd5d020db49bf3e95c824602 Reviewed-on: https://go-review.googlesource.com/c/tools/+/226370 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Heschi Kreinick <heschi@google.com>
134 lines
3.8 KiB
Go
134 lines
3.8 KiB
Go
// Copyright 2020 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 gocommand is a helper for calling the go command.
|
|
package gocommand
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// An Invocation represents a call to the go command.
|
|
type Invocation struct {
|
|
Verb string
|
|
Args []string
|
|
BuildFlags []string
|
|
Env []string
|
|
WorkingDir string
|
|
Logf func(format string, args ...interface{})
|
|
}
|
|
|
|
// Run runs the invocation, returning its stdout and an error suitable for
|
|
// human consumption, including stderr.
|
|
func (i *Invocation) Run(ctx context.Context) (*bytes.Buffer, error) {
|
|
stdout, _, friendly, _ := i.RunRaw(ctx)
|
|
return stdout, friendly
|
|
}
|
|
|
|
// RunRaw is like RunPiped, but also returns the raw stderr and error for callers
|
|
// that want to do low-level error handling/recovery.
|
|
func (i *Invocation) RunRaw(ctx context.Context) (stdout *bytes.Buffer, stderr *bytes.Buffer, friendlyError error, rawError error) {
|
|
stdout = &bytes.Buffer{}
|
|
stderr = &bytes.Buffer{}
|
|
rawError = i.RunPiped(ctx, stdout, stderr)
|
|
if rawError != nil {
|
|
// Check for 'go' executable not being found.
|
|
if ee, ok := rawError.(*exec.Error); ok && ee.Err == exec.ErrNotFound {
|
|
friendlyError = fmt.Errorf("go command required, not found: %v", ee)
|
|
}
|
|
if ctx.Err() != nil {
|
|
friendlyError = ctx.Err()
|
|
}
|
|
friendlyError = fmt.Errorf("err: %v: stderr: %s", rawError, stderr)
|
|
}
|
|
return
|
|
}
|
|
|
|
// RunPiped is like Run, but relies on the given stdout/stderr
|
|
func (i *Invocation) RunPiped(ctx context.Context, stdout, stderr io.Writer) error {
|
|
log := i.Logf
|
|
if log == nil {
|
|
log = func(string, ...interface{}) {}
|
|
}
|
|
|
|
goArgs := []string{i.Verb}
|
|
switch i.Verb {
|
|
case "mod":
|
|
// mod needs the sub-verb before build flags.
|
|
goArgs = append(goArgs, i.Args[0])
|
|
goArgs = append(goArgs, i.BuildFlags...)
|
|
goArgs = append(goArgs, i.Args[1:]...)
|
|
case "env":
|
|
// env doesn't take build flags.
|
|
goArgs = append(goArgs, i.Args...)
|
|
default:
|
|
goArgs = append(goArgs, i.BuildFlags...)
|
|
goArgs = append(goArgs, i.Args...)
|
|
}
|
|
cmd := exec.Command("go", goArgs...)
|
|
cmd.Stdout = stdout
|
|
cmd.Stderr = stderr
|
|
// On darwin the cwd gets resolved to the real path, which breaks anything that
|
|
// expects the working directory to keep the original path, including the
|
|
// go command when dealing with modules.
|
|
// The Go stdlib has a special feature where if the cwd and the PWD are the
|
|
// same node then it trusts the PWD, so by setting it in the env for the child
|
|
// process we fix up all the paths returned by the go command.
|
|
cmd.Env = append(os.Environ(), i.Env...)
|
|
if i.WorkingDir != "" {
|
|
cmd.Env = append(cmd.Env, "PWD="+i.WorkingDir)
|
|
cmd.Dir = i.WorkingDir
|
|
}
|
|
|
|
defer func(start time.Time) { log("%s for %v", time.Since(start), cmdDebugStr(cmd)) }(time.Now())
|
|
|
|
return runCmdContext(ctx, cmd)
|
|
}
|
|
|
|
// runCmdContext is like exec.CommandContext except it sends os.Interrupt
|
|
// before os.Kill.
|
|
func runCmdContext(ctx context.Context, cmd *exec.Cmd) error {
|
|
if err := cmd.Start(); err != nil {
|
|
return err
|
|
}
|
|
resChan := make(chan error, 1)
|
|
go func() {
|
|
resChan <- cmd.Wait()
|
|
}()
|
|
|
|
select {
|
|
case err := <-resChan:
|
|
return err
|
|
case <-ctx.Done():
|
|
}
|
|
// Cancelled. Interrupt and see if it ends voluntarily.
|
|
cmd.Process.Signal(os.Interrupt)
|
|
select {
|
|
case err := <-resChan:
|
|
return err
|
|
case <-time.After(time.Second):
|
|
}
|
|
// Didn't shut down in response to interrupt. Kill it hard.
|
|
cmd.Process.Kill()
|
|
return <-resChan
|
|
}
|
|
|
|
func cmdDebugStr(cmd *exec.Cmd) string {
|
|
env := make(map[string]string)
|
|
for _, kv := range cmd.Env {
|
|
split := strings.Split(kv, "=")
|
|
k, v := split[0], split[1]
|
|
env[k] = v
|
|
}
|
|
|
|
return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v GOPROXY=%v PWD=%v go %v", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["GOPROXY"], env["PWD"], cmd.Args)
|
|
}
|