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

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: Only emit (end) when subannotations were actually emitted 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') | scripts/slave/annotated_run.py » ('J')
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 }
523 for key in fhandles: 524 for key in fhandles:
524 if key in step_dict: 525 if key in step_dict:
525 fhandles[key] = open(step_dict[key], 526 fhandles[key] = open(step_dict[key],
526 'rb' if key == 'stdin' else 'wb') 527 'rb' if key == 'stdin' else 'wb')
527 528
528 with modify_lookup_path(step_env.get('PATH')): 529 with modify_lookup_path(step_env.get('PATH')):
529 proc = subprocess.Popen( 530 proc = subprocess.Popen(
530 cmd, 531 cmd,
531 env=step_env, 532 env=step_env,
532 cwd=cwd, 533 cwd=cwd,
533 universal_newlines=True, 534 universal_newlines=True,
534 **fhandles) 535 **fhandles)
535 536
536 # Safe to close file handles now that subprocess has inherited them. 537 # Safe to close file handles now that subprocess has inherited them.
537 for handle in fhandles.itervalues(): 538 for handle in fhandles.itervalues():
538 if isinstance(handle, file): 539 if isinstance(handle, file):
539 handle.close() 540 handle.close()
540 541
541 outlock = threading.Lock() 542 outlock = threading.Lock()
542 def filter_lines(lock, allow_subannotations, inhandle, outhandle): 543 def filter_lines(inhandle, outhandle):
iannucci 2015/04/03 20:45:59 yay closures
luqui 2015/04/07 23:26:53 Acknowledged.
543 while True: 544 while True:
544 line = inhandle.readline() 545 line = inhandle.readline()
545 if not line: 546 if not line:
546 break 547 break
547 lock.acquire() 548 with outlock:
548 try: 549 if line.startswith('@@@'):
549 if not allow_subannotations and line.startswith('@@@'): 550 if subannotator:
550 outhandle.write('!') 551 # The subannotator might write to the handle, thus the lock.
iannucci 2015/04/03 20:45:59 where does subannotator get bound to outhandle?
luqui 2015/04/07 23:26:54 outhandle can be sys.stdout, which is written to b
551 outhandle.write(line) 552 MatchAnnotation(line.strip(), subannotator)
553 else:
554 outhandle.write('!')
555 outhandle.write(line)
556 else:
557 outhandle.write(line)
552 outhandle.flush() 558 outhandle.flush()
553 finally:
554 lock.release()
555 559
556 # Pump piped stdio through filter_lines. IO going to files on disk is 560 # Pump piped stdio through filter_lines. IO going to files on disk is
557 # not filtered. 561 # not filtered.
558 threads = [] 562 threads = []
559 for key in ('stdout', 'stderr'): 563 for key in ('stdout', 'stderr'):
560 if fhandles[key] == subprocess.PIPE: 564 if fhandles[key] == subprocess.PIPE:
561 inhandle = getattr(proc, key) 565 inhandle = getattr(proc, key)
562 outhandle = getattr(sys, key) 566 outhandle = getattr(sys, key)
563 threads.append(threading.Thread( 567 threads.append(threading.Thread(
564 target=filter_lines, 568 target=filter_lines,
565 args=(outlock, allow_subannotations, inhandle, outhandle))) 569 args=(inhandle, outhandle)))
566 570
567 for th in threads: 571 for th in threads:
568 th.start() 572 th.start()
569 proc.wait() 573 proc.wait()
570 for th in threads: 574 for th in threads:
571 th.join() 575 th.join()
572 returncode = proc.returncode 576 returncode = proc.returncode
573 except OSError: 577 except OSError:
574 # File wasn't found, error will be reported to stream when the exception 578 # File wasn't found, error will be reported to stream when the exception
575 # crosses the context manager. 579 # crosses the context manager.
576 step_annotation.step_exception_occured(*sys.exc_info()) 580 step_annotation.step_exception_occured(*sys.exc_info())
577 raise 581 raise
578 582
579 # TODO(martiniss) move logic into own module? 583 # TODO(martiniss) move logic into own module?
580 if trigger_specs: 584 if trigger_specs:
581 triggerBuilds(step_annotation, trigger_specs) 585 triggerBuilds(step_annotation, trigger_specs)
582 586
583 return step_annotation, returncode 587 return returncode
584 588
585 def update_build_failure(failure, retcode, **_kwargs): 589 def update_build_failure(failure, retcode, **_kwargs):
586 """Potentially moves failure from False to True, depending on returncode of 590 """Potentially moves failure from False to True, depending on returncode of
587 the run step and the step's configuration. 591 the run step and the step's configuration.
588 592
589 can_fail_build: A boolean indicating that a bad retcode for this step should 593 can_fail_build: A boolean indicating that a bad retcode for this step should
590 be intepreted as a build failure. 594 be intepreted as a build failure.
591 595
592 Returns new value for failure. 596 Returns new value for failure.
593 597
594 Called externally from annotated_run, which is why it's a separate function. 598 Called externally from annotated_run, which is why it's a separate function.
595 """ 599 """
596 # TODO(iannucci): Allow step to specify "OK" return values besides 0? 600 # TODO(iannucci): Allow step to specify "OK" return values besides 0?
597 return failure or retcode 601 return failure or retcode
598 602
599 def run_steps(steps, build_failure): 603 def run_steps(steps, build_failure):
600 for step in steps: 604 for step in steps:
601 error = _validate_step(step) 605 error = _validate_step(step)
602 if error: 606 if error:
603 print 'Invalid step - %s\n%s' % (error, json.dumps(step, indent=2)) 607 print 'Invalid step - %s\n%s' % (error, json.dumps(step, indent=2))
604 sys.exit(1) 608 sys.exit(1)
605 609
606 stream = StructuredAnnotationStream() 610 stream = StructuredAnnotationStream()
607 ret_codes = [] 611 ret_codes = []
608 build_failure = False 612 build_failure = False
609 prev_annotation = None 613 prev_annotation = None
610 for step in steps: 614 for step in steps:
611 if build_failure and not step.get('always_run', False): 615 if build_failure and not step.get('always_run', False):
iannucci 2015/04/03 20:45:59 this function isn't used for anything... it should
luqui 2015/04/07 23:26:54 A bit of code removal and unittest changes to do t
612 ret = None 616 ret = None
613 else: 617 else:
614 prev_annotation, ret = run_step(stream, **step) 618 prev_annotation = stream.step(step['name'])
619 prev_annotation.step_started()
620 ret = run_step(stream, step_annotation=prev_annotation, **step)
615 stream = prev_annotation.annotation_stream 621 stream = prev_annotation.annotation_stream
616 if ret > 0: 622 if ret > 0:
617 stream.step_cursor(stream.current_step) 623 stream.step_cursor(stream.current_step)
618 stream.emit('step returned non-zero exit code: %d' % ret) 624 stream.emit('step returned non-zero exit code: %d' % ret)
619 prev_annotation.step_failure() 625 prev_annotation.step_failure()
620 626
621 prev_annotation.step_ended() 627 prev_annotation.step_ended()
622 build_failure = update_build_failure(build_failure, ret) 628 build_failure = update_build_failure(build_failure, ret)
623 ret_codes.append(ret) 629 ret_codes.append(ret)
624 if prev_annotation: 630 if prev_annotation:
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
665 steps.extend(json.load(sys.stdin, object_hook=force_dict_strs)) 671 steps.extend(json.load(sys.stdin, object_hook=force_dict_strs))
666 else: 672 else:
667 with open(args[0], 'rb') as f: 673 with open(args[0], 'rb') as f:
668 steps.extend(json.load(f, object_hook=force_dict_strs)) 674 steps.extend(json.load(f, object_hook=force_dict_strs))
669 675
670 return 1 if run_steps(steps, False)[0] else 0 676 return 1 if run_steps(steps, False)[0] else 0
671 677
672 678
673 if __name__ == '__main__': 679 if __name__ == '__main__':
674 sys.exit(main()) 680 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | scripts/slave/annotated_run.py » ('j') | scripts/slave/annotated_run.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698