@@ -53,37 +53,55 @@ import (
53
53
"time"
54
54
)
55
55
56
- // Apply ExecCmdOption before process start, used for customize SysProcAttr.
57
- type ExecCmdOption func (cmd * exec.Cmd )
58
-
59
56
// Cmd represents an external command, similar to the Go built-in os/exec.Cmd.
60
57
// A Cmd cannot be reused after calling Start. Exported fields are read-only and
61
58
// should not be modified, except Env which can be set before calling Start.
62
59
// To create a new Cmd, call NewCmd or NewCmdOptions.
63
60
type Cmd struct {
64
- Name string
65
- Args []string
66
- Env []string
67
- Dir string
68
- Options []ExecCmdOption
69
- Stdout chan string // streaming STDOUT if enabled, else nil (see Options)
70
- Stderr chan string // streaming STDERR if enabled, else nil (see Options)
61
+ // Name of binary (command) to run. This is the only required value.
62
+ // No path expansion is done.
63
+ // Used to set underlying os/exec.Cmd.Path.
64
+ Name string
65
+
66
+ // Commands line arguments passed to the command.
67
+ // Args are optional.
68
+ // Used to set underlying os/exec.Cmd.Args.
69
+ Args []string
70
+
71
+ // Environment variables set before running the command.
72
+ // Env is optional.
73
+ Env []string
74
+
75
+ // Current working directory from which to run the command.
76
+ // Dir is optional; default is current working directory
77
+ // of calling process.
78
+ // Used to set underlying os/exec.Cmd.Dir.
79
+ Dir string
80
+
81
+ // Stdout sets streaming STDOUT if enabled, else nil (see Options).
82
+ Stdout chan string
83
+
84
+ // Stderr sets streaming STDERR if enabled, else nil (see Options).
85
+ Stderr chan string
86
+
71
87
* sync.Mutex
72
- started bool // cmd.Start called, no error
73
- stopped bool // Stop called
74
- done bool // run() done
75
- final bool // status finalized in Status
76
- startTime time.Time // if started true
77
- stdoutBuf * OutputBuffer
78
- stderrBuf * OutputBuffer
79
- stdoutStream * OutputStream
80
- stderrStream * OutputStream
81
- status Status
82
- statusChan chan Status // nil until Start() called
83
- doneChan chan struct {} // closed when done running
88
+ started bool // cmd.Start called, no error
89
+ stopped bool // Stop called
90
+ done bool // run() done
91
+ final bool // status finalized in Status
92
+ startTime time.Time // if started true
93
+ stdoutBuf * OutputBuffer
94
+ stderrBuf * OutputBuffer
95
+ stdoutStream * OutputStream
96
+ stderrStream * OutputStream
97
+ status Status
98
+ statusChan chan Status // nil until Start() called
99
+ doneChan chan struct {} // closed when done running
100
+ beforeExecFuncs []func (cmd * exec.Cmd )
84
101
}
85
102
86
103
var (
104
+ // ErrNotStarted is returned by Stop if called before Start or StartWithStdin.
87
105
ErrNotStarted = errors .New ("command not running" )
88
106
)
89
107
@@ -134,14 +152,20 @@ type Options struct {
134
152
// faster and more efficient than polling Cmd.Status. The caller must read both
135
153
// streaming channels, else lines are dropped silently.
136
154
Streaming bool
155
+
156
+ // BeforeExec is a list of functions called immediately before starting
157
+ // the real command. These functions can be used to customize the underlying
158
+ // os/exec.Cmd. For example, to set SysProcAttr.
159
+ BeforeExec []func (cmd * exec.Cmd )
137
160
}
138
161
139
162
// NewCmdOptions creates a new Cmd with options. The command is not started
140
163
// until Start is called.
141
164
func NewCmdOptions (options Options , name string , args ... string ) * Cmd {
142
165
c := & Cmd {
143
- Name : name ,
144
- Args : args ,
166
+ Name : name ,
167
+ Args : args ,
168
+ // --
145
169
Mutex : & sync.Mutex {},
146
170
status : Status {
147
171
Cmd : name ,
@@ -167,6 +191,16 @@ func NewCmdOptions(options Options, name string, args ...string) *Cmd {
167
191
c .stderrStream = NewOutputStream (c .Stderr )
168
192
}
169
193
194
+ if len (options .BeforeExec ) > 0 {
195
+ c .beforeExecFuncs = []func (cmd * exec.Cmd ){}
196
+ for _ , f := range options .BeforeExec {
197
+ if f == nil {
198
+ continue
199
+ }
200
+ c .beforeExecFuncs = append (c .beforeExecFuncs , f )
201
+ }
202
+ }
203
+
170
204
return c
171
205
}
172
206
@@ -185,7 +219,14 @@ func (c *Cmd) Clone() *Cmd {
185
219
)
186
220
clone .Dir = c .Dir
187
221
clone .Env = c .Env
188
- clone .Options = c .Options
222
+
223
+ if len (c .beforeExecFuncs ) > 0 {
224
+ clone .beforeExecFuncs = make ([]func (cmd * exec.Cmd ), len (c .beforeExecFuncs ))
225
+ for i := range c .beforeExecFuncs {
226
+ clone .beforeExecFuncs [i ] = c .beforeExecFuncs [i ]
227
+ }
228
+ }
229
+
189
230
return clone
190
231
}
191
232
@@ -374,7 +415,9 @@ func (c *Cmd) run(in io.Reader) {
374
415
// is nil, use the current process' environment.
375
416
cmd .Env = c .Env
376
417
cmd .Dir = c .Dir
377
- for _ , f := range c .Options {
418
+
419
+ // Run all optional commands to customize underlying os/exe.Cmd.
420
+ for _ , f := range c .beforeExecFuncs {
378
421
f (cmd )
379
422
}
380
423
0 commit comments