@@ -53,37 +53,55 @@ import (
5353 "time"
5454)
5555
56- // Apply ExecCmdOption before process start, used for customize SysProcAttr.
57- type ExecCmdOption func (cmd * exec.Cmd )
58-
5956// Cmd represents an external command, similar to the Go built-in os/exec.Cmd.
6057// A Cmd cannot be reused after calling Start. Exported fields are read-only and
6158// should not be modified, except Env which can be set before calling Start.
6259// To create a new Cmd, call NewCmd or NewCmdOptions.
6360type 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+
7187 * 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 )
84101}
85102
86103var (
104+ // ErrNotStarted is returned by Stop if called before Start or StartWithStdin.
87105 ErrNotStarted = errors .New ("command not running" )
88106)
89107
@@ -134,14 +152,20 @@ type Options struct {
134152 // faster and more efficient than polling Cmd.Status. The caller must read both
135153 // streaming channels, else lines are dropped silently.
136154 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 )
137160}
138161
139162// NewCmdOptions creates a new Cmd with options. The command is not started
140163// until Start is called.
141164func NewCmdOptions (options Options , name string , args ... string ) * Cmd {
142165 c := & Cmd {
143- Name : name ,
144- Args : args ,
166+ Name : name ,
167+ Args : args ,
168+ // --
145169 Mutex : & sync.Mutex {},
146170 status : Status {
147171 Cmd : name ,
@@ -167,6 +191,16 @@ func NewCmdOptions(options Options, name string, args ...string) *Cmd {
167191 c .stderrStream = NewOutputStream (c .Stderr )
168192 }
169193
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+
170204 return c
171205}
172206
@@ -185,7 +219,14 @@ func (c *Cmd) Clone() *Cmd {
185219 )
186220 clone .Dir = c .Dir
187221 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+
189230 return clone
190231}
191232
@@ -374,7 +415,9 @@ func (c *Cmd) run(in io.Reader) {
374415 // is nil, use the current process' environment.
375416 cmd .Env = c .Env
376417 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 {
378421 f (cmd )
379422 }
380423
0 commit comments