| OLD | NEW |
| 1 // Copyright 2017 The LUCI Authors. All rights reserved. | 1 // Copyright 2017 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. | 3 // that can be found in the LICENSE file. |
| 4 | 4 |
| 5 package python | 5 package python |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "os" | 8 "os" |
| 9 "os/exec" | 9 "os/exec" |
| 10 "strings" | 10 "strings" |
| 11 "sync" | 11 "sync" |
| 12 | 12 |
| 13 "github.com/luci/luci-go/common/errors" | 13 "github.com/luci/luci-go/common/errors" |
| 14 "github.com/luci/luci-go/common/logging" | 14 "github.com/luci/luci-go/common/logging" |
| 15 "github.com/luci/luci-go/common/system/exitcode" | |
| 16 | 15 |
| 17 "golang.org/x/net/context" | 16 "golang.org/x/net/context" |
| 18 ) | 17 ) |
| 19 | 18 |
| 20 type runnerFunc func(cmd *exec.Cmd, capture bool) (string, error) | 19 type runnerFunc func(cmd *exec.Cmd, capture bool) (string, error) |
| 21 | 20 |
| 22 // Interpreter represents a system Python interpreter. It exposes the ability | 21 // Interpreter represents a system Python interpreter. It exposes the ability |
| 23 // to use common functionality of that interpreter. | 22 // to use common functionality of that interpreter. |
| 24 type Interpreter struct { | 23 type Interpreter struct { |
| 25 // Python is the path to the system Python interpreter. | 24 // Python is the path to the system Python interpreter. |
| (...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 109 type Command struct { | 108 type Command struct { |
| 110 // Python is the path to the Python interpreter to use. It is automatica
lly | 109 // Python is the path to the Python interpreter to use. It is automatica
lly |
| 111 // populated from an Interpreter when created through an Interpreter's C
ommand | 110 // populated from an Interpreter when created through an Interpreter's C
ommand |
| 112 // method. | 111 // method. |
| 113 Python string | 112 Python string |
| 114 | 113 |
| 115 // WorkDir is the working directory to use when running the interpreter.
If | 114 // WorkDir is the working directory to use when running the interpreter.
If |
| 116 // empty, the current working directory will be used. | 115 // empty, the current working directory will be used. |
| 117 WorkDir string | 116 WorkDir string |
| 118 | 117 |
| 119 // ConnectSTDIN will cause this process' STDIN to be passed through to t
he | |
| 120 // Python subprocess. Otherwise, the Python subprocess will receive a cl
osed | |
| 121 // STDIN. | |
| 122 ConnectSTDIN bool | |
| 123 | |
| 124 // Isolated means that the Python invocation should include flags to iso
late | 118 // Isolated means that the Python invocation should include flags to iso
late |
| 125 // it from local system modification. | 119 // it from local system modification. |
| 126 // | 120 // |
| 127 // This removes environmental factors such as: | 121 // This removes environmental factors such as: |
| 128 // - The user's "site.py". | 122 // - The user's "site.py". |
| 129 // - The current PYTHONPATH environment variable. | 123 // - The current PYTHONPATH environment variable. |
| 130 // - Compiled ".pyc/.pyo" files. | 124 // - Compiled ".pyc/.pyo" files. |
| 131 Isolated bool | 125 Isolated bool |
| 132 | 126 |
| 133 // Env, if not nil, is the environment to supply. | 127 // Env, if not nil, is the environment to supply. |
| 134 Env []string | 128 Env []string |
| 135 | 129 |
| 136 // testRunner is the runner function to use to run an exec.Cmd. This can
be | 130 // testRunner is the runner function to use to run an exec.Cmd. This can
be |
| 137 // swapped out for testing. | 131 // swapped out for testing. |
| 138 testRunner runnerFunc | 132 testRunner runnerFunc |
| 139 } | 133 } |
| 140 | 134 |
| 141 // Run runs the configured Command with the supplied arguments. | 135 // Prepare generates an exec.Cmd with the Command's configuration. |
| 142 // | 136 func (ic *Command) Prepare(c context.Context, args ...string) (*exec.Cmd, error)
{ |
| 143 // Run returns wrapped errors. Use errors.Unwrap to get the main cause, if | |
| 144 // needed. If an error occurs during setup or invocation, it will be returned | |
| 145 // directly. If the interpreter runs and returns zero, nil will be returned. If | |
| 146 // the interpreter runs and returns non-zero, an Error instance will be returned | |
| 147 // containing that return code. | |
| 148 func (ic *Command) Run(c context.Context, args ...string) error { | |
| 149 if ic.Python == "" { | 137 if ic.Python == "" { |
| 150 » » return errors.New("a Python interpreter must be supplied") | 138 » » return nil, errors.New("a Python interpreter must be supplied") |
| 151 } | 139 } |
| 152 | 140 |
| 153 if ic.Isolated { | 141 if ic.Isolated { |
| 154 args = append([]string{ | 142 args = append([]string{ |
| 155 "-B", // Don't compile "pyo" binaries. | 143 "-B", // Don't compile "pyo" binaries. |
| 156 "-E", // Don't use PYTHON* enviornment variables. | 144 "-E", // Don't use PYTHON* enviornment variables. |
| 157 "-s", // Don't use user 'site.py'. | 145 "-s", // Don't use user 'site.py'. |
| 158 }, args...) | 146 }, args...) |
| 159 } | 147 } |
| 160 | 148 |
| 161 cmd := exec.CommandContext(c, ic.Python, args...) | 149 cmd := exec.CommandContext(c, ic.Python, args...) |
| 162 cmd.Stdout = os.Stdout | 150 cmd.Stdout = os.Stdout |
| 163 cmd.Stderr = os.Stderr | 151 cmd.Stderr = os.Stderr |
| 164 if ic.ConnectSTDIN { | |
| 165 cmd.Stdin = os.Stdin | |
| 166 } | |
| 167 cmd.Dir = ic.WorkDir | 152 cmd.Dir = ic.WorkDir |
| 168 cmd.Env = ic.Env | 153 cmd.Env = ic.Env |
| 169 | 154 |
| 155 return cmd, nil |
| 156 } |
| 157 |
| 158 // Run runs the configured Command with the supplied arguments. |
| 159 // |
| 160 // Run returns wrapped errors. Use errors.Unwrap to get the main cause, if |
| 161 // needed. If an error occurs during setup or invocation, including an exit |
| 162 // code related error, it will be returned, possibly wrapped. If the interpreter |
| 163 // runs and returns zero, nil will be returned. |
| 164 func (ic *Command) Run(c context.Context, args ...string) error { |
| 165 cmd, err := ic.Prepare(c, args...) |
| 166 if err != nil { |
| 167 return errors.Annotate(err).Err() |
| 168 } |
| 169 |
| 170 if logging.IsLogging(c, logging.Debug) { | 170 if logging.IsLogging(c, logging.Debug) { |
| 171 logging.Debugf(c, "Running Python command (cwd=%s): %s", | 171 logging.Debugf(c, "Running Python command (cwd=%s): %s", |
| 172 cmd.Dir, strings.Join(cmd.Args, " ")) | 172 cmd.Dir, strings.Join(cmd.Args, " ")) |
| 173 } | 173 } |
| 174 | 174 |
| 175 // Allow testing to supply an alternative runner function. | 175 // Allow testing to supply an alternative runner function. |
| 176 rf := ic.testRunner | 176 rf := ic.testRunner |
| 177 if rf == nil { | 177 if rf == nil { |
| 178 rf = defaultRunnerFunc | 178 rf = defaultRunnerFunc |
| 179 } | 179 } |
| 180 | 180 |
| 181 if _, err := rf(cmd, false); err != nil { | 181 if _, err := rf(cmd, false); err != nil { |
| 182 // If the process failed because of a non-zero return value, ret
urn that | |
| 183 // as our error. | |
| 184 if rc, has := exitcode.Get(err); has { | |
| 185 return errors.Annotate(Error(rc)).Reason("Python bootstr
ap returned non-zero").Err() | |
| 186 } | |
| 187 | |
| 188 return errors.Annotate(err).Reason("failed to run Python command
"). | 182 return errors.Annotate(err).Reason("failed to run Python command
"). |
| 189 D("python", ic.Python). | 183 D("python", ic.Python). |
| 190 D("args", args). | 184 D("args", args). |
| 191 Err() | 185 Err() |
| 192 } | 186 } |
| 193 return nil | 187 return nil |
| 194 } | 188 } |
| 195 | 189 |
| 196 func defaultRunnerFunc(cmd *exec.Cmd, capture bool) (string, error) { | 190 func defaultRunnerFunc(cmd *exec.Cmd, capture bool) (string, error) { |
| 197 // If we're capturing output, combine STDOUT and STDERR (see GetVersion | 191 // If we're capturing output, combine STDOUT and STDERR (see GetVersion |
| 198 // for details). | 192 // for details). |
| 199 if capture { | 193 if capture { |
| 200 out, err := cmd.CombinedOutput() | 194 out, err := cmd.CombinedOutput() |
| 201 if err != nil { | 195 if err != nil { |
| 202 return "", errors.Annotate(err).Err() | 196 return "", errors.Annotate(err).Err() |
| 203 } | 197 } |
| 204 return string(out), nil | 198 return string(out), nil |
| 205 } | 199 } |
| 206 | 200 |
| 207 // Non-capturing run. | 201 // Non-capturing run. |
| 208 if err := cmd.Run(); err != nil { | 202 if err := cmd.Run(); err != nil { |
| 209 return "", errors.Annotate(err).Err() | 203 return "", errors.Annotate(err).Err() |
| 210 } | 204 } |
| 211 return "", nil | 205 return "", nil |
| 212 } | 206 } |
| OLD | NEW |