| OLD | NEW |
| (Empty) | |
| 1 // Copyright 2017 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. |
| 4 |
| 5 package vpython |
| 6 |
| 7 import ( |
| 8 "os" |
| 9 "os/exec" |
| 10 "os/signal" |
| 11 |
| 12 "github.com/luci/luci-go/common/errors" |
| 13 "github.com/luci/luci-go/common/logging" |
| 14 "github.com/luci/luci-go/common/system/environ" |
| 15 "github.com/luci/luci-go/vpython/venv" |
| 16 |
| 17 "golang.org/x/net/context" |
| 18 ) |
| 19 |
| 20 var ( |
| 21 // EnvSpecPath is the exported enviornment variable for the specificatio
n path. |
| 22 // |
| 23 // This is added to the bootstrap enviornment used by Run to allow subpr
ocess |
| 24 // "vpython" invocations to automatically inherit the same environment. |
| 25 EnvSpecPath = "VPYTHON_VENV_SPEC_PATH" |
| 26 ) |
| 27 |
| 28 // Run sets up a Python VirtualEnv and executes the supplied Options. |
| 29 // |
| 30 // Run returns nil if if the Python environment was successfully set-up and the |
| 31 // Python interpreter was successfully run with a zero return code. If the |
| 32 // Python interpreter returns a non-zero return code, a PythonError (potentially |
| 33 // wrapped) will be returned. |
| 34 // |
| 35 // A generalized return code to return for an error value can be obtained via |
| 36 // ReturnCode. |
| 37 // |
| 38 // Run consists of: |
| 39 // |
| 40 // - Identify the target Python script to run (if there is one). |
| 41 // - Identifying the Python interpreter to use. |
| 42 // - Composing the environment specification. |
| 43 // - Constructing the virtual environment (download, install). |
| 44 // - Execute the Python process with the supplied arguments. |
| 45 // |
| 46 // The Python subprocess is bound to the lifetime of ctx, and will be terminated |
| 47 // if ctx is cancelled. |
| 48 func Run(c context.Context, opts Options) error { |
| 49 // Resolve our Options. |
| 50 if err := opts.resolve(c); err != nil { |
| 51 return errors.Annotate(err).Reason("could not resolve options").
Err() |
| 52 } |
| 53 |
| 54 // Create a local cancellation option (signal handling). |
| 55 c, cancelFunc := context.WithCancel(c) |
| 56 defer cancelFunc() |
| 57 |
| 58 // Create our virtual enviornment root directory. |
| 59 err := venv.With(c, opts.EnvConfig, opts.WaitForEnv, func(c context.Cont
ext, ve *venv.Env) error { |
| 60 // Build the augmented environment variables. |
| 61 e := opts.Environ |
| 62 if e.Len() == 0 { |
| 63 // If no environment was supplied, use the system enviro
nment. |
| 64 e = environ.System() |
| 65 } |
| 66 |
| 67 e.Set("VIRTUAL_ENV", ve.Root) // Set by VirtualEnv script. |
| 68 if ve.SpecPath != "" { |
| 69 e.Set(EnvSpecPath, ve.SpecPath) |
| 70 } |
| 71 |
| 72 // Run our bootstrapped Python command. |
| 73 cmd := ve.InterpreterCommand() |
| 74 cmd.WorkDir = opts.WorkDir |
| 75 cmd.Isolated = true |
| 76 cmd.Env = e.Sorted() |
| 77 |
| 78 pythonCmd, err := cmd.Prepare(c, opts.Args...) |
| 79 if err != nil { |
| 80 return errors.Annotate(err).Reason("failed to prepare co
mmand").Err() |
| 81 } |
| 82 pythonCmd.Stdin = os.Stdin |
| 83 |
| 84 if err := runAndForwardSignals(c, pythonCmd, cancelFunc); err !=
nil { |
| 85 return errors.Annotate(err).Reason("failed to execute bo
otstrapped Python").Err() |
| 86 } |
| 87 return nil |
| 88 }) |
| 89 if err != nil { |
| 90 return errors.Annotate(err).Err() |
| 91 } |
| 92 return nil |
| 93 } |
| 94 |
| 95 func runAndForwardSignals(c context.Context, cmd *exec.Cmd, cancelFunc context.C
ancelFunc) error { |
| 96 signalC := make(chan os.Signal, 1) |
| 97 signalDoneC := make(chan struct{}) |
| 98 signal.Notify(signalC, forwardedSignals...) |
| 99 defer func() { |
| 100 signal.Stop(signalC) |
| 101 |
| 102 close(signalC) |
| 103 <-signalDoneC |
| 104 }() |
| 105 |
| 106 if err := cmd.Start(); err != nil { |
| 107 return errors.Annotate(err).Reason("failed to start process").Er
r() |
| 108 } |
| 109 |
| 110 logging.Fields{ |
| 111 "pid": cmd.Process.Pid, |
| 112 }.Debugf(c, "Python subprocess has started!") |
| 113 |
| 114 // Start our signal forwarding goroutine, now that the process is runnin
g. |
| 115 go func() { |
| 116 defer func() { |
| 117 close(signalDoneC) |
| 118 }() |
| 119 |
| 120 for sig := range signalC { |
| 121 logging.Debugf(c, "Forwarding signal: %v", sig) |
| 122 if err := cmd.Process.Signal(sig); err != nil { |
| 123 logging.Fields{ |
| 124 logging.ErrorKey: err, |
| 125 "signal": sig, |
| 126 }.Errorf(c, "Failed to forward signal; terminati
ng immediately.") |
| 127 cancelFunc() |
| 128 } |
| 129 } |
| 130 }() |
| 131 |
| 132 err := cmd.Wait() |
| 133 logging.Debugf(c, "Python subprocess has terminated: %v", err) |
| 134 if err != nil { |
| 135 return errors.Annotate(err).Err() |
| 136 } |
| 137 return nil |
| 138 } |
| OLD | NEW |