You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

invoke.go 3.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. // Package gocommand is a helper for calling the go command.
  2. package gocommand
  3. import (
  4. "bytes"
  5. "context"
  6. "fmt"
  7. "io"
  8. "os"
  9. "os/exec"
  10. "strings"
  11. "time"
  12. )
  13. // An Invocation represents a call to the go command.
  14. type Invocation struct {
  15. Verb string
  16. Args []string
  17. BuildFlags []string
  18. Env []string
  19. WorkingDir string
  20. Logf func(format string, args ...interface{})
  21. }
  22. // Run runs the invocation, returning its stdout and an error suitable for
  23. // human consumption, including stderr.
  24. func (i *Invocation) Run(ctx context.Context) (*bytes.Buffer, error) {
  25. stdout, _, friendly, _ := i.RunRaw(ctx)
  26. return stdout, friendly
  27. }
  28. // RunRaw is like RunPiped, but also returns the raw stderr and error for callers
  29. // that want to do low-level error handling/recovery.
  30. func (i *Invocation) RunRaw(ctx context.Context) (stdout *bytes.Buffer, stderr *bytes.Buffer, friendlyError error, rawError error) {
  31. stdout = &bytes.Buffer{}
  32. stderr = &bytes.Buffer{}
  33. rawError = i.RunPiped(ctx, stdout, stderr)
  34. if rawError != nil {
  35. // Check for 'go' executable not being found.
  36. if ee, ok := rawError.(*exec.Error); ok && ee.Err == exec.ErrNotFound {
  37. friendlyError = fmt.Errorf("go command required, not found: %v", ee)
  38. }
  39. if ctx.Err() != nil {
  40. friendlyError = ctx.Err()
  41. }
  42. friendlyError = fmt.Errorf("err: %v: stderr: %s", rawError, stderr)
  43. }
  44. return
  45. }
  46. // RunPiped is like Run, but relies on the given stdout/stderr
  47. func (i *Invocation) RunPiped(ctx context.Context, stdout, stderr io.Writer) error {
  48. log := i.Logf
  49. if log == nil {
  50. log = func(string, ...interface{}) {}
  51. }
  52. goArgs := []string{i.Verb}
  53. switch i.Verb {
  54. case "mod":
  55. // mod needs the sub-verb before build flags.
  56. goArgs = append(goArgs, i.Args[0])
  57. goArgs = append(goArgs, i.BuildFlags...)
  58. goArgs = append(goArgs, i.Args[1:]...)
  59. case "env":
  60. // env doesn't take build flags.
  61. goArgs = append(goArgs, i.Args...)
  62. default:
  63. goArgs = append(goArgs, i.BuildFlags...)
  64. goArgs = append(goArgs, i.Args...)
  65. }
  66. cmd := exec.Command("go", goArgs...)
  67. cmd.Stdout = stdout
  68. cmd.Stderr = stderr
  69. // On darwin the cwd gets resolved to the real path, which breaks anything that
  70. // expects the working directory to keep the original path, including the
  71. // go command when dealing with modules.
  72. // The Go stdlib has a special feature where if the cwd and the PWD are the
  73. // same node then it trusts the PWD, so by setting it in the env for the child
  74. // process we fix up all the paths returned by the go command.
  75. cmd.Env = append(append([]string{}, i.Env...), "PWD="+i.WorkingDir)
  76. cmd.Dir = i.WorkingDir
  77. defer func(start time.Time) { log("%s for %v", time.Since(start), cmdDebugStr(cmd)) }(time.Now())
  78. return runCmdContext(ctx, cmd)
  79. }
  80. // runCmdContext is like exec.CommandContext except it sends os.Interrupt
  81. // before os.Kill.
  82. func runCmdContext(ctx context.Context, cmd *exec.Cmd) error {
  83. if err := cmd.Start(); err != nil {
  84. return err
  85. }
  86. resChan := make(chan error, 1)
  87. go func() {
  88. resChan <- cmd.Wait()
  89. }()
  90. select {
  91. case err := <-resChan:
  92. return err
  93. case <-ctx.Done():
  94. }
  95. // Cancelled. Interrupt and see if it ends voluntarily.
  96. cmd.Process.Signal(os.Interrupt)
  97. select {
  98. case err := <-resChan:
  99. return err
  100. case <-time.After(time.Second):
  101. }
  102. // Didn't shut down in response to interrupt. Kill it hard.
  103. cmd.Process.Kill()
  104. return <-resChan
  105. }
  106. func cmdDebugStr(cmd *exec.Cmd) string {
  107. env := make(map[string]string)
  108. for _, kv := range cmd.Env {
  109. split := strings.Split(kv, "=")
  110. k, v := split[0], split[1]
  111. env[k] = v
  112. }
  113. 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)
  114. }