Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(28)

Unified Diff: vpython/run.go

Issue 2702873002: vpython: Add primary execution package. (Closed)
Patch Set: more windows signals Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « vpython/python/python_test.go ('k') | vpython/system_posix.go » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: vpython/run.go
diff --git a/vpython/run.go b/vpython/run.go
new file mode 100644
index 0000000000000000000000000000000000000000..16f959738e10e1128d544105856ee366898db1f4
--- /dev/null
+++ b/vpython/run.go
@@ -0,0 +1,138 @@
+// Copyright 2017 The LUCI Authors. All rights reserved.
+// Use of this source code is governed under the Apache License, Version 2.0
+// that can be found in the LICENSE file.
+
+package vpython
+
+import (
+ "os"
+ "os/exec"
+ "os/signal"
+
+ "github.com/luci/luci-go/common/errors"
+ "github.com/luci/luci-go/common/logging"
+ "github.com/luci/luci-go/common/system/environ"
+ "github.com/luci/luci-go/vpython/venv"
+
+ "golang.org/x/net/context"
+)
+
+var (
+ // EnvSpecPath is the exported enviornment variable for the specification path.
+ //
+ // This is added to the bootstrap enviornment used by Run to allow subprocess
+ // "vpython" invocations to automatically inherit the same environment.
+ EnvSpecPath = "VPYTHON_VENV_SPEC_PATH"
+)
+
+// Run sets up a Python VirtualEnv and executes the supplied Options.
+//
+// Run returns nil if if the Python environment was successfully set-up and the
+// Python interpreter was successfully run with a zero return code. If the
+// Python interpreter returns a non-zero return code, a PythonError (potentially
+// wrapped) will be returned.
+//
+// A generalized return code to return for an error value can be obtained via
+// ReturnCode.
+//
+// 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).Reason("could not resolve options").Err()
+ }
+
+ // Create a local cancellation option (signal handling).
+ c, cancelFunc := context.WithCancel(c)
+ defer cancelFunc()
+
+ // Create our virtual enviornment root directory.
+ err := venv.With(c, opts.EnvConfig, opts.WaitForEnv, func(c context.Context, ve *venv.Env) error {
+ // Build the augmented environment variables.
+ e := opts.Environ
+ if e.Len() == 0 {
+ // If no environment was supplied, use the system environment.
+ e = environ.System()
+ }
+
+ e.Set("VIRTUAL_ENV", ve.Root) // Set by VirtualEnv script.
+ if ve.SpecPath != "" {
+ e.Set(EnvSpecPath, ve.SpecPath)
+ }
+
+ // Run our bootstrapped Python command.
+ cmd := ve.InterpreterCommand()
+ cmd.WorkDir = opts.WorkDir
+ cmd.Isolated = true
+ cmd.Env = e.Sorted()
+
+ pythonCmd, err := cmd.Prepare(c, opts.Args...)
+ if err != nil {
+ return errors.Annotate(err).Reason("failed to prepare command").Err()
+ }
+ pythonCmd.Stdin = os.Stdin
+
+ if err := runAndForwardSignals(c, pythonCmd, cancelFunc); err != nil {
+ return errors.Annotate(err).Reason("failed to execute bootstrapped Python").Err()
+ }
+ return nil
+ })
+ if err != nil {
+ return errors.Annotate(err).Err()
+ }
+ return nil
+}
+
+func runAndForwardSignals(c context.Context, cmd *exec.Cmd, cancelFunc context.CancelFunc) error {
+ signalC := make(chan os.Signal, 1)
+ signalDoneC := make(chan struct{})
+ signal.Notify(signalC, forwardedSignals...)
+ defer func() {
+ signal.Stop(signalC)
+
+ close(signalC)
+ <-signalDoneC
+ }()
+
+ if err := cmd.Start(); err != nil {
+ return errors.Annotate(err).Reason("failed to start process").Err()
+ }
+
+ logging.Fields{
+ "pid": cmd.Process.Pid,
+ }.Debugf(c, "Python subprocess has started!")
+
+ // Start our signal forwarding goroutine, now that the process is running.
+ go func() {
+ defer func() {
+ close(signalDoneC)
+ }()
+
+ for sig := range signalC {
+ logging.Debugf(c, "Forwarding signal: %v", sig)
+ if err := cmd.Process.Signal(sig); err != nil {
+ logging.Fields{
+ logging.ErrorKey: err,
+ "signal": sig,
+ }.Errorf(c, "Failed to forward signal; terminating immediately.")
+ cancelFunc()
+ }
+ }
+ }()
+
+ err := cmd.Wait()
+ logging.Debugf(c, "Python subprocess has terminated: %v", err)
+ if err != nil {
+ return errors.Annotate(err).Err()
+ }
+ return nil
+}
« no previous file with comments | « vpython/python/python_test.go ('k') | vpython/system_posix.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698