| 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 vpython | 5 package vpython |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "os" | 8 "os" |
| 9 "os/exec" | 9 "os/exec" |
| 10 "os/signal" | 10 "os/signal" |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 43 // - Identifying the Python interpreter to use. | 43 // - Identifying the Python interpreter to use. |
| 44 // - Composing the environment specification. | 44 // - Composing the environment specification. |
| 45 // - Constructing the virtual environment (download, install). | 45 // - Constructing the virtual environment (download, install). |
| 46 // - Execute the Python process with the supplied arguments. | 46 // - Execute the Python process with the supplied arguments. |
| 47 // | 47 // |
| 48 // The Python subprocess is bound to the lifetime of ctx, and will be terminated | 48 // The Python subprocess is bound to the lifetime of ctx, and will be terminated |
| 49 // if ctx is cancelled. | 49 // if ctx is cancelled. |
| 50 func Run(c context.Context, opts Options) error { | 50 func Run(c context.Context, opts Options) error { |
| 51 // Resolve our Options. | 51 // Resolve our Options. |
| 52 if err := opts.resolve(c); err != nil { | 52 if err := opts.resolve(c); err != nil { |
| 53 » » return errors.Annotate(err).Reason("could not resolve options").
Err() | 53 » » return errors.Annotate(err, "could not resolve options").Err() |
| 54 } | 54 } |
| 55 | 55 |
| 56 // Create a local cancellation option (signal handling). | 56 // Create a local cancellation option (signal handling). |
| 57 c, cancelFunc := context.WithCancel(c) | 57 c, cancelFunc := context.WithCancel(c) |
| 58 defer cancelFunc() | 58 defer cancelFunc() |
| 59 | 59 |
| 60 // Create our virtual environment root directory. | 60 // Create our virtual environment root directory. |
| 61 err := venv.With(c, opts.EnvConfig, opts.WaitForEnv, func(c context.Cont
ext, ve *venv.Env) error { | 61 err := venv.With(c, opts.EnvConfig, opts.WaitForEnv, func(c context.Cont
ext, ve *venv.Env) error { |
| 62 // Build the augmented environment variables. | 62 // Build the augmented environment variables. |
| 63 e := opts.Environ | 63 e := opts.Environ |
| (...skipping 28 matching lines...) Expand all Loading... |
| 92 cmd.Dir = opts.WorkDir | 92 cmd.Dir = opts.WorkDir |
| 93 cmd.Env = e.Sorted() | 93 cmd.Env = e.Sorted() |
| 94 cmd.Stdin = os.Stdin | 94 cmd.Stdin = os.Stdin |
| 95 cmd.Stdout = os.Stdout | 95 cmd.Stdout = os.Stdout |
| 96 cmd.Stderr = os.Stderr | 96 cmd.Stderr = os.Stderr |
| 97 | 97 |
| 98 logging.Debugf(c, "Running Python command: %s\nWorkDir: %s\nEnv:
%s", cmd.Args, cmd.Dir, cmd.Env) | 98 logging.Debugf(c, "Running Python command: %s\nWorkDir: %s\nEnv:
%s", cmd.Args, cmd.Dir, cmd.Env) |
| 99 | 99 |
| 100 // Output the Python command being executed. | 100 // Output the Python command being executed. |
| 101 if err := runAndForwardSignals(c, cmd, cancelFunc); err != nil { | 101 if err := runAndForwardSignals(c, cmd, cancelFunc); err != nil { |
| 102 » » » return errors.Annotate(err).Reason("failed to execute bo
otstrapped Python").Err() | 102 » » » return errors.Annotate(err, "failed to execute bootstrap
ped Python").Err() |
| 103 } | 103 } |
| 104 return nil | 104 return nil |
| 105 }) | 105 }) |
| 106 if err != nil { | 106 if err != nil { |
| 107 » » return errors.Annotate(err).Err() | 107 » » return errors.Annotate(err, "").Err() |
| 108 } | 108 } |
| 109 return nil | 109 return nil |
| 110 } | 110 } |
| 111 | 111 |
| 112 func runAndForwardSignals(c context.Context, cmd *exec.Cmd, cancelFunc context.C
ancelFunc) error { | 112 func runAndForwardSignals(c context.Context, cmd *exec.Cmd, cancelFunc context.C
ancelFunc) error { |
| 113 signalC := make(chan os.Signal, 1) | 113 signalC := make(chan os.Signal, 1) |
| 114 signalDoneC := make(chan struct{}) | 114 signalDoneC := make(chan struct{}) |
| 115 signal.Notify(signalC, forwardedSignals...) | 115 signal.Notify(signalC, forwardedSignals...) |
| 116 defer func() { | 116 defer func() { |
| 117 signal.Stop(signalC) | 117 signal.Stop(signalC) |
| 118 | 118 |
| 119 close(signalC) | 119 close(signalC) |
| 120 <-signalDoneC | 120 <-signalDoneC |
| 121 }() | 121 }() |
| 122 | 122 |
| 123 if err := cmd.Start(); err != nil { | 123 if err := cmd.Start(); err != nil { |
| 124 » » return errors.Annotate(err).Reason("failed to start process").Er
r() | 124 » » return errors.Annotate(err, "failed to start process").Err() |
| 125 } | 125 } |
| 126 | 126 |
| 127 logging.Fields{ | 127 logging.Fields{ |
| 128 "pid": cmd.Process.Pid, | 128 "pid": cmd.Process.Pid, |
| 129 }.Debugf(c, "Python subprocess has started!") | 129 }.Debugf(c, "Python subprocess has started!") |
| 130 | 130 |
| 131 // Start our signal forwarding goroutine, now that the process is runnin
g. | 131 // Start our signal forwarding goroutine, now that the process is runnin
g. |
| 132 go func() { | 132 go func() { |
| 133 defer func() { | 133 defer func() { |
| 134 close(signalDoneC) | 134 close(signalDoneC) |
| 135 }() | 135 }() |
| 136 | 136 |
| 137 for sig := range signalC { | 137 for sig := range signalC { |
| 138 logging.Debugf(c, "Forwarding signal: %v", sig) | 138 logging.Debugf(c, "Forwarding signal: %v", sig) |
| 139 if err := cmd.Process.Signal(sig); err != nil { | 139 if err := cmd.Process.Signal(sig); err != nil { |
| 140 logging.Fields{ | 140 logging.Fields{ |
| 141 logging.ErrorKey: err, | 141 logging.ErrorKey: err, |
| 142 "signal": sig, | 142 "signal": sig, |
| 143 }.Errorf(c, "Failed to forward signal; terminati
ng immediately.") | 143 }.Errorf(c, "Failed to forward signal; terminati
ng immediately.") |
| 144 cancelFunc() | 144 cancelFunc() |
| 145 } | 145 } |
| 146 } | 146 } |
| 147 }() | 147 }() |
| 148 | 148 |
| 149 err := cmd.Wait() | 149 err := cmd.Wait() |
| 150 logging.Debugf(c, "Python subprocess has terminated: %v", err) | 150 logging.Debugf(c, "Python subprocess has terminated: %v", err) |
| 151 if err != nil { | 151 if err != nil { |
| 152 » » return errors.Annotate(err).Err() | 152 » » return errors.Annotate(err, "").Err() |
| 153 } | 153 } |
| 154 return nil | 154 return nil |
| 155 } | 155 } |
| 156 | 156 |
| 157 func prefixPATH(env environ.Env, components ...string) { | 157 func prefixPATH(env environ.Env, components ...string) { |
| 158 if len(components) == 0 { | 158 if len(components) == 0 { |
| 159 return | 159 return |
| 160 } | 160 } |
| 161 | 161 |
| 162 // Clone "components" so we don't mutate our caller's array. | 162 // Clone "components" so we don't mutate our caller's array. |
| 163 components = append([]string(nil), components...) | 163 components = append([]string(nil), components...) |
| 164 | 164 |
| 165 // If there is a current PATH (likely), add that to the end. | 165 // If there is a current PATH (likely), add that to the end. |
| 166 cur, _ := env.Get("PATH") | 166 cur, _ := env.Get("PATH") |
| 167 if len(cur) > 0 { | 167 if len(cur) > 0 { |
| 168 components = append(components, cur) | 168 components = append(components, cur) |
| 169 } | 169 } |
| 170 | 170 |
| 171 env.Set("PATH", strings.Join(components, string(os.PathListSeparator))) | 171 env.Set("PATH", strings.Join(components, string(os.PathListSeparator))) |
| 172 } | 172 } |
| OLD | NEW |