OLD | NEW |
---|---|
1 # Copyright 2016 The LUCI Authors. All rights reserved. | 1 # Copyright 2016 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 import calendar | 5 import calendar |
6 import collections | 6 import collections |
7 import contextlib | 7 import contextlib |
8 import datetime | 8 import datetime |
9 import itertools | 9 import itertools |
10 import json | 10 import json |
11 import os | 11 import os |
12 import pprint | 12 import pprint |
13 import re | 13 import re |
14 import StringIO | 14 import StringIO |
15 import sys | 15 import sys |
16 import tempfile | 16 import tempfile |
17 import time | 17 import time |
18 import traceback | 18 import traceback |
19 | 19 |
20 from . import recipe_api | 20 from . import recipe_api |
21 from . import recipe_test_api | 21 from . import recipe_test_api |
22 from . import stream | 22 from . import stream |
23 from . import types | 23 from . import types |
24 from . import util | 24 from . import util |
25 | 25 |
26 from libs import luci_context as luci_ctx | |
27 | |
26 import subprocess42 | 28 import subprocess42 |
27 | 29 |
28 | 30 |
29 if sys.platform == "win32": | 31 if sys.platform == "win32": |
30 # Windows has a bad habit of opening a dialog when a console program | 32 # Windows has a bad habit of opening a dialog when a console program |
31 # crashes, rather than just letting it crash. Therefore, when a | 33 # crashes, rather than just letting it crash. Therefore, when a |
32 # program crashes on Windows, we don't find out until the build step | 34 # program crashes on Windows, we don't find out until the build step |
33 # times out. This code prevents the dialog from appearing, so that we | 35 # times out. This code prevents the dialog from appearing, so that we |
34 # find out immediately and don't waste time waiting for a user to | 36 # find out immediately and don't waste time waiting for a user to |
35 # close the dialog. | 37 # close the dialog. |
(...skipping 168 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
204 'stderr': step_stream, | 206 'stderr': step_stream, |
205 'stdin': None, | 207 'stdin': None, |
206 } | 208 } |
207 for key in handles: | 209 for key in handles: |
208 fileName = getattr(step_config, key) | 210 fileName = getattr(step_config, key) |
209 if fileName: | 211 if fileName: |
210 handles[key] = open(fileName, 'rb' if key == 'stdin' else 'wb') | 212 handles[key] = open(fileName, 'rb' if key == 'stdin' else 'wb') |
211 # The subprocess will inherit and close these handles. | 213 # The subprocess will inherit and close these handles. |
212 retcode = self._run_cmd( | 214 retcode = self._run_cmd( |
213 cmd=step_config.cmd, timeout=step_config.timeout, handles=handles, | 215 cmd=step_config.cmd, timeout=step_config.timeout, handles=handles, |
214 env=step_env, cwd=step_config.cwd) | 216 env=step_env, luci_context=rendered_step.config.luci_context, |
217 cwd=step_config.cwd) | |
215 except subprocess42.TimeoutExpired as e: | 218 except subprocess42.TimeoutExpired as e: |
216 #FIXME: Make this respect the infra_step argument | 219 #FIXME: Make this respect the infra_step argument |
217 step_stream.set_step_status('FAILURE') | 220 step_stream.set_step_status('FAILURE') |
218 raise recipe_api.StepTimeout(step_config.name, e.timeout) | 221 raise recipe_api.StepTimeout(step_config.name, e.timeout) |
219 except OSError: | 222 except OSError: |
220 with step_stream.new_log_stream('exception') as l: | 223 with step_stream.new_log_stream('exception') as l: |
221 trace = traceback.format_exc().splitlines() | 224 trace = traceback.format_exc().splitlines() |
222 for line in trace: | 225 for line in trace: |
223 l.write_line(line) | 226 l.write_line(line) |
224 step_stream.set_step_status('EXCEPTION') | 227 step_stream.set_step_status('EXCEPTION') |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
300 yield 'in dir %s:' % (cwd,) | 303 yield 'in dir %s:' % (cwd,) |
301 for key, value in sorted(step.config._asdict().items()): | 304 for key, value in sorted(step.config._asdict().items()): |
302 if value is not None: | 305 if value is not None: |
303 yield ' %s: %s' % (key, self._render_step_value(value)) | 306 yield ' %s: %s' % (key, self._render_step_value(value)) |
304 yield 'full environment:' | 307 yield 'full environment:' |
305 for key, value in sorted(env.items()): | 308 for key, value in sorted(env.items()): |
306 yield ' %s: %s' % (key, value) | 309 yield ' %s: %s' % (key, value) |
307 yield '' | 310 yield '' |
308 stream.output_iter(step_stream, gen_step_prelude()) | 311 stream.output_iter(step_stream, gen_step_prelude()) |
309 | 312 |
310 def _run_cmd(self, cmd, timeout, handles, env, cwd): | 313 def _run_cmd(self, cmd, timeout, handles, env, luci_context, cwd): |
311 """Runs cmd (subprocess-style). | 314 """Runs cmd (subprocess-style). |
312 | 315 |
313 Args: | 316 Args: |
314 cmd: a subprocess-style command list, with command first then args. | 317 cmd: a subprocess-style command list, with command first then args. |
315 handles: A dictionary from ('stdin', 'stdout', 'stderr'), each value | 318 handles: A dictionary from ('stdin', 'stdout', 'stderr'), each value |
316 being *either* a stream.StreamEngine.Stream or a python file object | 319 being *either* a stream.StreamEngine.Stream or a python file object |
317 to direct that subprocess's filehandle to. | 320 to direct that subprocess's filehandle to. |
321 luci_context: full value of LUCI_CONTEXT dict. | |
iannucci
2017/08/04 18:55:50
Maybe "entire intended value of the LUCI_CONTEXT d
| |
318 env: the full environment to run the command in -- this is passed | 322 env: the full environment to run the command in -- this is passed |
319 unaltered to subprocess.Popen. | 323 unaltered to subprocess.Popen. |
320 cwd: the working directory of the command. | 324 cwd: the working directory of the command. |
321 """ | 325 """ |
322 fhandles = {} | 326 fhandles = {} |
323 | 327 |
324 # If we are given StreamEngine.Streams, map them to PIPE for subprocess. | 328 # If we are given StreamEngine.Streams, map them to PIPE for subprocess. |
325 # We will manually forward them to their corresponding stream. | 329 # We will manually forward them to their corresponding stream. |
326 for key in ('stdout', 'stderr'): | 330 for key in ('stdout', 'stderr'): |
327 handle = handles.get(key) | 331 handle = handles.get(key) |
328 if isinstance(handle, stream.StreamEngine.Stream): | 332 if isinstance(handle, stream.StreamEngine.Stream): |
329 fhandles[key] = subprocess42.PIPE | 333 fhandles[key] = subprocess42.PIPE |
330 else: | 334 else: |
331 fhandles[key] = handle | 335 fhandles[key] = handle |
332 | 336 |
333 # stdin must be a real handle, if it exists | 337 # stdin must be a real handle, if it exists |
334 fhandles['stdin'] = handles.get('stdin') | 338 fhandles['stdin'] = handles.get('stdin') |
335 | 339 |
336 with _modify_lookup_path(env.get('PATH')): | 340 with luci_ctx.replace(luci_context) as path: |
Vadim Sh.
2017/07/29 00:40:55
no changes here except lines 340-343
Rietveld is t
| |
337 proc = subprocess42.Popen( | 341 if path: |
iannucci
2017/08/04 18:55:50
nit: I would rename path to luci_ctx_path or somet
| |
338 cmd, | 342 env = env.copy() |
339 env=env, | 343 env['LUCI_CONTEXT'] = path |
340 cwd=cwd, | |
341 detached=True, | |
342 universal_newlines=True, | |
343 **fhandles) | |
344 | 344 |
345 # Safe to close file handles now that subprocess has inherited them. | 345 with _modify_lookup_path(env.get('PATH')): |
346 for handle in fhandles.itervalues(): | 346 proc = subprocess42.Popen( |
347 if isinstance(handle, file): | 347 cmd, |
348 handle.close() | 348 env=env, |
349 cwd=cwd, | |
350 detached=True, | |
351 universal_newlines=True, | |
352 **fhandles) | |
349 | 353 |
350 outstreams = {} | 354 # Safe to close file handles now that subprocess has inherited them. |
351 linebufs = {} | 355 for handle in fhandles.itervalues(): |
356 if isinstance(handle, file): | |
357 handle.close() | |
352 | 358 |
353 for key in ('stdout', 'stderr'): | 359 outstreams = {} |
354 handle = handles.get(key) | 360 linebufs = {} |
355 if isinstance(handle, stream.StreamEngine.Stream): | |
356 outstreams[key] = handle | |
357 linebufs[key] = _streamingLinebuf() | |
358 | 361 |
359 if linebufs: | 362 for key in ('stdout', 'stderr'): |
360 # manually check the timeout, because we poll | 363 handle = handles.get(key) |
361 start_time = time.time() | 364 if isinstance(handle, stream.StreamEngine.Stream): |
362 for pipe, data in proc.yield_any(timeout=1): | 365 outstreams[key] = handle |
363 if timeout and time.time() - start_time > timeout: | 366 linebufs[key] = _streamingLinebuf() |
364 # Don't know the name of the step, so raise this and it'll get caught | |
365 raise subprocess42.TimeoutExpired(cmd, timeout) | |
366 | 367 |
367 if pipe is None: | 368 if linebufs: |
368 continue | 369 # manually check the timeout, because we poll |
369 buf = linebufs.get(pipe) | 370 start_time = time.time() |
370 if not buf: | 371 for pipe, data in proc.yield_any(timeout=1): |
371 continue | 372 if timeout and time.time() - start_time > timeout: |
372 buf.ingest(data) | 373 # Don't know the name of the step, so raise this and it'll get caugh t |
373 for line in buf.get_buffered(): | 374 raise subprocess42.TimeoutExpired(cmd, timeout) |
374 outstreams[pipe].write_line(line) | 375 |
375 else: | 376 if pipe is None: |
376 proc.wait(timeout) | 377 continue |
378 buf = linebufs.get(pipe) | |
379 if not buf: | |
380 continue | |
381 buf.ingest(data) | |
382 for line in buf.get_buffered(): | |
383 outstreams[pipe].write_line(line) | |
384 else: | |
385 proc.wait(timeout) | |
377 | 386 |
378 return proc.returncode | 387 return proc.returncode |
379 | 388 |
380 def _trigger_builds(self, step, trigger_specs): | 389 def _trigger_builds(self, step, trigger_specs): |
381 assert trigger_specs is not None | 390 assert trigger_specs is not None |
382 for trig in trigger_specs: | 391 for trig in trigger_specs: |
383 builder_name = trig.builder_name | 392 builder_name = trig.builder_name |
384 if not builder_name: | 393 if not builder_name: |
385 raise ValueError('Trigger spec: builder_name is not set') | 394 raise ValueError('Trigger spec: builder_name is not set') |
386 | 395 |
(...skipping 441 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
828 supplied command, and only uses the |env| kwarg for modifying the environment | 837 supplied command, and only uses the |env| kwarg for modifying the environment |
829 of the child process. | 838 of the child process. |
830 """ | 839 """ |
831 saved_path = os.environ['PATH'] | 840 saved_path = os.environ['PATH'] |
832 try: | 841 try: |
833 if path is not None: | 842 if path is not None: |
834 os.environ['PATH'] = path | 843 os.environ['PATH'] = path |
835 yield | 844 yield |
836 finally: | 845 finally: |
837 os.environ['PATH'] = saved_path | 846 os.environ['PATH'] = saved_path |
OLD | NEW |