123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126 |
- // 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(append([]string{}, i.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)
- }
|