@@ -12,29 +12,152 @@ import (
12
12
"github.com/magefile/mage/mg"
13
13
)
14
14
15
+ // runOptions is a set of options to be applied with ExecSh.
16
+ type runOptions struct {
17
+ cmd string
18
+ args []string
19
+ dir string
20
+ env map [string ]string
21
+ stderr , stdout io.Writer
22
+ }
23
+
24
+ // RunOpt applies an option to a runOptions set.
25
+ type RunOpt func (* runOptions )
26
+
27
+ // WithV sets stderr and stdout the standard streams
28
+ func WithV () RunOpt {
29
+ return func (options * runOptions ) {
30
+ options .stdout = os .Stdout
31
+ options .stderr = os .Stderr
32
+ }
33
+ }
34
+
35
+ // WithEnv sets the env passed in env vars.
36
+ func WithEnv (env map [string ]string ) RunOpt {
37
+ return func (options * runOptions ) {
38
+ if options .env == nil {
39
+ options .env = make (map [string ]string )
40
+ }
41
+ for k , v := range env {
42
+ options .env [k ] = v
43
+ }
44
+ }
45
+ }
46
+
47
+ // WithStderr sets the stderr stream.
48
+ func WithStderr (w io.Writer ) RunOpt {
49
+ return func (options * runOptions ) {
50
+ options .stderr = w
51
+ }
52
+ }
53
+
54
+ // WithStdout sets the stdout stream.
55
+ func WithStdout (w io.Writer ) RunOpt {
56
+ return func (options * runOptions ) {
57
+ options .stdout = w
58
+ }
59
+ }
60
+
61
+ // WithDir sets the working directory for the command.
62
+ func WithDir (dir string ) RunOpt {
63
+ return func (options * runOptions ) {
64
+ options .dir = dir
65
+ }
66
+ }
67
+
68
+ // WithArgs appends command arguments.
69
+ func WithArgs (args ... string ) RunOpt {
70
+ return func (options * runOptions ) {
71
+ if options .args == nil {
72
+ options .args = make ([]string , 0 , len (args ))
73
+ }
74
+ options .args = append (options .args , args ... )
75
+ }
76
+ }
77
+
78
+ // RunSh returns a function that calls ExecSh, only returning errors.
79
+ func RunSh (cmd string , options ... RunOpt ) func (args ... string ) error {
80
+ run := ExecSh (cmd , options ... )
81
+ return func (args ... string ) error {
82
+ _ , err := run ()
83
+ return err
84
+ }
85
+ }
86
+
87
+ // ExecSh returns a function that executes the command, piping its stdout and
88
+ // stderr according to the config options. If the command fails, it will return
89
+ // an error that, if returned from a target or mg.Deps call, will cause mage to
90
+ // exit with the same code as the command failed with.
91
+ //
92
+ // ExecSh takes a variable list of RunOpt objects to configure how the command
93
+ // is executed. See RunOpt docs for more details.
94
+ //
95
+ // Env vars configured on the command override the current environment variables
96
+ // set (which are also passed to the command). The cmd and args may include
97
+ // references to environment variables in $FOO format, in which case these will be
98
+ // expanded before the command is run.
99
+ //
100
+ // Ran reports if the command ran (rather than was not found or not executable).
101
+ // Code reports the exit code the command returned if it ran. If err == nil, ran
102
+ // is always true and code is always 0.
103
+ func ExecSh (cmd string , options ... RunOpt ) func (args ... string ) (bool , error ) {
104
+ opts := runOptions {
105
+ cmd : cmd ,
106
+ }
107
+ for _ , o := range options {
108
+ o (& opts )
109
+ }
110
+
111
+ if opts .stdout == nil && mg .Verbose () {
112
+ opts .stdout = os .Stdout
113
+ }
114
+
115
+ return func (args ... string ) (bool , error ) {
116
+ expand := func (s string ) string {
117
+ s2 , ok := opts .env [s ]
118
+ if ok {
119
+ return s2
120
+ }
121
+ return os .Getenv (s )
122
+ }
123
+ cmd = os .Expand (cmd , expand )
124
+ finalArgs := append (opts .args , args ... )
125
+ for i := range finalArgs {
126
+ finalArgs [i ] = os .Expand (finalArgs [i ], expand )
127
+ }
128
+ ran , code , err := run (opts .dir , opts .env , opts .stdout , opts .stderr , cmd , finalArgs ... )
129
+
130
+ if err == nil {
131
+ return ran , nil
132
+ }
133
+ if ran {
134
+ return ran , mg .Fatalf (code , `running "%s %s" failed with exit code %d` , cmd , strings .Join (args , " " ), code )
135
+ }
136
+ return ran , fmt .Errorf (`failed to run "%s %s: %v"` , cmd , strings .Join (args , " " ), err )
137
+ }
138
+ }
139
+
15
140
// RunCmd returns a function that will call Run with the given command. This is
16
141
// useful for creating command aliases to make your scripts easier to read, like
17
142
// this:
18
143
//
19
- // // in a helper file somewhere
20
- // var g0 = sh.RunCmd("go") // go is a keyword :(
144
+ // // in a helper file somewhere
145
+ // var g0 = sh.RunCmd("go") // go is a keyword :(
21
146
//
22
- // // somewhere in your main code
23
- // if err := g0("install", "github.com/gohugo/hugo"); err != nil {
24
- // return err
25
- // }
147
+ // // somewhere in your main code
148
+ // if err := g0("install", "github.com/gohugo/hugo"); err != nil {
149
+ // return err
150
+ // }
26
151
//
27
152
// Args passed to command get baked in as args to the command when you run it.
28
153
// Any args passed in when you run the returned function will be appended to the
29
154
// original args. For example, this is equivalent to the above:
30
155
//
31
- // var goInstall = sh.RunCmd("go", "install") goInstall("github.com/gohugo/hugo")
156
+ // var goInstall = sh.RunCmd("go", "install") goInstall("github.com/gohugo/hugo")
32
157
//
33
158
// RunCmd uses Exec underneath, so see those docs for more details.
34
159
func RunCmd (cmd string , args ... string ) func (args ... string ) error {
35
- return func (args2 ... string ) error {
36
- return Run (cmd , append (args , args2 ... )... )
37
- }
160
+ return RunSh (cmd , WithArgs (args ... ))
38
161
}
39
162
40
163
// OutCmd is like RunCmd except the command returns the output of the
@@ -47,45 +170,38 @@ func OutCmd(cmd string, args ...string) func(args ...string) (string, error) {
47
170
48
171
// Run is like RunWith, but doesn't specify any environment variables.
49
172
func Run (cmd string , args ... string ) error {
50
- return RunWith ( nil , cmd , args ... )
173
+ return RunSh ( cmd , WithArgs ( args ... ))( )
51
174
}
52
175
53
176
// RunV is like Run, but always sends the command's stdout to os.Stdout.
54
177
func RunV (cmd string , args ... string ) error {
55
- _ , err := Exec (nil , os .Stdout , os .Stderr , cmd , args ... )
56
- return err
178
+ return RunSh (cmd , WithV (), WithArgs (args ... ))()
57
179
}
58
180
59
181
// RunWith runs the given command, directing stderr to this program's stderr and
60
182
// printing stdout to stdout if mage was run with -v. It adds adds env to the
61
183
// environment variables for the command being run. Environment variables should
62
184
// be in the format name=value.
63
185
func RunWith (env map [string ]string , cmd string , args ... string ) error {
64
- var output io.Writer
65
- if mg .Verbose () {
66
- output = os .Stdout
67
- }
68
- _ , err := Exec (env , output , os .Stderr , cmd , args ... )
69
- return err
186
+ return RunSh (cmd , WithEnv (env ), WithArgs (args ... ))()
70
187
}
71
188
72
189
// RunWithV is like RunWith, but always sends the command's stdout to os.Stdout.
73
190
func RunWithV (env map [string ]string , cmd string , args ... string ) error {
74
- _ , err := Exec (env , os .Stdout , os .Stderr , cmd , args ... )
75
- return err
191
+ return RunSh (cmd , WithV (), WithEnv (env ), WithArgs (args ... ))()
76
192
}
77
193
78
194
// Output runs the command and returns the text from stdout.
79
195
func Output (cmd string , args ... string ) (string , error ) {
80
196
buf := & bytes.Buffer {}
81
- _ , err := Exec ( nil , buf , os .Stderr , cmd , args ... )
197
+ err := RunSh ( cmd , WithStderr ( os .Stderr ), WithStdout ( buf ), WithArgs ( args ... ))( )
82
198
return strings .TrimSuffix (buf .String (), "\n " ), err
83
199
}
84
200
85
201
// OutputWith is like RunWith, but returns what is written to stdout.
86
202
func OutputWith (env map [string ]string , cmd string , args ... string ) (string , error ) {
87
203
buf := & bytes.Buffer {}
88
- _ , err := Exec ( env , buf , os .Stderr , cmd , args ... )
204
+ err := RunSh ( cmd , WithEnv ( env ), WithStderr ( os .Stderr ), WithStdout ( buf ), WithArgs ( args ... ))( )
89
205
return strings .TrimSuffix (buf .String (), "\n " ), err
90
206
}
91
207
@@ -102,40 +218,23 @@ func OutputWith(env map[string]string, cmd string, args ...string) (string, erro
102
218
// Code reports the exit code the command returned if it ran. If err == nil, ran
103
219
// is always true and code is always 0.
104
220
func Exec (env map [string ]string , stdout , stderr io.Writer , cmd string , args ... string ) (ran bool , err error ) {
105
- expand := func (s string ) string {
106
- s2 , ok := env [s ]
107
- if ok {
108
- return s2
109
- }
110
- return os .Getenv (s )
111
- }
112
- cmd = os .Expand (cmd , expand )
113
- for i := range args {
114
- args [i ] = os .Expand (args [i ], expand )
115
- }
116
- ran , code , err := run (env , stdout , stderr , cmd , args ... )
117
- if err == nil {
118
- return true , nil
119
- }
120
- if ran {
121
- return ran , mg .Fatalf (code , `running "%s %s" failed with exit code %d` , cmd , strings .Join (args , " " ), code )
122
- }
123
- return ran , fmt .Errorf (`failed to run "%s %s: %v"` , cmd , strings .Join (args , " " ), err )
221
+ return ExecSh (cmd , WithArgs (args ... ), WithStderr (stderr ), WithStdout (stdout ), WithEnv (env ))()
124
222
}
125
223
126
- func run (env map [string ]string , stdout , stderr io.Writer , cmd string , args ... string ) (ran bool , code int , err error ) {
224
+ func run (dir string , env map [string ]string , stdout , stderr io.Writer , cmd string , args ... string ) (ran bool , code int , err error ) {
127
225
c := exec .Command (cmd , args ... )
128
226
c .Env = os .Environ ()
129
227
for k , v := range env {
130
228
c .Env = append (c .Env , k + "=" + v )
131
229
}
230
+ c .Dir = dir
132
231
c .Stderr = stderr
133
232
c .Stdout = stdout
134
233
c .Stdin = os .Stdin
135
234
136
- var quoted []string
235
+ var quoted []string
137
236
for i := range args {
138
- quoted = append (quoted , fmt .Sprintf ("%q" , args [i ]));
237
+ quoted = append (quoted , fmt .Sprintf ("%q" , args [i ]))
139
238
}
140
239
// To protect against logging from doing exec in global variables
141
240
if mg .Verbose () {
@@ -144,6 +243,7 @@ func run(env map[string]string, stdout, stderr io.Writer, cmd string, args ...st
144
243
err = c .Run ()
145
244
return CmdRan (err ), ExitStatus (err ), err
146
245
}
246
+
147
247
// CmdRan examines the error to determine if it was generated as a result of a
148
248
// command running via os/exec.Command. If the error is nil, or the command ran
149
249
// (even if it exited with a non-zero exit code), CmdRan reports true. If the
0 commit comments