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