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 |