| // Copyright 2017 The LUCI Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package vpython |
| |
| import ( |
| "context" |
| "os" |
| "strings" |
| |
| "go.chromium.org/luci/vpython/python" |
| "go.chromium.org/luci/vpython/venv" |
| |
| "go.chromium.org/luci/common/errors" |
| "go.chromium.org/luci/common/logging" |
| "go.chromium.org/luci/common/system/environ" |
| ) |
| |
| // Run sets up a Python VirtualEnv and executes the supplied Options. |
| // |
| // If the Python interpreter was successfully launched, Run will never return, |
| // and the process will exit with the return code of the Python interpreter. |
| // |
| // If the Python environment could not be set-up, or if the interpreter could |
| // not be invoked, Run will return an non-nil error. |
| // |
| // Run consists of: |
| // |
| // - Identify the target Python script to run (if there is one). |
| // - Identifying the Python interpreter to use. |
| // - Composing the environment specification. |
| // - Constructing the virtual environment (download, install). |
| // - Execute the Python process with the supplied arguments. |
| // |
| // The Python subprocess is bound to the lifetime of ctx, and will be terminated |
| // if ctx is cancelled. |
| func Run(c context.Context, opts Options) error { |
| // Resolve our Options. |
| if err := opts.resolve(c); err != nil { |
| return errors.Annotate(err, "could not resolve options").Err() |
| } |
| |
| if opts.EnvConfig.Spec.PythonVersion == "" { |
| opts.EnvConfig.Spec.PythonVersion = opts.DefaultSpec.PythonVersion |
| } |
| |
| // Create our virtual environment root directory. |
| opts.EnvConfig.FailIfLocked = !opts.WaitForEnv |
| err := venv.With(c, opts.EnvConfig, func(c context.Context, ve *venv.Env) error { |
| e := opts.Environ.Clone() |
| python.IsolateEnvironment(&e, !opts.ClearPythonPath) |
| |
| e.Set("VIRTUAL_ENV", ve.Root) // Set by VirtualEnv script. |
| if !opts.VpythonOptIn { |
| // Prepend BinDir to $PATH |
| e.Set("PATH", strings.Join( |
| []string{ve.BinDir, e.GetEmpty("PATH")}, string(os.PathListSeparator))) |
| } |
| |
| // Run our bootstrapped Python command. |
| logging.Debugf(c, "Python environment:\nWorkDir: %s\nEnv: %s", opts.WorkDir, e) |
| if err := systemSpecificLaunch(c, ve, opts.CommandLine, e, opts.WorkDir); err != nil { |
| return errors.Annotate(err, "failed to execute bootstrapped Python").Err() |
| } |
| return nil |
| }) |
| if err == nil { |
| panic("must not return nil error") |
| } |
| return err |
| } |
| |
| // Exec runs the specified Python command. |
| // |
| // Once the process launches, Context cancellation will not have an impact. |
| // |
| // interp is the Python interperer to run. |
| // |
| // cl is the populated CommandLine to run. |
| // |
| // env is the environment to install. |
| // |
| // dir, if not empty, is the working directory of the command. |
| // |
| // setupFn, if not nil, is a function that will be run immediately before |
| // execution, after all operations that are permitted to fail have completed. |
| // Any error returned here will result in a panic. |
| // |
| // If an error occurs during execution, it will be returned here. Otherwise, |
| // Exec will not return, and this process will exit with the return code of the |
| // executed process. |
| // |
| // The implementation of Exec is platform-specific. |
| func Exec(c context.Context, interp *python.Interpreter, cl *python.CommandLine, env environ.Env, dir string, setupFn func() error) error { |
| // Don't use cl.SetIsolatedFlags here, because they include -B and -E, which |
| // both turn off commonly-used aspects of the python interpreter. We do set |
| // '-s' though, because we don't want vpython to pick up the user's site |
| // directory by default (to maintain some semblance of isolation). |
| cl = cl.Clone() |
| cl.AddSingleFlag("s") |
| |
| argv := append([]string{interp.Python}, cl.BuildArgs()...) |
| logging.Debugf(c, "Exec Python command: %#v", argv) |
| return execImpl(c, argv, env, dir, setupFn) |
| } |