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

Side by Side Diff: third_party/recipe_engine/main.py

Issue 1151423002: Move recipe engine to third_party/recipe_engine. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Moved field_composer_test with its buddies Created 5 years, 6 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 | Annotate | Revision Log
« no previous file with comments | « third_party/recipe_engine/loader.py ('k') | third_party/recipe_engine/recipe_api.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 # Copyright (c) 2013-2015 The Chromium Authors. All rights reserved.
2 # Copyright (c) 2013 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 3 # found in the LICENSE file.
5 4
6 """Entry point for fully-annotated builds. 5 """Entry point for fully-annotated builds.
7 6
8 This script is part of the effort to move all builds to annotator-based 7 This script is part of the effort to move all builds to annotator-based
9 systems. Any builder configured to use the AnnotatorFactory.BaseFactory() 8 systems. Any builder configured to use the AnnotatorFactory.BaseFactory()
10 found in scripts/master/factory/annotator_factory.py executes a single 9 found in scripts/master/factory/annotator_factory.py executes a single
11 AddAnnotatedScript step. That step (found in annotator_commands.py) calls 10 AddAnnotatedScript step. That step (found in annotator_commands.py) calls
12 this script with the build- and factory-properties passed on the command 11 this script with the build- and factory-properties passed on the command
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after
57 'step_history' is an OrderedDict of {stepname -> StepData}, always representing 56 'step_history' is an OrderedDict of {stepname -> StepData}, always representing
58 the current history of what steps have run, what they returned, and any 57 the current history of what steps have run, what they returned, and any
59 json data they emitted. Additionally, the OrderedDict has the following 58 json data they emitted. Additionally, the OrderedDict has the following
60 convenience functions defined: 59 convenience functions defined:
61 * last_step - Returns the last step that ran or None 60 * last_step - Returns the last step that ran or None
62 * nth_step(n) - Returns the N'th step that ran or None 61 * nth_step(n) - Returns the N'th step that ran or None
63 62
64 'failed' is a boolean representing if the build is in a 'failed' state. 63 'failed' is a boolean representing if the build is in a 'failed' state.
65 """ 64 """
66 65
66 import collections
67 import contextlib
67 import copy 68 import copy
68 import functools 69 import functools
69 import json 70 import json
70 import optparse
71 import os 71 import os
72 import subprocess 72 import subprocess
73 import sys 73 import sys
74 import threading
74 import traceback 75 import traceback
75 76
76 import cStringIO 77 import cStringIO
77 78
78 import common.python26_polyfill # pylint: disable=W0611
79 import collections # Import after polyfill to get OrderedDict on 2.6
80 79
81 from common import annotator 80 from . import loader
82 from common import chromium_utils 81 from . import recipe_api
83 82 from . import recipe_test_api
84 from slave import recipe_loader 83 from . import util
85 from slave import recipe_test_api
86 from slave import recipe_util
87 from slave import recipe_api
88 84
89 85
90 SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__)) 86 SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__))
91 87
92 88
93 class StepPresentation(object): 89 class StepPresentation(object):
94 STATUSES = set(('SUCCESS', 'FAILURE', 'WARNING', 'EXCEPTION')) 90 STATUSES = set(('SUCCESS', 'FAILURE', 'WARNING', 'EXCEPTION'))
95 91
96 def __init__(self): 92 def __init__(self):
97 self._finalized = False 93 self._finalized = False
(...skipping 123 matching lines...) Expand 10 before | Expand all | Expand 10 after
221 Args: 217 Args:
222 step_test: The test data json dictionary for this step, if any. 218 step_test: The test data json dictionary for this step, if any.
223 Passed through unaltered to each placeholder. 219 Passed through unaltered to each placeholder.
224 220
225 Returns any placeholder instances that were found while rendering the step. 221 Returns any placeholder instances that were found while rendering the step.
226 """ 222 """
227 # Process 'cmd', rendering placeholders there. 223 # Process 'cmd', rendering placeholders there.
228 placeholders = collections.defaultdict(lambda: collections.defaultdict(list)) 224 placeholders = collections.defaultdict(lambda: collections.defaultdict(list))
229 new_cmd = [] 225 new_cmd = []
230 for item in step.get('cmd', []): 226 for item in step.get('cmd', []):
231 if isinstance(item, recipe_util.Placeholder): 227 if isinstance(item, util.Placeholder):
232 module_name, placeholder_name = item.name_pieces 228 module_name, placeholder_name = item.name_pieces
233 tdata = step_test.pop_placeholder(item.name_pieces) 229 tdata = step_test.pop_placeholder(item.name_pieces)
234 new_cmd.extend(item.render(tdata)) 230 new_cmd.extend(item.render(tdata))
235 placeholders[module_name][placeholder_name].append((item, tdata)) 231 placeholders[module_name][placeholder_name].append((item, tdata))
236 else: 232 else:
237 new_cmd.append(item) 233 new_cmd.append(item)
238 step['cmd'] = new_cmd 234 step['cmd'] = new_cmd
239 235
240 # Process 'stdout', 'stderr' and 'stdin' placeholders, if given. 236 # Process 'stdout', 'stderr' and 'stdin' placeholders, if given.
241 stdio_placeholders = {} 237 stdio_placeholders = {}
242 for key in ('stdout', 'stderr', 'stdin'): 238 for key in ('stdout', 'stderr', 'stdin'):
243 placeholder = step.get(key) 239 placeholder = step.get(key)
244 tdata = None 240 tdata = None
245 if placeholder: 241 if placeholder:
246 assert isinstance(placeholder, recipe_util.Placeholder), key 242 assert isinstance(placeholder, util.Placeholder), key
247 tdata = getattr(step_test, key) 243 tdata = getattr(step_test, key)
248 placeholder.render(tdata) 244 placeholder.render(tdata)
249 assert placeholder.backing_file 245 assert placeholder.backing_file
250 step[key] = placeholder.backing_file 246 step[key] = placeholder.backing_file
251 stdio_placeholders[key] = (placeholder, tdata) 247 stdio_placeholders[key] = (placeholder, tdata)
252 248
253 return Placeholders(cmd=placeholders, **stdio_placeholders) 249 return Placeholders(cmd=placeholders, **stdio_placeholders)
254 250
255 251
256 def get_placeholder_results(step_result, placeholders): 252 def get_placeholder_results(step_result, placeholders):
(...skipping 20 matching lines...) Expand all
277 273
278 274
279 def get_callable_name(func): 275 def get_callable_name(func):
280 """Returns __name__ of a callable, handling functools.partial types.""" 276 """Returns __name__ of a callable, handling functools.partial types."""
281 if isinstance(func, functools.partial): 277 if isinstance(func, functools.partial):
282 return get_callable_name(func.func) 278 return get_callable_name(func.func)
283 else: 279 else:
284 return func.__name__ 280 return func.__name__
285 281
286 282
287 def get_args(argv):
288 """Process command-line arguments."""
289
290 parser = optparse.OptionParser(
291 description='Entry point for annotated builds.')
292 parser.add_option('--build-properties',
293 action='callback', callback=chromium_utils.convert_json,
294 type='string', default={},
295 help='build properties in JSON format')
296 parser.add_option('--factory-properties',
297 action='callback', callback=chromium_utils.convert_json,
298 type='string', default={},
299 help='factory properties in JSON format')
300 parser.add_option('--build-properties-gz',
301 action='callback', callback=chromium_utils.convert_gz_json,
302 type='string', default={}, dest='build_properties',
303 help='build properties in b64 gz JSON format')
304 parser.add_option('--factory-properties-gz',
305 action='callback', callback=chromium_utils.convert_gz_json,
306 type='string', default={}, dest='factory_properties',
307 help='factory properties in b64 gz JSON format')
308 parser.add_option('--keep-stdin', action='store_true', default=False,
309 help='don\'t close stdin when running recipe steps')
310 return parser.parse_args(argv)
311
312
313 def main(argv=None):
314 opts, _ = get_args(argv)
315
316 stream = annotator.StructuredAnnotationStream()
317 universe = recipe_loader.RecipeUniverse()
318
319 ret = run_steps(stream, opts.build_properties, opts.factory_properties,
320 universe)
321 return ret.status_code
322
323
324 # Return value of run_steps and RecipeEngine.run. 283 # Return value of run_steps and RecipeEngine.run.
325 RecipeExecutionResult = collections.namedtuple( 284 RecipeExecutionResult = collections.namedtuple(
326 'RecipeExecutionResult', 'status_code steps_ran') 285 'RecipeExecutionResult', 'status_code steps_ran')
327 286
328 287
329 def get_recipe_properties(factory_properties, build_properties): 288 def run_steps(properties,
330 """Constructs the recipe's properties from buildbot's properties. 289 stream,
331 290 universe,
332 This merges factory_properties and build_properties. Furthermore, it 291 test_data=recipe_test_api.DisabledTestData()):
333 tries to reconstruct the 'recipe' property from builders.pyl if it isn't
334 already there, and in that case merges in properties form builders.pyl.
335 """
336 properties = factory_properties.copy()
337 properties.update(build_properties)
338
339 # Try to reconstruct the recipe from builders.pyl if not given.
340 if 'recipe' not in properties:
341 mastername = properties['mastername']
342 buildername = properties['buildername']
343
344 master_path = chromium_utils.MasterPath(mastername)
345 builders_file = os.path.join(master_path, 'builders.pyl')
346 if os.path.isfile(builders_file):
347 builders = chromium_utils.ReadBuildersFile(builders_file)
348 assert buildername in builders['builders'], (
349 'buildername %s is not listed in %s' % (buildername, builders_file))
350 builder = builders['builders'][buildername]
351
352 # Update properties with builders.pyl data.
353 properties['recipe'] = builder['recipe']
354 properties.update(builder.get('properties', {}))
355 else:
356 raise LookupError('Cannot find recipe for %s on %s' %
357 (build_properties['buildername'],
358 build_properties['mastername']))
359 return properties
360
361
362 def run_steps(stream, build_properties, factory_properties,
363 universe, test_data=recipe_test_api.DisabledTestData()):
364 """Returns a tuple of (status_code, steps_ran). 292 """Returns a tuple of (status_code, steps_ran).
365 293
366 Only one of these values will be set at a time. This is mainly to support the 294 Only one of these values will be set at a time. This is mainly to support the
367 testing interface used by unittests/recipes_test.py. 295 testing interface used by unittests/recipes_test.py.
368 """ 296 """
369 stream.honor_zero_return_code() 297 stream.honor_zero_return_code()
370 298
371 # TODO(iannucci): Stop this when blamelist becomes sane data. 299 # TODO(iannucci): Stop this when blamelist becomes sane data.
372 if ('blamelist_real' in build_properties and 300 if ('blamelist_real' in properties and
373 'blamelist' in build_properties): 301 'blamelist' in properties):
374 build_properties['blamelist'] = build_properties['blamelist_real'] 302 properties['blamelist'] = properties['blamelist_real']
375 del build_properties['blamelist_real'] 303 del properties['blamelist_real']
376 304
377 # NOTE(iannucci): 'root' was a terribly bad idea and has been replaced by 305 # NOTE(iannucci): 'root' was a terribly bad idea and has been replaced by
378 # 'patch_project'. 'root' had Rietveld knowing about the implementation of 306 # 'patch_project'. 'root' had Rietveld knowing about the implementation of
379 # the builders. 'patch_project' lets the builder (recipe) decide its own 307 # the builders. 'patch_project' lets the builder (recipe) decide its own
380 # destiny. 308 # destiny.
381 build_properties.pop('root', None) 309 properties.pop('root', None)
382
383 properties = get_recipe_properties(
384 factory_properties=factory_properties,
385 build_properties=build_properties)
386 310
387 # TODO(iannucci): A much better way to do this would be to dynamically 311 # TODO(iannucci): A much better way to do this would be to dynamically
388 # detect if the mirrors are actually available during the execution of the 312 # detect if the mirrors are actually available during the execution of the
389 # recipe. 313 # recipe.
390 if ('use_mirror' not in properties and ( 314 if ('use_mirror' not in properties and (
391 'TESTING_MASTERNAME' in os.environ or 315 'TESTING_MASTERNAME' in os.environ or
392 'TESTING_SLAVENAME' in os.environ)): 316 'TESTING_SLAVENAME' in os.environ)):
393 properties['use_mirror'] = False 317 properties['use_mirror'] = False
394 318
395 # It's an integration point with a new recipe engine that can run steps 319 # It's an integration point with a new recipe engine that can run steps
(...skipping 24 matching lines...) Expand all
420 'contents of the file into run_recipe.py, with the < operator.', 344 'contents of the file into run_recipe.py, with the < operator.',
421 ] 345 ]
422 346
423 for line in run_recipe_help_lines: 347 for line in run_recipe_help_lines:
424 s.step_log_line('run_recipe', line) 348 s.step_log_line('run_recipe', line)
425 s.step_log_end('run_recipe') 349 s.step_log_end('run_recipe')
426 350
427 try: 351 try:
428 recipe_module = universe.load_recipe(recipe) 352 recipe_module = universe.load_recipe(recipe)
429 stream.emit('Running recipe with %s' % (properties,)) 353 stream.emit('Running recipe with %s' % (properties,))
430 api = recipe_loader.create_recipe_api(recipe_module.LOADED_DEPS, 354 api = loader.create_recipe_api(recipe_module.LOADED_DEPS,
431 engine, 355 engine,
432 test_data) 356 test_data)
433 steps = recipe_module.GenSteps 357 steps = recipe_module.GenSteps
434 s.step_text('<br/>running recipe: "%s"' % recipe) 358 s.step_text('<br/>running recipe: "%s"' % recipe)
435 except recipe_loader.NoSuchRecipe as e: 359 except loader.NoSuchRecipe as e:
436 s.step_text('<br/>recipe not found: %s' % e) 360 s.step_text('<br/>recipe not found: %s' % e)
437 s.step_failure() 361 s.step_failure()
438 return RecipeExecutionResult(2, None) 362 return RecipeExecutionResult(2, None)
439 363
440 # Run the steps emitted by a recipe via the engine, emitting annotations 364 # Run the steps emitted by a recipe via the engine, emitting annotations
441 # into |stream| along the way. 365 # into |stream| along the way.
442 return engine.run(steps, api) 366 return engine.run(steps, api)
443 367
444 368
369 def _merge_envs(original, override):
370 """Merges two environments.
371
372 Returns a new environment dict with entries from |override| overwriting
373 corresponding entries in |original|. Keys whose value is None will completely
374 remove the environment variable. Values can contain %(KEY)s strings, which
375 will be substituted with the values from the original (useful for amending, as
376 opposed to overwriting, variables like PATH).
377 """
378 result = original.copy()
379 if not override:
380 return result
381 for k, v in override.items():
382 if v is None:
383 if k in result:
384 del result[k]
385 else:
386 result[str(k)] = str(v) % original
387 return result
388
389
390 def _print_step(step, env, stream):
391 """Prints the step command and relevant metadata.
392
393 Intended to be similar to the information that Buildbot prints at the
394 beginning of each non-annotator step.
395 """
396 step_info_lines = []
397 step_info_lines.append(' '.join(step['cmd']))
398 step_info_lines.append('in dir %s:' % (step['cwd'] or os.getcwd()))
399 for key, value in sorted(step.items()):
400 if value is not None:
401 if callable(value):
402 # This prevents functions from showing up as:
403 # '<function foo at 0x7f523ec7a410>'
404 # which is tricky to test.
405 value = value.__name__+'(...)'
406 step_info_lines.append(' %s: %s' % (key, value))
407 step_info_lines.append('full environment:')
408 for key, value in sorted(env.items()):
409 step_info_lines.append(' %s: %s' % (key, value))
410 step_info_lines.append('')
411 stream.emit('\n'.join(step_info_lines))
412
413
414 @contextlib.contextmanager
415 def _modify_lookup_path(path):
416 """Places the specified path into os.environ.
417
418 Necessary because subprocess.Popen uses os.environ to perform lookup on the
419 supplied command, and only uses the |env| kwarg for modifying the environment
420 of the child process.
421 """
422 saved_path = os.environ['PATH']
423 try:
424 if path is not None:
425 os.environ['PATH'] = path
426 yield
427 finally:
428 os.environ['PATH'] = saved_path
429
430
431 def _normalize_change(change):
432 assert isinstance(change, dict), 'Change is not a dict'
433 change = change.copy()
434
435 # Convert when_timestamp to UNIX timestamp.
436 when = change.get('when_timestamp')
437 if isinstance(when, datetime.datetime):
438 when = calendar.timegm(when.utctimetuple())
439 change['when_timestamp'] = when
440
441 return change
442
443
444 def _trigger_builds(step, trigger_specs):
445 assert trigger_specs is not None
446 for trig in trigger_specs:
447 builder_name = trig.get('builder_name')
448 if not builder_name:
449 raise ValueError('Trigger spec: builder_name is not set')
450
451 changes = trig.get('buildbot_changes', [])
452 assert isinstance(changes, list), 'buildbot_changes must be a list'
453 changes = map(_normalize_change, changes)
454
455 step.step_trigger(json.dumps({
456 'builderNames': [builder_name],
457 'bucket': trig.get('bucket'),
458 'changes': changes,
459 'properties': trig.get('properties'),
460 }, sort_keys=True))
461
462
463 def _run_annotated_step(
464 stream, name, cmd, cwd=None, env=None, allow_subannotations=False,
465 trigger_specs=None, **kwargs):
466 """Runs a single step.
467
468 Context:
469 stream: StructuredAnnotationStream to use to emit step
470
471 Step parameters:
472 name: name of the step, will appear in buildbots waterfall
473 cmd: command to run, list of one or more strings
474 cwd: absolute path to working directory for the command
475 env: dict with overrides for environment variables
476 allow_subannotations: if True, lets the step emit its own annotations
477 trigger_specs: a list of trigger specifications, which are dict with keys:
478 properties: a dict of properties.
479 Buildbot requires buildername property.
480
481 Known kwargs:
482 stdout: Path to a file to put step stdout into. If used, stdout won't appear
483 in annotator's stdout (and |allow_subannotations| is ignored).
484 stderr: Path to a file to put step stderr into. If used, stderr won't appear
485 in annotator's stderr.
486 stdin: Path to a file to read step stdin from.
487
488 Returns the returncode of the step.
489 """
490 if isinstance(cmd, basestring):
491 cmd = (cmd,)
492 cmd = map(str, cmd)
493
494 # For error reporting.
495 step_dict = kwargs.copy()
496 step_dict.update({
497 'name': name,
498 'cmd': cmd,
499 'cwd': cwd,
500 'env': env,
501 'allow_subannotations': allow_subannotations,
502 })
503 step_env = _merge_envs(os.environ, env)
504
505 step_annotation = stream.step(name)
506 step_annotation.step_started()
507
508 _print_step(step_dict, step_env, stream)
509 returncode = 0
510 if cmd:
511 try:
512 # Open file handles for IO redirection based on file names in step_dict.
513 fhandles = {
514 'stdout': subprocess.PIPE,
515 'stderr': subprocess.PIPE,
516 'stdin': None,
517 }
518 for key in fhandles:
519 if key in step_dict:
520 fhandles[key] = open(step_dict[key],
521 'rb' if key == 'stdin' else 'wb')
522
523 if sys.platform.startswith('win'):
524 # Windows has a bad habit of opening a dialog when a console program
525 # crashes, rather than just letting it crash. Therefore, when a program
526 # crashes on Windows, we don't find out until the build step times out.
527 # This code prevents the dialog from appearing, so that we find out
528 # immediately and don't waste time waiting for a user to close the
529 # dialog.
530 import ctypes
531 # SetErrorMode(SEM_NOGPFAULTERRORBOX). For more information, see:
532 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms680621.aspx
533 ctypes.windll.kernel32.SetErrorMode(0x0002)
534 # CREATE_NO_WINDOW. For more information, see:
535 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
536 creationflags = 0x8000000
537 else:
538 creationflags = 0
539
540 with _modify_lookup_path(step_env.get('PATH')):
541 proc = subprocess.Popen(
542 cmd,
543 env=step_env,
544 cwd=cwd,
545 universal_newlines=True,
546 creationflags=creationflags,
547 **fhandles)
548
549 # Safe to close file handles now that subprocess has inherited them.
550 for handle in fhandles.itervalues():
551 if isinstance(handle, file):
552 handle.close()
553
554 outlock = threading.Lock()
555 def filter_lines(lock, allow_subannotations, inhandle, outhandle):
556 while True:
557 line = inhandle.readline()
558 if not line:
559 break
560 lock.acquire()
561 try:
562 if not allow_subannotations and line.startswith('@@@'):
563 outhandle.write('!')
564 outhandle.write(line)
565 outhandle.flush()
566 finally:
567 lock.release()
568
569 # Pump piped stdio through filter_lines. IO going to files on disk is
570 # not filtered.
571 threads = []
572 for key in ('stdout', 'stderr'):
573 if fhandles[key] == subprocess.PIPE:
574 inhandle = getattr(proc, key)
575 outhandle = getattr(sys, key)
576 threads.append(threading.Thread(
577 target=filter_lines,
578 args=(outlock, allow_subannotations, inhandle, outhandle)))
579
580 for th in threads:
581 th.start()
582 proc.wait()
583 for th in threads:
584 th.join()
585 returncode = proc.returncode
586 except OSError:
587 # File wasn't found, error will be reported to stream when the exception
588 # crosses the context manager.
589 step_annotation.step_exception_occured(*sys.exc_info())
590 raise
591
592 # TODO(martiniss) move logic into own module?
593 if trigger_specs:
594 _trigger_builds(step_annotation, trigger_specs)
595
596 return step_annotation, returncode
597
598
445 class RecipeEngine(object): 599 class RecipeEngine(object):
446 """Knows how to execute steps emitted by a recipe, holds global state such as 600 """Knows how to execute steps emitted by a recipe, holds global state such as
447 step history and build properties. Each recipe module API has a reference to 601 step history and build properties. Each recipe module API has a reference to
448 this object. 602 this object.
449 603
450 Recipe modules that are aware of the engine: 604 Recipe modules that are aware of the engine:
451 * properties - uses engine.properties. 605 * properties - uses engine.properties.
452 * step_history - uses engine.step_history. 606 * step_history - uses engine.step_history.
453 * step - uses engine.create_step(...). 607 * step - uses engine.create_step(...).
454 608
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after
549 step_test = self._test_data.pop_step_test_data(step['name'], 703 step_test = self._test_data.pop_step_test_data(step['name'],
550 test_data_fn) 704 test_data_fn)
551 placeholders = render_step(step, step_test) 705 placeholders = render_step(step, step_test)
552 706
553 self._step_history[step['name']] = step 707 self._step_history[step['name']] = step
554 self._emit_results() 708 self._emit_results()
555 709
556 step_result = None 710 step_result = None
557 711
558 if not self._test_data.enabled: 712 if not self._test_data.enabled:
559 self._previous_step_annotation, retcode = annotator.run_step( 713 self._previous_step_annotation, retcode = _run_annotated_step(
560 self._stream, **step) 714 self._stream, **step)
561 715
562 step_result = StepData(step, retcode) 716 step_result = StepData(step, retcode)
563 self._previous_step_annotation.annotation_stream.step_cursor(step['name']) 717 self._previous_step_annotation.annotation_stream.step_cursor(step['name'])
564 else: 718 else:
565 self._previous_step_annotation = annotation = self._stream.step( 719 self._previous_step_annotation = annotation = self._stream.step(
566 step['name']) 720 step['name'])
567 annotation.step_started() 721 annotation.step_started()
568 try: 722 try:
569 annotation.stream = cStringIO.StringIO() 723 annotation.stream = cStringIO.StringIO()
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after
643 self._step_history[final_result['name']] = final_result 797 self._step_history[final_result['name']] = final_result
644 798
645 return RecipeExecutionResult(retcode, self._step_history) 799 return RecipeExecutionResult(retcode, self._step_history)
646 800
647 def create_step(self, step): # pylint: disable=R0201 801 def create_step(self, step): # pylint: disable=R0201
648 # This version of engine doesn't do anything, just converts step to dict 802 # This version of engine doesn't do anything, just converts step to dict
649 # (that is consumed by annotator engine). 803 # (that is consumed by annotator engine).
650 return step.as_jsonish() 804 return step.as_jsonish()
651 805
652 806
653 class ParallelRecipeEngine(RecipeEngine):
654 """New engine that knows how to run steps in parallel.
655
656 TODO(vadimsh): Implement it.
657 """
658
659 def __init__(self, stream, properties, test_data):
660 super(ParallelRecipeEngine, self).__init__()
661 self._stream = stream
662 self._properties = properties
663 self._test_data = test_data
664
665 @property
666 def properties(self):
667 return self._properties
668
669 def run(self, steps_function, api):
670 raise NotImplementedError
671
672 def create_step(self, step):
673 raise NotImplementedError
674
675
676 def update_scripts():
677 if os.environ.get('RUN_SLAVE_UPDATED_SCRIPTS'):
678 os.environ.pop('RUN_SLAVE_UPDATED_SCRIPTS')
679 return False
680
681 stream = annotator.StructuredAnnotationStream()
682
683 with stream.step('update_scripts') as s:
684 build_root = os.path.join(SCRIPT_PATH, '..', '..')
685 gclient_name = 'gclient'
686 if sys.platform.startswith('win'):
687 gclient_name += '.bat'
688 gclient_path = os.path.join(build_root, '..', 'depot_tools', gclient_name)
689 gclient_cmd = [gclient_path, 'sync', '--force', '--verbose']
690 cmd_dict = {
691 'name': 'update_scripts',
692 'cmd': gclient_cmd,
693 'cwd': build_root,
694 }
695 annotator.print_step(cmd_dict, os.environ, stream)
696 if subprocess.call(gclient_cmd, cwd=build_root) != 0:
697 s.step_text('gclient sync failed!')
698 s.step_warnings()
699 os.environ['RUN_SLAVE_UPDATED_SCRIPTS'] = '1'
700
701 # After running update scripts, set PYTHONIOENCODING=UTF-8 for the real
702 # annotated_run.
703 os.environ['PYTHONIOENCODING'] = 'UTF-8'
704
705 return True
706
707
708 def shell_main(argv):
709 if update_scripts():
710 return subprocess.call([sys.executable] + argv)
711 else:
712 return main(argv)
713
714
715 if __name__ == '__main__':
716 sys.exit(shell_main(sys.argv))
OLDNEW
« no previous file with comments | « third_party/recipe_engine/loader.py ('k') | third_party/recipe_engine/recipe_api.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698