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

Side by Side Diff: scripts/common/annotator.py

Issue 1001183002: Make allow_subannotations more robust. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: fixnits Created 5 years, 8 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 | « no previous file | scripts/slave/annotated_run.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 #!/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
(...skipping 446 matching lines...) Expand 10 before | Expand all | Expand 10 after
457 457
458 step.step_trigger(json.dumps({ 458 step.step_trigger(json.dumps({
459 'builderNames': [builder_name], 459 'builderNames': [builder_name],
460 'bucket': trig.get('bucket'), 460 'bucket': trig.get('bucket'),
461 'changes': changes, 461 'changes': changes,
462 'properties': trig.get('properties'), 462 'properties': trig.get('properties'),
463 }, sort_keys=True)) 463 }, sort_keys=True))
464 464
465 465
466 def run_step(stream, name, cmd, 466 def run_step(stream, name, cmd,
467 step_annotation,
467 cwd=None, env=None, 468 cwd=None, env=None,
468 allow_subannotations=False, 469 subannotator=None,
469 trigger_specs=None, 470 trigger_specs=None,
470 **kwargs): 471 **kwargs):
471 """Runs a single step. 472 """Runs a single step.
472 473
473 Context: 474 Context:
474 stream: StructuredAnnotationStream to use to emit step 475 stream: StructuredAnnotationStream to use to emit step
476 step_annotation: optional StructuredAnnotationStep to use instead
477 of creating one
475 478
476 Step parameters: 479 Step parameters:
477 name: name of the step, will appear in buildbots waterfall 480 name: name of the step, will appear in buildbots waterfall
478 cmd: command to run, list of one or more strings 481 cmd: command to run, list of one or more strings
479 cwd: absolute path to working directory for the command 482 cwd: absolute path to working directory for the command
480 env: dict with overrides for environment variables 483 env: dict with overrides for environment variables
481 allow_subannotations: if True, lets the step emit its own annotations 484 subannotator: a callback_implementor class used to parse subannotations
485 that the command emits; if None, subannotations will be suppressed.
482 trigger_specs: a list of trigger specifications, which are dict with keys: 486 trigger_specs: a list of trigger specifications, which are dict with keys:
483 properties: a dict of properties. 487 properties: a dict of properties.
484 Buildbot requires buildername property. 488 Buildbot requires buildername property.
485 489
486 Known kwargs: 490 Known kwargs:
487 stdout: Path to a file to put step stdout into. If used, stdout won't appear 491 stdout: Path to a file to put step stdout into. If used, stdout won't appear
488 in annotator's stdout (and |allow_subannotations| is ignored). 492 in annotator's stdout (and |allow_subannotations| is ignored).
489 stderr: Path to a file to put step stderr into. If used, stderr won't appear 493 stderr: Path to a file to put step stderr into. If used, stderr won't appear
490 in annotator's stderr. 494 in annotator's stderr.
491 stdin: Path to a file to read step stdin from. 495 stdin: Path to a file to read step stdin from.
492 496
493 Returns the returncode of the step. 497 Returns the returncode of the step.
494 """ 498 """
495 if isinstance(cmd, basestring): 499 if isinstance(cmd, basestring):
496 cmd = (cmd,) 500 cmd = (cmd,)
497 cmd = map(str, cmd) 501 cmd = map(str, cmd)
498 502
499 # For error reporting. 503 # For error reporting.
500 step_dict = kwargs.copy() 504 step_dict = kwargs.copy()
501 step_dict.update({ 505 step_dict.update({
502 'name': name, 506 'name': name,
503 'cmd': cmd, 507 'cmd': cmd,
504 'cwd': cwd, 508 'cwd': cwd,
505 'env': env, 509 'env': env,
506 'allow_subannotations': allow_subannotations, 510 'allow_subannotations': subannotator is not None,
507 }) 511 })
508 step_env = _merge_envs(os.environ, env) 512 step_env = _merge_envs(os.environ, env)
509 513
510 step_annotation = stream.step(name)
511 step_annotation.step_started()
512
513 print_step(step_dict, step_env, stream) 514 print_step(step_dict, step_env, stream)
514 returncode = 0 515 returncode = 0
515 if cmd: 516 if cmd:
516 try: 517 try:
517 # Open file handles for IO redirection based on file names in step_dict. 518 # Open file handles for IO redirection based on file names in step_dict.
518 fhandles = { 519 fhandles = {
519 'stdout': subprocess.PIPE, 520 'stdout': subprocess.PIPE,
520 'stderr': subprocess.PIPE, 521 'stderr': subprocess.PIPE,
521 'stdin': None, 522 'stdin': None,
522 } 523 }
(...skipping 27 matching lines...) Expand all
550 universal_newlines=True, 551 universal_newlines=True,
551 creationflags=creationflags, 552 creationflags=creationflags,
552 **fhandles) 553 **fhandles)
553 554
554 # Safe to close file handles now that subprocess has inherited them. 555 # Safe to close file handles now that subprocess has inherited them.
555 for handle in fhandles.itervalues(): 556 for handle in fhandles.itervalues():
556 if isinstance(handle, file): 557 if isinstance(handle, file):
557 handle.close() 558 handle.close()
558 559
559 outlock = threading.Lock() 560 outlock = threading.Lock()
560 def filter_lines(lock, allow_subannotations, inhandle, outhandle): 561 def filter_lines(inhandle, outhandle):
561 while True: 562 while True:
562 line = inhandle.readline() 563 line = inhandle.readline()
563 if not line: 564 if not line:
564 break 565 break
565 lock.acquire() 566 with outlock:
566 try: 567 if line.startswith('@@@'):
567 if not allow_subannotations and line.startswith('@@@'): 568 if subannotator:
568 outhandle.write('!') 569 # The subannotator might write to the handle, thus the lock.
569 outhandle.write(line) 570 MatchAnnotation(line.strip(), subannotator)
571 else:
572 outhandle.write('!')
573 outhandle.write(line)
574 else:
575 outhandle.write(line)
570 outhandle.flush() 576 outhandle.flush()
571 finally:
572 lock.release()
573 577
574 # Pump piped stdio through filter_lines. IO going to files on disk is 578 # Pump piped stdio through filter_lines. IO going to files on disk is
575 # not filtered. 579 # not filtered.
576 threads = [] 580 threads = []
577 for key in ('stdout', 'stderr'): 581 for key in ('stdout', 'stderr'):
578 if fhandles[key] == subprocess.PIPE: 582 if fhandles[key] == subprocess.PIPE:
579 inhandle = getattr(proc, key) 583 inhandle = getattr(proc, key)
580 outhandle = getattr(sys, key) 584 outhandle = getattr(sys, key)
581 threads.append(threading.Thread( 585 threads.append(threading.Thread(
582 target=filter_lines, 586 target=filter_lines,
583 args=(outlock, allow_subannotations, inhandle, outhandle))) 587 args=(inhandle, outhandle)))
584 588
585 for th in threads: 589 for th in threads:
586 th.start() 590 th.start()
587 proc.wait() 591 proc.wait()
588 for th in threads: 592 for th in threads:
589 th.join() 593 th.join()
590 returncode = proc.returncode 594 returncode = proc.returncode
591 except OSError: 595 except OSError:
592 # File wasn't found, error will be reported to stream when the exception 596 # File wasn't found, error will be reported to stream when the exception
593 # crosses the context manager. 597 # crosses the context manager.
594 step_annotation.step_exception_occured(*sys.exc_info()) 598 step_annotation.step_exception_occured(*sys.exc_info())
595 raise 599 raise
596 600
597 # TODO(martiniss) move logic into own module? 601 # TODO(martiniss) move logic into own module?
598 if trigger_specs: 602 if trigger_specs:
599 triggerBuilds(step_annotation, trigger_specs) 603 triggerBuilds(step_annotation, trigger_specs)
600 604
601 return step_annotation, returncode 605 return returncode
602 606
603 def update_build_failure(failure, retcode, **_kwargs): 607 def update_build_failure(failure, retcode, **_kwargs):
604 """Potentially moves failure from False to True, depending on returncode of 608 """Potentially moves failure from False to True, depending on returncode of
605 the run step and the step's configuration. 609 the run step and the step's configuration.
606 610
607 can_fail_build: A boolean indicating that a bad retcode for this step should 611 can_fail_build: A boolean indicating that a bad retcode for this step should
608 be intepreted as a build failure. 612 be intepreted as a build failure.
609 613
610 Returns new value for failure. 614 Returns new value for failure.
611 615
(...skipping 10 matching lines...) Expand all
622 sys.exit(1) 626 sys.exit(1)
623 627
624 stream = StructuredAnnotationStream() 628 stream = StructuredAnnotationStream()
625 ret_codes = [] 629 ret_codes = []
626 build_failure = False 630 build_failure = False
627 prev_annotation = None 631 prev_annotation = None
628 for step in steps: 632 for step in steps:
629 if build_failure and not step.get('always_run', False): 633 if build_failure and not step.get('always_run', False):
630 ret = None 634 ret = None
631 else: 635 else:
632 prev_annotation, ret = run_step(stream, **step) 636 prev_annotation = stream.step(step['name'])
637 prev_annotation.step_started()
638 ret = run_step(stream, step_annotation=prev_annotation, **step)
633 stream = prev_annotation.annotation_stream 639 stream = prev_annotation.annotation_stream
634 if ret > 0: 640 if ret > 0:
635 stream.step_cursor(stream.current_step) 641 stream.step_cursor(stream.current_step)
636 stream.emit('step returned non-zero exit code: %d' % ret) 642 stream.emit('step returned non-zero exit code: %d' % ret)
637 prev_annotation.step_failure() 643 prev_annotation.step_failure()
638 644
639 prev_annotation.step_ended() 645 prev_annotation.step_ended()
640 build_failure = update_build_failure(build_failure, ret) 646 build_failure = update_build_failure(build_failure, ret)
641 ret_codes.append(ret) 647 ret_codes.append(ret)
642 if prev_annotation: 648 if prev_annotation:
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
683 steps.extend(json.load(sys.stdin, object_hook=force_dict_strs)) 689 steps.extend(json.load(sys.stdin, object_hook=force_dict_strs))
684 else: 690 else:
685 with open(args[0], 'rb') as f: 691 with open(args[0], 'rb') as f:
686 steps.extend(json.load(f, object_hook=force_dict_strs)) 692 steps.extend(json.load(f, object_hook=force_dict_strs))
687 693
688 return 1 if run_steps(steps, False)[0] else 0 694 return 1 if run_steps(steps, False)[0] else 0
689 695
690 696
691 if __name__ == '__main__': 697 if __name__ == '__main__':
692 sys.exit(main()) 698 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | scripts/slave/annotated_run.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698