|
| 1 | +// This file is part of arduino-cli. |
| 2 | +// |
| 3 | +// Copyright 2020 ARDUINO SA (http://www.arduino.cc/) |
| 4 | +// |
| 5 | +// This software is released under the GNU General Public License version 3, |
| 6 | +// which covers the main part of arduino-cli. |
| 7 | +// The terms of this license can be found at: |
| 8 | +// https://www.gnu.org/licenses/gpl-3.0.en.html |
| 9 | +// |
| 10 | +// You can be released from the requirements of the above licenses by purchasing |
| 11 | +// a commercial license. Buying such a license is mandatory if you want to |
| 12 | +// modify or otherwise use the software for commercial activities involving the |
| 13 | +// Arduino software without disclosing the source code of your own applications. |
| 14 | +// To purchase a commercial license, send an email to [email protected]. |
| 15 | + |
| 16 | +package executils |
| 17 | + |
| 18 | +import ( |
| 19 | + "io" |
| 20 | + "os" |
| 21 | + "os/exec" |
| 22 | + |
| 23 | + "github.com/arduino/go-paths-helper" |
| 24 | + "github.com/pkg/errors" |
| 25 | +) |
| 26 | + |
| 27 | +// Process is representation of an external process run |
| 28 | +type Process struct { |
| 29 | + cmd *exec.Cmd |
| 30 | +} |
| 31 | + |
| 32 | +// NewProcess creates a command with the provided command line arguments. |
| 33 | +// The first argument is the path to the executable, the remainder are the |
| 34 | +// arguments to the command. |
| 35 | +func NewProcess(args ...string) (*Process, error) { |
| 36 | + if args == nil || len(args) == 0 { |
| 37 | + return nil, errors.New("no executable specified") |
| 38 | + } |
| 39 | + p := &Process{ |
| 40 | + cmd: exec.Command(args[0], args[1:]...), |
| 41 | + } |
| 42 | + TellCommandNotToSpawnShell(p.cmd) |
| 43 | + |
| 44 | + // This is required because some tools detects if the program is running |
| 45 | + // from terminal by looking at the stdin/out bindings. |
| 46 | + // https://github.com/arduino/arduino-cli/issues/844 |
| 47 | + p.cmd.Stdin = NullReader |
| 48 | + return p, nil |
| 49 | +} |
| 50 | + |
| 51 | +// NewProcessFromPath creates a command from the provided executable path and |
| 52 | +// command line arguments. |
| 53 | +func NewProcessFromPath(executable *paths.Path, args ...string) (*Process, error) { |
| 54 | + processArgs := []string{executable.String()} |
| 55 | + processArgs = append(processArgs, args...) |
| 56 | + return NewProcess(processArgs...) |
| 57 | +} |
| 58 | + |
| 59 | +// RedirectStdoutTo will redirect the process' stdout to the specified |
| 60 | +// writer. Any previous redirection will be overwritten. |
| 61 | +func (p *Process) RedirectStdoutTo(out io.Writer) { |
| 62 | + p.cmd.Stdout = out |
| 63 | +} |
| 64 | + |
| 65 | +// RedirectStderrTo will redirect the process' stdout to the specified |
| 66 | +// writer. Any previous redirection will be overwritten. |
| 67 | +func (p *Process) RedirectStderrTo(out io.Writer) { |
| 68 | + p.cmd.Stderr = out |
| 69 | +} |
| 70 | + |
| 71 | +// StdinPipe returns a pipe that will be connected to the command's standard |
| 72 | +// input when the command starts. The pipe will be closed automatically after |
| 73 | +// Wait sees the command exit. A caller need only call Close to force the pipe |
| 74 | +// to close sooner. For example, if the command being run will not exit until |
| 75 | +// standard input is closed, the caller must close the pipe. |
| 76 | +func (p *Process) StdinPipe() (io.WriteCloser, error) { |
| 77 | + if p.cmd.Stdin == NullReader { |
| 78 | + p.cmd.Stdin = nil |
| 79 | + } |
| 80 | + return p.cmd.StdinPipe() |
| 81 | +} |
| 82 | + |
| 83 | +// StdoutPipe returns a pipe that will be connected to the command's standard |
| 84 | +// output when the command starts. |
| 85 | +func (p *Process) StdoutPipe() (io.ReadCloser, error) { |
| 86 | + return p.cmd.StdoutPipe() |
| 87 | +} |
| 88 | + |
| 89 | +// StderrPipe returns a pipe that will be connected to the command's standard |
| 90 | +// error when the command starts. |
| 91 | +func (p *Process) StderrPipe() (io.ReadCloser, error) { |
| 92 | + return p.cmd.StderrPipe() |
| 93 | +} |
| 94 | + |
| 95 | +// Start will start the underliyng process. |
| 96 | +func (p *Process) Start() error { |
| 97 | + return p.cmd.Start() |
| 98 | +} |
| 99 | + |
| 100 | +// Wait waits for the command to exit and waits for any copying to stdin or copying |
| 101 | +// from stdout or stderr to complete. |
| 102 | +func (p *Process) Wait() error { |
| 103 | + // TODO: make some helpers to retrieve exit codes out of *ExitError. |
| 104 | + return p.cmd.Wait() |
| 105 | +} |
| 106 | + |
| 107 | +// Signal sends a signal to the Process. Sending Interrupt on Windows is not implemented. |
| 108 | +func (p *Process) Signal(sig os.Signal) error { |
| 109 | + return p.cmd.Process.Signal(sig) |
| 110 | +} |
| 111 | + |
| 112 | +// Kill causes the Process to exit immediately. Kill does not wait until the Process has |
| 113 | +// actually exited. This only kills the Process itself, not any other processes it may |
| 114 | +// have started. |
| 115 | +func (p *Process) Kill() error { |
| 116 | + return p.cmd.Process.Kill() |
| 117 | +} |
| 118 | + |
| 119 | +// SetDir sets the working directory of the command. If Dir is the empty string, Run |
| 120 | +// runs the command in the calling process's current directory. |
| 121 | +func (p *Process) SetDir(dir string) { |
| 122 | + p.cmd.Dir = dir |
| 123 | +} |
| 124 | + |
| 125 | +// SetDirFromPath sets the working directory of the command. If path is nil, Run |
| 126 | +// runs the command in the calling process's current directory. |
| 127 | +func (p *Process) SetDirFromPath(path *paths.Path) { |
| 128 | + if path == nil { |
| 129 | + p.cmd.Dir = "" |
| 130 | + } else { |
| 131 | + p.cmd.Dir = path.String() |
| 132 | + } |
| 133 | +} |
| 134 | + |
| 135 | +// Run starts the specified command and waits for it to complete. |
| 136 | +func (p *Process) Run() error { |
| 137 | + return p.cmd.Run() |
| 138 | +} |
0 commit comments