blob: a52f8c83becf1ead01ab461e90f823c5815436d2 [file] [log] [blame]
// 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)
}