| 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 """Contains generating and parsing systems of the Chromium Buildbot Annotator. | 6 """Contains generating and parsing systems of the Chromium Buildbot Annotator. |
| 7 | 7 |
| 8 When executed as a script, this reads step name / command pairs from a file and | 8 When executed as a script, this reads step name / command pairs from a file and |
| 9 executes those lines while annotating the output. The input is json: | 9 executes those lines while annotating the output. The input is json: |
| 10 | 10 |
| 11 [{"name": "step_name", "cmd": ["command", "arg1", "arg2"]}, | 11 [{"name": "step_name", "cmd": ["command", "arg1", "arg2"]}, |
| 12 {"name": "step_name2", "cmd": ["command2", "arg1"]}] | 12 {"name": "step_name2", "cmd": ["command2", "arg1"]}] |
| 13 | 13 |
| 14 """ | 14 """ |
| 15 | 15 |
| 16 import json | 16 import json |
| 17 import optparse | 17 import optparse |
| 18 import os | 18 import os |
| 19 import re | 19 import re |
| 20 import subprocess |
| 20 import sys | 21 import sys |
| 22 import threading |
| 21 import traceback | 23 import traceback |
| 22 | 24 |
| 23 from common import chromium_utils | |
| 24 | |
| 25 | 25 |
| 26 def emit(line, stream, flush_before=None): | 26 def emit(line, stream, flush_before=None): |
| 27 if flush_before: | 27 if flush_before: |
| 28 flush_before.flush() | 28 flush_before.flush() |
| 29 print >> stream, '\n' + line | 29 print >> stream |
| 30 # WinDOS can only handle 64kb of output to the console at a time, per process. |
| 31 if sys.platform.startswith('win'): |
| 32 lim = 2**15 |
| 33 while line: |
| 34 to_print, line = line[:lim], line[lim:] |
| 35 stream.write(to_print) |
| 36 else: |
| 37 print >> stream, line |
| 30 stream.flush() | 38 stream.flush() |
| 31 | 39 |
| 32 | 40 |
| 33 class StepCommands(object): | 41 class StepCommands(object): |
| 34 """Class holding step commands. Intended to be subclassed.""" | 42 """Class holding step commands. Intended to be subclassed.""" |
| 35 def __init__(self, stream, flush_before): | 43 def __init__(self, stream, flush_before): |
| 36 self.stream = stream | 44 self.stream = stream |
| 37 self.flush_before = flush_before | 45 self.flush_before = flush_before |
| 38 | 46 |
| 39 def emit(self, line): | 47 def emit(self, line): |
| (...skipping 366 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 406 | 414 |
| 407 # For error reporting. | 415 # For error reporting. |
| 408 step_dict = locals().copy() | 416 step_dict = locals().copy() |
| 409 step_dict.pop('kwargs') | 417 step_dict.pop('kwargs') |
| 410 step_dict.pop('stream') | 418 step_dict.pop('stream') |
| 411 step_dict.update(kwargs) | 419 step_dict.update(kwargs) |
| 412 | 420 |
| 413 for step_name in (seed_steps or []): | 421 for step_name in (seed_steps or []): |
| 414 stream.seed_step(step_name) | 422 stream.seed_step(step_name) |
| 415 | 423 |
| 416 filter_obj = None | |
| 417 if not allow_subannotations: | |
| 418 class AnnotationFilter(chromium_utils.RunCommandFilter): | |
| 419 # Missing __init__ | |
| 420 # Method could be a function (but not really since it's an override) | |
| 421 # pylint: disable=W0232,R0201 | |
| 422 def FilterLine(self, line): | |
| 423 return line.replace('@@@', '###') | |
| 424 filter_obj = AnnotationFilter() | |
| 425 | |
| 426 ret = None | 424 ret = None |
| 427 with stream.step(name) as s: | 425 with stream.step(name) as s: |
| 428 print_step(step_dict) | 426 print_step(step_dict) |
| 429 try: | 427 try: |
| 430 ret = chromium_utils.RunCommand(command=cmd, | 428 proc = subprocess.Popen( |
| 431 cwd=cwd, | 429 cmd, |
| 432 env=_merge_envs(os.environ, env), | 430 env=_merge_envs(os.environ, env), |
| 433 filter_obj=filter_obj, | 431 cwd=cwd, |
| 434 print_cmd=False) | 432 stdout=subprocess.PIPE, |
| 433 stderr=subprocess.PIPE) |
| 434 |
| 435 outlock = threading.Lock() |
| 436 def filter_lines(lock, allow_subannotations, inhandle, outhandle): |
| 437 while True: |
| 438 line = inhandle.readline() |
| 439 if not line: |
| 440 break |
| 441 lock.acquire() |
| 442 try: |
| 443 if not allow_subannotations and line.startswith('@@@'): |
| 444 outhandle.write('!') |
| 445 outhandle.write(line) |
| 446 outhandle.flush() |
| 447 finally: |
| 448 lock.release() |
| 449 |
| 450 outthread = threading.Thread( |
| 451 target=filter_lines, |
| 452 args=(outlock, allow_subannotations, proc.stdout, sys.stdout)) |
| 453 errthread = threading.Thread( |
| 454 target=filter_lines, |
| 455 args=(outlock, allow_subannotations, proc.stderr, sys.stderr)) |
| 456 outthread.start() |
| 457 errthread.start() |
| 458 proc.wait() |
| 459 outthread.join() |
| 460 errthread.join() |
| 461 ret = proc.returncode |
| 435 except OSError: | 462 except OSError: |
| 436 # File wasn't found, error will be reported to stream when the exception | 463 # File wasn't found, error will be reported to stream when the exception |
| 437 # crosses the context manager. | 464 # crosses the context manager. |
| 438 ret = -1 | 465 ret = -1 |
| 439 raise | 466 raise |
| 440 if ret > 0: | 467 if ret > 0: |
| 441 stream.step_cursor(stream.current_step) | 468 stream.step_cursor(stream.current_step) |
| 442 print 'step returned non-zero exit code: %d' % ret | 469 print 'step returned non-zero exit code: %d' % ret |
| 443 s.step_failure() | 470 s.step_failure() |
| 444 if followup_fn: | 471 if followup_fn: |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 482 usage = '%s <command list file or - for stdin>' % sys.argv[0] | 509 usage = '%s <command list file or - for stdin>' % sys.argv[0] |
| 483 parser = optparse.OptionParser(usage=usage) | 510 parser = optparse.OptionParser(usage=usage) |
| 484 _, args = parser.parse_args() | 511 _, args = parser.parse_args() |
| 485 if not args: | 512 if not args: |
| 486 parser.error('Must specify an input filename.') | 513 parser.error('Must specify an input filename.') |
| 487 if len(args) > 1: | 514 if len(args) > 1: |
| 488 parser.error('Too many arguments specified.') | 515 parser.error('Too many arguments specified.') |
| 489 | 516 |
| 490 steps = [] | 517 steps = [] |
| 491 | 518 |
| 519 def force_list_str(lst): |
| 520 ret = [] |
| 521 for v in lst: |
| 522 if isinstance(v, basestring): |
| 523 v = str(v) |
| 524 elif isinstance(v, list): |
| 525 v = force_list_str(v) |
| 526 elif isinstance(v, dict): |
| 527 v = force_dict_strs(v) |
| 528 ret.append(v) |
| 529 return ret |
| 530 |
| 531 def force_dict_strs(obj): |
| 532 ret = {} |
| 533 for k, v in obj.iteritems(): |
| 534 if isinstance(v, basestring): |
| 535 v = str(v) |
| 536 elif isinstance(v, list): |
| 537 v = force_list_str(v) |
| 538 elif isinstance(v, dict): |
| 539 v = force_dict_strs(v) |
| 540 ret[str(k)] = v |
| 541 return ret |
| 542 |
| 492 if args[0] == '-': | 543 if args[0] == '-': |
| 493 steps.extend(json.load(sys.stdin)) | 544 steps.extend(json.load(sys.stdin, object_hook=force_dict_strs)) |
| 494 else: | 545 else: |
| 495 with open(args[0], 'rb') as f: | 546 with open(args[0], 'rb') as f: |
| 496 steps.extend(json.load(f)) | 547 steps.extend(json.load(f, object_hook=force_dict_strs)) |
| 497 | 548 |
| 498 return 1 if run_steps(steps, False)[0] else 0 | 549 return 1 if run_steps(steps, False)[0] else 0 |
| 499 | 550 |
| 500 | 551 |
| 501 if __name__ == '__main__': | 552 if __name__ == '__main__': |
| 502 sys.exit(main()) | 553 sys.exit(main()) |
| OLD | NEW |