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

Side by Side Diff: recipe_engine/step_runner.py

Issue 2985323002: Add support for LUCI_CONTEXT.
Patch Set: Created 3 years, 4 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 unified diff | Download patch
OLDNEW
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
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
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
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698