Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2013 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 | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Entry point for fully-annotated builds. | 6 """Entry point for fully-annotated builds. |
| 7 | 7 |
| 8 This script is part of the effort to move all builds to annotator-based | 8 This script is part of the effort to move all builds to annotator-based |
| 9 systems. Any builder configured to use the AnnotatorFactory.BaseFactory() | 9 systems. Any builder configured to use the AnnotatorFactory.BaseFactory() |
| 10 found in scripts/master/factory/annotator_factory.py executes a single | 10 found in scripts/master/factory/annotator_factory.py executes a single |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 54 will cease calling the generator and move on to the next item in | 54 will cease calling the generator and move on to the next item in |
| 55 iterable_of_things. | 55 iterable_of_things. |
| 56 | 56 |
| 57 'step_history' is an OrderedDict of {stepname -> StepData}, always representing | 57 'step_history' is an OrderedDict of {stepname -> StepData}, always representing |
| 58 the current history of what steps have run, what they returned, and any | 58 the current history of what steps have run, what they returned, and any |
| 59 json data they emitted. Additionally, the OrderedDict has the following | 59 json data they emitted. Additionally, the OrderedDict has the following |
| 60 convenience functions defined: | 60 convenience functions defined: |
| 61 * last_step - Returns the last step that ran or None | 61 * last_step - Returns the last step that ran or None |
| 62 * nth_step(n) - Returns the N'th step that ran or None | 62 * nth_step(n) - Returns the N'th step that ran or None |
| 63 | 63 |
| 64 'failed' is a boolean representing if the build is in a 'failed' state. | 64 'failed' is a boolean representing if the build is in a 'failed' state. |
|
iannucci
2015/05/27 02:03:27
rename to something more appropriate like 'main.py
luqui
2015/05/28 21:47:38
Done.
| |
| 65 """ | 65 """ |
| 66 | 66 |
| 67 import collections | |
| 68 import contextlib | |
| 67 import copy | 69 import copy |
| 68 import functools | 70 import functools |
| 69 import json | 71 import json |
| 70 import optparse | |
| 71 import os | 72 import os |
| 72 import subprocess | 73 import subprocess |
| 73 import sys | 74 import sys |
| 75 import threading | |
| 74 import traceback | 76 import traceback |
| 75 | 77 |
| 76 import cStringIO | 78 import cStringIO |
| 77 | 79 |
| 78 import common.python26_polyfill # pylint: disable=W0611 | |
| 79 import collections # Import after polyfill to get OrderedDict on 2.6 | |
| 80 | 80 |
| 81 from common import annotator | 81 from . import recipe_loader |
| 82 from common import chromium_utils | 82 from . import recipe_test_api |
| 83 | 83 from . import recipe_util |
| 84 from slave import recipe_loader | 84 from . import recipe_api |
| 85 from slave import recipe_test_api | |
| 86 from slave import recipe_util | |
| 87 from slave import recipe_api | |
| 88 | 85 |
| 89 | 86 |
| 90 SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__)) | 87 SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__)) |
| 91 | 88 |
| 92 | 89 |
| 93 class StepPresentation(object): | 90 class StepPresentation(object): |
| 94 STATUSES = set(('SUCCESS', 'FAILURE', 'WARNING', 'EXCEPTION')) | 91 STATUSES = set(('SUCCESS', 'FAILURE', 'WARNING', 'EXCEPTION')) |
| 95 | 92 |
| 96 def __init__(self): | 93 def __init__(self): |
| 97 self._finalized = False | 94 self._finalized = False |
| (...skipping 179 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 277 | 274 |
| 278 | 275 |
| 279 def get_callable_name(func): | 276 def get_callable_name(func): |
| 280 """Returns __name__ of a callable, handling functools.partial types.""" | 277 """Returns __name__ of a callable, handling functools.partial types.""" |
| 281 if isinstance(func, functools.partial): | 278 if isinstance(func, functools.partial): |
| 282 return get_callable_name(func.func) | 279 return get_callable_name(func.func) |
| 283 else: | 280 else: |
| 284 return func.__name__ | 281 return func.__name__ |
| 285 | 282 |
| 286 | 283 |
| 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. | 284 # Return value of run_steps and RecipeEngine.run. |
| 325 RecipeExecutionResult = collections.namedtuple( | 285 RecipeExecutionResult = collections.namedtuple( |
| 326 'RecipeExecutionResult', 'status_code steps_ran') | 286 'RecipeExecutionResult', 'status_code steps_ran') |
| 327 | 287 |
| 328 | 288 |
| 329 def get_recipe_properties(factory_properties, build_properties): | 289 def run_steps(properties, |
| 330 """Constructs the recipe's properties from buildbot's properties. | 290 stream, |
| 331 | 291 universe, |
| 332 This merges factory_properties and build_properties. Furthermore, it | 292 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). | 293 """Returns a tuple of (status_code, steps_ran). |
| 365 | 294 |
| 366 Only one of these values will be set at a time. This is mainly to support the | 295 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. | 296 testing interface used by unittests/recipes_test.py. |
| 368 """ | 297 """ |
| 369 stream.honor_zero_return_code() | 298 stream.honor_zero_return_code() |
| 370 | 299 |
| 371 # TODO(iannucci): Stop this when blamelist becomes sane data. | 300 # TODO(iannucci): Stop this when blamelist becomes sane data. |
| 372 if ('blamelist_real' in build_properties and | 301 if ('blamelist_real' in properties and |
| 373 'blamelist' in build_properties): | 302 'blamelist' in properties): |
| 374 build_properties['blamelist'] = build_properties['blamelist_real'] | 303 properties['blamelist'] = properties['blamelist_real'] |
| 375 del build_properties['blamelist_real'] | 304 del properties['blamelist_real'] |
| 376 | 305 |
| 377 # NOTE(iannucci): 'root' was a terribly bad idea and has been replaced by | 306 # NOTE(iannucci): 'root' was a terribly bad idea and has been replaced by |
| 378 # 'patch_project'. 'root' had Rietveld knowing about the implementation of | 307 # 'patch_project'. 'root' had Rietveld knowing about the implementation of |
| 379 # the builders. 'patch_project' lets the builder (recipe) decide its own | 308 # the builders. 'patch_project' lets the builder (recipe) decide its own |
| 380 # destiny. | 309 # destiny. |
| 381 build_properties.pop('root', None) | 310 properties.pop('root', None) |
| 382 | |
| 383 properties = get_recipe_properties( | |
| 384 factory_properties=factory_properties, | |
| 385 build_properties=build_properties) | |
| 386 | 311 |
| 387 # TODO(iannucci): A much better way to do this would be to dynamically | 312 # 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 | 313 # detect if the mirrors are actually available during the execution of the |
| 389 # recipe. | 314 # recipe. |
| 390 if ('use_mirror' not in properties and ( | 315 if ('use_mirror' not in properties and ( |
| 391 'TESTING_MASTERNAME' in os.environ or | 316 'TESTING_MASTERNAME' in os.environ or |
| 392 'TESTING_SLAVENAME' in os.environ)): | 317 'TESTING_SLAVENAME' in os.environ)): |
| 393 properties['use_mirror'] = False | 318 properties['use_mirror'] = False |
| 394 | 319 |
| 395 # It's an integration point with a new recipe engine that can run steps | 320 # It's an integration point with a new recipe engine that can run steps |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 435 except recipe_loader.NoSuchRecipe as e: | 360 except recipe_loader.NoSuchRecipe as e: |
| 436 s.step_text('<br/>recipe not found: %s' % e) | 361 s.step_text('<br/>recipe not found: %s' % e) |
| 437 s.step_failure() | 362 s.step_failure() |
| 438 return RecipeExecutionResult(2, None) | 363 return RecipeExecutionResult(2, None) |
| 439 | 364 |
| 440 # Run the steps emitted by a recipe via the engine, emitting annotations | 365 # Run the steps emitted by a recipe via the engine, emitting annotations |
| 441 # into |stream| along the way. | 366 # into |stream| along the way. |
| 442 return engine.run(steps, api) | 367 return engine.run(steps, api) |
| 443 | 368 |
| 444 | 369 |
| 370 def _merge_envs(original, override): | |
| 371 """Merges two environments. | |
| 372 | |
| 373 Returns a new environment dict with entries from |override| overwriting | |
| 374 corresponding entries in |original|. Keys whose value is None will completely | |
| 375 remove the environment variable. Values can contain %(KEY)s strings, which | |
| 376 will be substituted with the values from the original (useful for amending, as | |
| 377 opposed to overwriting, variables like PATH). | |
| 378 """ | |
| 379 result = original.copy() | |
| 380 if not override: | |
| 381 return result | |
| 382 for k, v in override.items(): | |
| 383 if v is None: | |
| 384 if k in result: | |
| 385 del result[k] | |
| 386 else: | |
| 387 result[str(k)] = str(v) % original | |
| 388 return result | |
| 389 | |
| 390 | |
| 391 def _print_step(step, env, stream): | |
| 392 """Prints the step command and relevant metadata. | |
| 393 | |
| 394 Intended to be similar to the information that Buildbot prints at the | |
| 395 beginning of each non-annotator step. | |
| 396 """ | |
| 397 step_info_lines = [] | |
| 398 step_info_lines.append(' '.join(step['cmd'])) | |
| 399 step_info_lines.append('in dir %s:' % (step['cwd'] or os.getcwd())) | |
| 400 for key, value in sorted(step.items()): | |
| 401 if value is not None: | |
| 402 if callable(value): | |
| 403 # This prevents functions from showing up as: | |
| 404 # '<function foo at 0x7f523ec7a410>' | |
| 405 # which is tricky to test. | |
| 406 value = value.__name__+'(...)' | |
| 407 step_info_lines.append(' %s: %s' % (key, value)) | |
| 408 step_info_lines.append('full environment:') | |
| 409 for key, value in sorted(env.items()): | |
| 410 step_info_lines.append(' %s: %s' % (key, value)) | |
| 411 step_info_lines.append('') | |
| 412 stream.emit('\n'.join(step_info_lines)) | |
| 413 | |
| 414 | |
| 415 @contextlib.contextmanager | |
| 416 def _modify_lookup_path(path): | |
| 417 """Places the specified path into os.environ. | |
| 418 | |
| 419 Necessary because subprocess.Popen uses os.environ to perform lookup on the | |
| 420 supplied command, and only uses the |env| kwarg for modifying the environment | |
| 421 of the child process. | |
| 422 """ | |
| 423 saved_path = os.environ['PATH'] | |
| 424 try: | |
| 425 if path is not None: | |
| 426 os.environ['PATH'] = path | |
| 427 yield | |
| 428 finally: | |
| 429 os.environ['PATH'] = saved_path | |
| 430 | |
| 431 | |
| 432 def _normalize_change(change): | |
| 433 assert isinstance(change, dict), 'Change is not a dict' | |
| 434 change = change.copy() | |
| 435 | |
| 436 # Convert when_timestamp to UNIX timestamp. | |
| 437 when = change.get('when_timestamp') | |
| 438 if isinstance(when, datetime.datetime): | |
| 439 when = calendar.timegm(when.utctimetuple()) | |
| 440 change['when_timestamp'] = when | |
| 441 | |
| 442 return change | |
| 443 | |
| 444 | |
| 445 def _trigger_builds(step, trigger_specs): | |
| 446 assert trigger_specs is not None | |
| 447 for trig in trigger_specs: | |
| 448 builder_name = trig.get('builder_name') | |
| 449 if not builder_name: | |
| 450 raise ValueError('Trigger spec: builder_name is not set') | |
| 451 | |
| 452 changes = trig.get('buildbot_changes', []) | |
| 453 assert isinstance(changes, list), 'buildbot_changes must be a list' | |
| 454 changes = map(_normalize_change, changes) | |
| 455 | |
| 456 step.step_trigger(json.dumps({ | |
| 457 'builderNames': [builder_name], | |
| 458 'bucket': trig.get('bucket'), | |
| 459 'changes': changes, | |
| 460 'properties': trig.get('properties'), | |
| 461 }, sort_keys=True)) | |
| 462 | |
| 463 | |
| 464 def _run_annotated_step( | |
| 465 stream, name, cmd, cwd=None, env=None, allow_subannotations=False, | |
| 466 trigger_specs=None, **kwargs): | |
| 467 """Runs a single step. | |
| 468 | |
| 469 Context: | |
| 470 stream: StructuredAnnotationStream to use to emit step | |
| 471 | |
| 472 Step parameters: | |
| 473 name: name of the step, will appear in buildbots waterfall | |
| 474 cmd: command to run, list of one or more strings | |
| 475 cwd: absolute path to working directory for the command | |
| 476 env: dict with overrides for environment variables | |
| 477 allow_subannotations: if True, lets the step emit its own annotations | |
| 478 trigger_specs: a list of trigger specifications, which are dict with keys: | |
| 479 properties: a dict of properties. | |
| 480 Buildbot requires buildername property. | |
| 481 | |
| 482 Known kwargs: | |
| 483 stdout: Path to a file to put step stdout into. If used, stdout won't appear | |
| 484 in annotator's stdout (and |allow_subannotations| is ignored). | |
| 485 stderr: Path to a file to put step stderr into. If used, stderr won't appear | |
| 486 in annotator's stderr. | |
| 487 stdin: Path to a file to read step stdin from. | |
| 488 | |
| 489 Returns the returncode of the step. | |
| 490 """ | |
| 491 if isinstance(cmd, basestring): | |
| 492 cmd = (cmd,) | |
| 493 cmd = map(str, cmd) | |
| 494 | |
| 495 # For error reporting. | |
| 496 step_dict = kwargs.copy() | |
| 497 step_dict.update({ | |
| 498 'name': name, | |
| 499 'cmd': cmd, | |
| 500 'cwd': cwd, | |
| 501 'env': env, | |
| 502 'allow_subannotations': allow_subannotations, | |
| 503 }) | |
| 504 step_env = _merge_envs(os.environ, env) | |
| 505 | |
| 506 step_annotation = stream.step(name) | |
| 507 step_annotation.step_started() | |
| 508 | |
| 509 _print_step(step_dict, step_env, stream) | |
| 510 returncode = 0 | |
| 511 if cmd: | |
| 512 try: | |
| 513 # Open file handles for IO redirection based on file names in step_dict. | |
| 514 fhandles = { | |
| 515 'stdout': subprocess.PIPE, | |
| 516 'stderr': subprocess.PIPE, | |
| 517 'stdin': None, | |
| 518 } | |
| 519 for key in fhandles: | |
| 520 if key in step_dict: | |
| 521 fhandles[key] = open(step_dict[key], | |
| 522 'rb' if key == 'stdin' else 'wb') | |
| 523 | |
| 524 if sys.platform.startswith('win'): | |
| 525 # Windows has a bad habit of opening a dialog when a console program | |
| 526 # crashes, rather than just letting it crash. Therefore, when a program | |
| 527 # crashes on Windows, we don't find out until the build step times out. | |
| 528 # This code prevents the dialog from appearing, so that we find out | |
| 529 # immediately and don't waste time waiting for a user to close the | |
| 530 # dialog. | |
| 531 import ctypes | |
| 532 # SetErrorMode(SEM_NOGPFAULTERRORBOX). For more information, see: | |
| 533 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms680621.aspx | |
| 534 ctypes.windll.kernel32.SetErrorMode(0x0002) | |
| 535 # CREATE_NO_WINDOW. For more information, see: | |
| 536 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx | |
| 537 creationflags = 0x8000000 | |
| 538 else: | |
| 539 creationflags = 0 | |
| 540 | |
| 541 with _modify_lookup_path(step_env.get('PATH')): | |
| 542 proc = subprocess.Popen( | |
| 543 cmd, | |
| 544 env=step_env, | |
| 545 cwd=cwd, | |
| 546 universal_newlines=True, | |
| 547 creationflags=creationflags, | |
| 548 **fhandles) | |
| 549 | |
| 550 # Safe to close file handles now that subprocess has inherited them. | |
| 551 for handle in fhandles.itervalues(): | |
| 552 if isinstance(handle, file): | |
| 553 handle.close() | |
| 554 | |
| 555 outlock = threading.Lock() | |
| 556 def filter_lines(lock, allow_subannotations, inhandle, outhandle): | |
| 557 while True: | |
| 558 line = inhandle.readline() | |
| 559 if not line: | |
| 560 break | |
| 561 lock.acquire() | |
| 562 try: | |
| 563 if not allow_subannotations and line.startswith('@@@'): | |
| 564 outhandle.write('!') | |
| 565 outhandle.write(line) | |
| 566 outhandle.flush() | |
| 567 finally: | |
| 568 lock.release() | |
| 569 | |
| 570 # Pump piped stdio through filter_lines. IO going to files on disk is | |
| 571 # not filtered. | |
| 572 threads = [] | |
| 573 for key in ('stdout', 'stderr'): | |
| 574 if fhandles[key] == subprocess.PIPE: | |
| 575 inhandle = getattr(proc, key) | |
| 576 outhandle = getattr(sys, key) | |
| 577 threads.append(threading.Thread( | |
| 578 target=filter_lines, | |
| 579 args=(outlock, allow_subannotations, inhandle, outhandle))) | |
| 580 | |
| 581 for th in threads: | |
| 582 th.start() | |
| 583 proc.wait() | |
| 584 for th in threads: | |
| 585 th.join() | |
| 586 returncode = proc.returncode | |
| 587 except OSError: | |
| 588 # File wasn't found, error will be reported to stream when the exception | |
| 589 # crosses the context manager. | |
| 590 step_annotation.step_exception_occured(*sys.exc_info()) | |
| 591 raise | |
| 592 | |
| 593 # TODO(martiniss) move logic into own module? | |
| 594 if trigger_specs: | |
| 595 _trigger_builds(step_annotation, trigger_specs) | |
| 596 | |
| 597 return step_annotation, returncode | |
| 598 | |
| 599 | |
| 445 class RecipeEngine(object): | 600 class RecipeEngine(object): |
| 446 """Knows how to execute steps emitted by a recipe, holds global state such as | 601 """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 | 602 step history and build properties. Each recipe module API has a reference to |
| 448 this object. | 603 this object. |
| 449 | 604 |
| 450 Recipe modules that are aware of the engine: | 605 Recipe modules that are aware of the engine: |
| 451 * properties - uses engine.properties. | 606 * properties - uses engine.properties. |
| 452 * step_history - uses engine.step_history. | 607 * step_history - uses engine.step_history. |
| 453 * step - uses engine.create_step(...). | 608 * step - uses engine.create_step(...). |
| 454 | 609 |
| (...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 549 step_test = self._test_data.pop_step_test_data(step['name'], | 704 step_test = self._test_data.pop_step_test_data(step['name'], |
| 550 test_data_fn) | 705 test_data_fn) |
| 551 placeholders = render_step(step, step_test) | 706 placeholders = render_step(step, step_test) |
| 552 | 707 |
| 553 self._step_history[step['name']] = step | 708 self._step_history[step['name']] = step |
| 554 self._emit_results() | 709 self._emit_results() |
| 555 | 710 |
| 556 step_result = None | 711 step_result = None |
| 557 | 712 |
| 558 if not self._test_data.enabled: | 713 if not self._test_data.enabled: |
| 559 self._previous_step_annotation, retcode = annotator.run_step( | 714 self._previous_step_annotation, retcode = _run_annotated_step( |
| 560 self._stream, **step) | 715 self._stream, **step) |
| 561 | 716 |
| 562 step_result = StepData(step, retcode) | 717 step_result = StepData(step, retcode) |
| 563 self._previous_step_annotation.annotation_stream.step_cursor(step['name']) | 718 self._previous_step_annotation.annotation_stream.step_cursor(step['name']) |
| 564 else: | 719 else: |
| 565 self._previous_step_annotation = annotation = self._stream.step( | 720 self._previous_step_annotation = annotation = self._stream.step( |
| 566 step['name']) | 721 step['name']) |
| 567 annotation.step_started() | 722 annotation.step_started() |
| 568 try: | 723 try: |
| 569 annotation.stream = cStringIO.StringIO() | 724 annotation.stream = cStringIO.StringIO() |
| (...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 643 self._step_history[final_result['name']] = final_result | 798 self._step_history[final_result['name']] = final_result |
| 644 | 799 |
| 645 return RecipeExecutionResult(retcode, self._step_history) | 800 return RecipeExecutionResult(retcode, self._step_history) |
| 646 | 801 |
| 647 def create_step(self, step): # pylint: disable=R0201 | 802 def create_step(self, step): # pylint: disable=R0201 |
| 648 # This version of engine doesn't do anything, just converts step to dict | 803 # This version of engine doesn't do anything, just converts step to dict |
| 649 # (that is consumed by annotator engine). | 804 # (that is consumed by annotator engine). |
| 650 return step.as_jsonish() | 805 return step.as_jsonish() |
| 651 | 806 |
| 652 | 807 |
| 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)) | |
| OLD | NEW |