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

Unified Diff: third_party/recipe_engine/third_party/annotator.py

Issue 1151423002: Move recipe engine to third_party/recipe_engine. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Copyright notices Created 5 years, 7 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 side-by-side diff with in-line comments
Download patch
Index: third_party/recipe_engine/third_party/annotator.py
diff --git a/third_party/recipe_engine/third_party/annotator.py b/third_party/recipe_engine/third_party/annotator.py
new file mode 100644
index 0000000000000000000000000000000000000000..dd222524ee170987ecbc600793d7ee1b397f5d70
--- /dev/null
+++ b/third_party/recipe_engine/third_party/annotator.py
@@ -0,0 +1,323 @@
+#!/usr/bin/env python
iannucci 2015/05/27 02:03:27 don't want the shebang any more move to ..
luqui 2015/05/28 21:47:38 Done.
+# Copyright (c) 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Contains the parsing system of the Chromium Buildbot Annotator."""
+
+import sys
+import traceback
+
+# These are maps of annotation key -> number of expected arguments.
+STEP_ANNOTATIONS = {
+ 'SET_BUILD_PROPERTY': 2,
+ 'STEP_CLEAR': 0,
+ 'STEP_EXCEPTION': 0,
+ 'STEP_FAILURE': 0,
+ 'STEP_LINK': 2,
+ 'STEP_LOG_END': 1,
+ 'STEP_LOG_END_PERF': 2,
+ 'STEP_LOG_LINE': 2,
+ 'STEP_SUMMARY_CLEAR': 0,
+ 'STEP_SUMMARY_TEXT': 1,
+ 'STEP_TEXT': 1,
+ 'STEP_TRIGGER': 1,
+ 'STEP_WARNINGS': 0,
+}
+
+CONTROL_ANNOTATIONS = {
+ 'STEP_CLOSED': 0,
+ 'STEP_STARTED': 0,
+}
+
+STREAM_ANNOTATIONS = {
+ 'HALT_ON_FAILURE': 0,
+ 'HONOR_ZERO_RETURN_CODE': 0,
+ 'SEED_STEP': 1,
+ 'SEED_STEP_TEXT': 2,
+ 'STEP_CURSOR': 1,
+}
+
+DEPRECATED_ANNOTATIONS = {
+ 'BUILD_STEP': 1,
+}
+
+ALL_ANNOTATIONS = {}
+ALL_ANNOTATIONS.update(STEP_ANNOTATIONS)
+ALL_ANNOTATIONS.update(CONTROL_ANNOTATIONS)
+ALL_ANNOTATIONS.update(STREAM_ANNOTATIONS)
+ALL_ANNOTATIONS.update(DEPRECATED_ANNOTATIONS)
+
+# This is a mapping of old_annotation_name -> new_annotation_name.
+# Theoretically all annotator scripts should use the new names, but it's hard
+# to tell due to the decentralized nature of the annotator.
+DEPRECATED_ALIASES = {
+ 'BUILD_FAILED': 'STEP_FAILURE',
+ 'BUILD_WARNINGS': 'STEP_WARNINGS',
+ 'BUILD_EXCEPTION': 'STEP_EXCEPTION',
+ 'link': 'STEP_LINK',
+}
+
+# A couple of the annotations have the format:
+# @@@THING arg@@@
+# for reasons no one knows. We only need this case until all masters have been
+# restarted to pick up the new master-side parsing code.
+OLD_STYLE_ANNOTATIONS = set((
+ 'SEED_STEP',
+ 'STEP_CURSOR',
+))
+
+
+def emit(line, stream, flush_before=None):
+ if flush_before:
+ flush_before.flush()
+ print >> stream
+ # WinDOS can only handle 64kb of output to the console at a time, per process.
+ if sys.platform.startswith('win'):
+ lim = 2**15
+ while line:
+ to_print, line = line[:lim], line[lim:]
+ stream.write(to_print)
+ stream.write('\n')
+ else:
+ print >> stream, line
+ stream.flush()
+
+
+class MetaAnnotationPrinter(type):
+ def __new__(mcs, name, bases, dct):
+ annotation_map = dct.get('ANNOTATIONS')
+ if annotation_map:
+ for key, v in annotation_map.iteritems():
+ key = key.lower()
+ dct[key] = mcs.make_printer_fn(key, v)
+ return type.__new__(mcs, name, bases, dct)
+
+ @staticmethod
+ def make_printer_fn(name, n_args):
+ """Generates a method which emits an annotation to the log stream."""
+ upname = name.upper()
+ if upname in OLD_STYLE_ANNOTATIONS:
+ assert n_args >= 1
+ fmt = '@@@%s %%s%s@@@' % (upname, '@%s' * (n_args - 1))
+ else:
+ fmt = '@@@%s%s@@@' % (upname, '@%s' * n_args)
+
+ inner_args = n_args + 1 # self counts
+ infix = '1 argument' if inner_args == 1 else ('%d arguments' % inner_args)
+ err = '%s() takes %s (%%d given)' % (name, infix)
+
+ def printer(self, *args):
+ if len(args) != n_args:
+ raise TypeError(err % (len(args) + 1))
+ self.emit(fmt % args)
+ printer.__name__ = name
+ printer.__doc__ = """Emits an annotation for %s.""" % name.upper()
+
+ return printer
+
+
+class AnnotationPrinter(object):
+ """A derivable class which will inject annotation-printing methods into the
+ subclass.
+
+ A subclass should define a class variable ANNOTATIONS equal to a
+ dictionary of the form { '<ANNOTATION_NAME>': <# args> }. This class will
+ then inject methods whose names are the undercased version of your
+ annotation names, and which take the number of arguments specified in the
+ dictionary.
+
+ Example:
+ >>> my_annotations = { 'STEP_LOG_LINE': 2 }
+ >>> class MyObj(AnnotationPrinter):
+ ... ANNOTATIONS = my_annotations
+ ...
+ >>> o = MyObj()
+ >>> o.step_log_line('logname', 'here is a line to put in the log')
+ @@@STEP_LOG_LINE@logname@here is a line to put in the log@@@
+ >>> o.step_log_line()
+ Traceback (most recent call last):
+ File "<stdin>", line 1, in <module>
+ TypeError: step_log_line() takes exactly 3 arguments (1 given)
+ >>> o.setp_log_line.__doc__
+ "Emits an annotation for STEP_LOG_LINE."
+ >>>
+ """
+ __metaclass__ = MetaAnnotationPrinter
+
+ def __init__(self, stream, flush_before):
+ self.stream = stream
+ self.flush_before = flush_before
+
+ def emit(self, line):
+ emit(line, self.stream, self.flush_before)
+
+
+class StepCommands(AnnotationPrinter):
+ """Class holding step commands. Intended to be subclassed."""
+ ANNOTATIONS = STEP_ANNOTATIONS
+
+ def __init__(self, stream, flush_before):
+ super(StepCommands, self).__init__(stream, flush_before)
+ self.emitted_logs = set()
+
+ def write_log_lines(self, logname, lines, perf=None):
+ if logname in self.emitted_logs:
+ raise ValueError('Log %s has been emitted multiple times.' % logname)
+ self.emitted_logs.add(logname)
+
+ logname = logname.replace('/', '&#x2f;')
+
+ for line in lines:
+ self.step_log_line(logname, line)
+ if perf:
+ self.step_log_end_perf(logname, perf)
+ else:
+ self.step_log_end(logname)
+
+
+class StepControlCommands(AnnotationPrinter):
+ """Subclass holding step control commands. Intended to be subclassed.
+
+ This is subclassed out so callers in StructuredAnnotationStep can't call
+ step_started() or step_closed().
+ """
+ ANNOTATIONS = CONTROL_ANNOTATIONS
+
+
+class StructuredAnnotationStep(StepCommands, StepControlCommands):
+ """Helper class to provide context for a step."""
+
+ def __init__(self, annotation_stream, *args, **kwargs):
+ self.annotation_stream = annotation_stream
+ super(StructuredAnnotationStep, self).__init__(*args, **kwargs)
+ self.control = StepControlCommands(self.stream, self.flush_before)
+ self.emitted_logs = set()
+
+
+ def __enter__(self):
+ return self.step_started()
+
+ def step_started(self):
+ self.control.step_started()
+ return self
+
+ def __exit__(self, exc_type, exc_value, tb):
+ self.annotation_stream.step_cursor(self.annotation_stream.current_step)
+ #TODO(martinis) combine this and step_ended
+ if exc_type:
+ self.step_exception_occured(exc_type, exc_value, tb)
+
+ self.control.step_closed()
+ self.annotation_stream.current_step = ''
+ return not exc_type
+
+ def step_exception_occured(self, exc_type, exc_value, tb):
+ trace = traceback.format_exception(exc_type, exc_value, tb)
+ trace_lines = ''.join(trace).split('\n')
+ self.write_log_lines('exception', filter(None, trace_lines))
+ self.step_exception()
+
+ def step_ended(self):
+ self.annotation_stream.step_cursor(self.annotation_stream.current_step)
+ self.control.step_closed()
+ self.annotation_stream.current_step = ''
+
+ return True
+
+
+class StructuredAnnotationStream(AnnotationPrinter):
+ """Provides an interface to handle an annotated build.
+
+ StructuredAnnotationStream handles most of the step setup and closure calls
+ for you. All you have to do is execute your code within the steps and set any
+ failures or warnings that come up. You may optionally provide a list of steps
+ to seed before execution.
+
+ Usage:
+
+ stream = StructuredAnnotationStream()
+ with stream.step('compile') as s:
+ # do something
+ if error:
+ s.step_failure()
+ with stream.step('test') as s:
+ # do something
+ if warnings:
+ s.step_warnings()
+ """
+ ANNOTATIONS = STREAM_ANNOTATIONS
+
+ def __init__(self, stream=sys.stdout,
+ flush_before=sys.stderr,
+ seed_steps=None): # pylint: disable=W0613
+ super(StructuredAnnotationStream, self).__init__(stream=stream,
+ flush_before=flush_before)
+ self.current_step = ''
+
+ def step(self, name):
+ """Provide a context with which to execute a step."""
+ if self.current_step:
+ raise Exception('Can\'t start step %s while in step %s.' % (
+ name, self.current_step))
+
+ self.seed_step(name)
+ self.step_cursor(name)
+ self.current_step = name
+ return StructuredAnnotationStep(self, stream=self.stream,
+ flush_before=self.flush_before)
+
+
+def MatchAnnotation(line, callback_implementor):
+ """Call back into |callback_implementor| if line contains an annotation.
+
+ Args:
+ line (str) - The line to analyze
+ callback_implementor (object) - An object which contains methods
+ corresponding to all of the annotations in the |ALL_ANNOTATIONS|
+ dictionary. For example, it should contain a method STEP_SUMMARY_TEXT
+ taking a single argument.
+
+ Parsing method:
+ * if line doesn't match /^@@@.*@@@$/, return without calling back
+ * Look for the first '@' or ' '
+ """
+ if not (line.startswith('@@@') and line.endswith('@@@') and len(line) > 6):
+ return
+ line = line[3:-3]
+
+ # look until the first @ or ' '
+ idx = min((x for x in (line.find('@'), line.find(' '), len(line)) if x > 0))
+ cmd_text = line[:idx]
+ cmd = DEPRECATED_ALIASES.get(cmd_text, cmd_text)
+
+ field_count = ALL_ANNOTATIONS.get(cmd)
+ if field_count is None:
+ raise Exception('Unrecognized annotator command "%s"' % cmd_text)
+
+ if field_count:
+ if idx == len(line):
+ raise Exception('Annotator command "%s" expects %d args, got 0.'
+ % (cmd_text, field_count))
+
+ line = line[idx+1:]
+
+ args = line.split('@', field_count-1)
+ if len(args) != field_count:
+ raise Exception('Annotator command "%s" expects %d args, got %d.'
+ % (cmd_text, field_count, len(args)))
+ else:
+ line = line[len(cmd_text):]
+ if line:
+ raise Exception('Annotator command "%s" expects no args, got cruft "%s".'
+ % (cmd_text, line))
+ args = []
+
+ fn = getattr(callback_implementor, cmd, None)
+ if fn is None:
+ raise Exception('"%s" does not implement "%s"'
+ % (callback_implementor, cmd))
+
+ fn(*args)
+
+

Powered by Google App Engine
This is Rietveld 408576698