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 |