cmd.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. package sh
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "log"
  7. "os"
  8. "os/exec"
  9. "strings"
  10. "github.com/magefile/mage/mg"
  11. )
  12. // RunCmd returns a function that will call Run with the given command. This is
  13. // useful for creating command aliases to make your scripts easier to read, like
  14. // this:
  15. //
  16. // // in a helper file somewhere
  17. // var g0 = sh.RunCmd("go") // go is a keyword :(
  18. //
  19. // // somewhere in your main code
  20. // if err := g0("install", "github.com/gohugo/hugo"); err != nil {
  21. // return err
  22. // }
  23. //
  24. // Args passed to command get baked in as args to the command when you run it.
  25. // Any args passed in when you run the returned function will be appended to the
  26. // original args. For example, this is equivalent to the above:
  27. //
  28. // var goInstall = sh.RunCmd("go", "install") goInstall("github.com/gohugo/hugo")
  29. //
  30. // RunCmd uses Exec underneath, so see those docs for more details.
  31. func RunCmd(cmd string, args ...string) func(args ...string) error {
  32. return func(args2 ...string) error {
  33. return Run(cmd, append(args, args2...)...)
  34. }
  35. }
  36. // OutCmd is like RunCmd except the command returns the output of the
  37. // command.
  38. func OutCmd(cmd string, args ...string) func(args ...string) (string, error) {
  39. return func(args2 ...string) (string, error) {
  40. return Output(cmd, append(args, args2...)...)
  41. }
  42. }
  43. // Run is like RunWith, but doesn't specify any environment variables.
  44. func Run(cmd string, args ...string) error {
  45. return RunWith(nil, cmd, args...)
  46. }
  47. // RunV is like Run, but always sends the command's stdout to os.Stdout.
  48. func RunV(cmd string, args ...string) error {
  49. _, err := Exec(nil, os.Stdout, os.Stderr, cmd, args...)
  50. return err
  51. }
  52. // RunWith runs the given command, directing stderr to this program's stderr and
  53. // printing stdout to stdout if mage was run with -v. It adds adds env to the
  54. // environment variables for the command being run. Environment variables should
  55. // be in the format name=value.
  56. func RunWith(env map[string]string, cmd string, args ...string) error {
  57. var output io.Writer
  58. if mg.Verbose() {
  59. output = os.Stdout
  60. }
  61. _, err := Exec(env, output, os.Stderr, cmd, args...)
  62. return err
  63. }
  64. // Output runs the command and returns the text from stdout.
  65. func Output(cmd string, args ...string) (string, error) {
  66. buf := &bytes.Buffer{}
  67. _, err := Exec(nil, buf, os.Stderr, cmd, args...)
  68. return strings.TrimSuffix(buf.String(), "\n"), err
  69. }
  70. // OutputWith is like RunWith, ubt returns what is written to stdout.
  71. func OutputWith(env map[string]string, cmd string, args ...string) (string, error) {
  72. buf := &bytes.Buffer{}
  73. _, err := Exec(env, buf, os.Stderr, cmd, args...)
  74. return strings.TrimSuffix(buf.String(), "\n"), err
  75. }
  76. // Exec executes the command, piping its stderr to mage's stderr and
  77. // piping its stdout to the given writer. If the command fails, it will return
  78. // an error that, if returned from a target or mg.Deps call, will cause mage to
  79. // exit with the same code as the command failed with. Env is a list of
  80. // environment variables to set when running the command, these override the
  81. // current environment variables set (which are also passed to the command). cmd
  82. // and args may include references to environment variables in $FOO format, in
  83. // which case these will be expanded before the command is run.
  84. //
  85. // Ran reports if the command ran (rather than was not found or not executable).
  86. // Code reports the exit code the command returned if it ran. If err == nil, ran
  87. // is always true and code is always 0.
  88. func Exec(env map[string]string, stdout, stderr io.Writer, cmd string, args ...string) (ran bool, err error) {
  89. expand := func(s string) string {
  90. s2, ok := env[s]
  91. if ok {
  92. return s2
  93. }
  94. return os.Getenv(s)
  95. }
  96. cmd = os.Expand(cmd, expand)
  97. for i := range args {
  98. args[i] = os.Expand(args[i], expand)
  99. }
  100. ran, code, err := run(env, stdout, stderr, cmd, args...)
  101. if err == nil {
  102. return true, nil
  103. }
  104. if ran {
  105. return ran, mg.Fatalf(code, `running "%s %s" failed with exit code %d`, cmd, strings.Join(args, " "), code)
  106. }
  107. return ran, fmt.Errorf(`failed to run "%s %s: %v"`, cmd, strings.Join(args, " "), err)
  108. }
  109. func run(env map[string]string, stdout, stderr io.Writer, cmd string, args ...string) (ran bool, code int, err error) {
  110. c := exec.Command(cmd, args...)
  111. c.Env = os.Environ()
  112. for k, v := range env {
  113. c.Env = append(c.Env, k+"="+v)
  114. }
  115. c.Stderr = stderr
  116. c.Stdout = stdout
  117. c.Stdin = os.Stdin
  118. log.Println("exec:", cmd, strings.Join(args, " "))
  119. err = c.Run()
  120. return cmdRan(err), ExitStatus(err), err
  121. }
  122. func cmdRan(err error) bool {
  123. if err == nil {
  124. return true
  125. }
  126. ee, ok := err.(*exec.ExitError)
  127. if ok {
  128. return ee.Exited()
  129. }
  130. return false
  131. }
  132. type exitStatus interface {
  133. ExitStatus() int
  134. }
  135. // ExitStatus returns the exit status of the error if it is an exec.ExitError
  136. // or if it implements ExitStatus() int.
  137. // 0 if it is nil or 1 if it is a different error.
  138. func ExitStatus(err error) int {
  139. if err == nil {
  140. return 0
  141. }
  142. if e, ok := err.(exitStatus); ok {
  143. return e.ExitStatus()
  144. }
  145. if e, ok := err.(*exec.ExitError); ok {
  146. if ex, ok := e.Sys().(exitStatus); ok {
  147. return ex.ExitStatus()
  148. }
  149. }
  150. return 1
  151. }