diff -r 000000000000 -r 044383f39525 sbsv2/raptor/python/filter_utils.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sbsv2/raptor/python/filter_utils.py Tue Oct 27 16:36:35 2009 +0000 @@ -0,0 +1,249 @@ +# +# Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +# All rights reserved. +# This component and the accompanying materials are made available +# under the terms of the License "Eclipse Public License v1.0" +# which accompanies this distribution, and is available +# at the URL "http://www.eclipse.org/legal/epl-v10.html". +# +# Initial Contributors: +# Nokia Corporation - initial contribution. +# +# Contributors: +# +# Description: +# Classes, methods and regex available for use in log filters +# + + +import re + + +# General log structure +logTag = re.compile('\?xml|buildlog|info|warning|error|recipe|whatlog|build|export|archive|member|bitmap|resource|stringtable|bmconvcmdfile)[>| ]') +logHeader = re.compile('.+)[\'|\"] xmlns=[\'|\"](?P.+)[\'|\"] xmlns:xsi=[\'|\"](?P.+)[\'|\"] xsi:schemaLocation=[\'|\"](?P.+)[\'|\"]>') +clean = re.compile('.*.+)[\'|\"] />') +exports = re.compile('(Copied|Unzipped (?P\d+) files from) (?P.+) to (?P.+)') + +# Tool errors and warnings +mwError = re.compile('(.+:\d+:(?! (note|warning):) .+|mw(ld|cc)sym2(.exe)?:(?! (note|warning):) .+ \'.+\' .+)') +mwWarning = re.compile('.+:\d+: warning: .+|mw(ld|cc)sym2(.exe)?: warning: .+') + + +class AutoFlushedStream(file): + """ Wrapper for STDOUT/STDERR streams to ensure that a flush is performed + after write methods. + Use to avoid buffering when log output in real time is required.""" + + def __init__(self, aStream): + self.__stream = aStream + + def write(self, aText): + self.__stream.write(aText) + self.__stream.flush() + + def writelines(self, aTextList): + self.__stream.writelines(aTextList) + self.__stream.flush() + + +class RecipeFactory(object): + "Factory class to ease creation of appropriately specialised Recipe objects." + + def newRecipe(self, aLine=None, aCustomIgnore=None): + """ Creates objects of base type Recipe depending on the name + of the recipe being processed.""" + + name = "" + header = None + if aLine: + header = Recipe.header.match(aLine) + if header: + name = header.group("name") + + if name.startswith("win32"): + return Win32Recipe(aLine, aCustomIgnore) + else: + return Recipe(aLine, aCustomIgnore) + + +class Recipe(object): + """ Recipe base class. + Provides a means to get hold of recipe content in a generic way. + Includes a basic understanding of errors and warnings - sub-classes can + override output, error and warning methods to specialise.""" + + # Flags to normalise client access, mapping directly to regex groups + name = "name" + target = "target" + host = "host" + layer = "layer" + component = "component" + bldinf = "bldinf" + mmp = "mmp" + config = "config" + platform = "platform" + phase = "phase" + source = "source" + start = "start" + elapsed = "elapsed" + exit = "exit" + code = "code" + attempts = "attempts" + + # Basic errors/warnings + error = re.compile('Error: ') + warning = re.compile('Warning: ') + + # Recipe metadata + header = re.compile('.+)[\'|\"]\s+target=[\'|\"](?P.+)[\'|\"]\s+host=[\'|\"](?P.+)[\'|\"]\s+layer=[\'|\"](?P.*)[\'|\"]\s+component=[\'|\"](?P.*)[\'|\"]\s+bldinf=[\'|\"](?P.+)[\'|\"]\s+mmp=[\'|\"](?P.*)[\'|\"]\s+config=[\'|\"](?P.+)[\'|\"]\s+platform=[\'|\"](?P.*)[\'|\"]\s+phase=[\'|\"](?P.+)[\'|\"]\s+source=[\'|\"](?P.*)[\'|\"]\s*>') + call = re.compile('^\+ (?P.+)$') + status = re.compile('\(ok|failed|retry))[\'|\"](\s+code=[\'|\"](?P\d+)[\'|\"])?\s+attempt=[\'|\"](?P\d+)[\'|\"]\s*\/>') + ignore = re.compile('\d+\.\d+)[\'|\"]\s+elapsed=[\'|\"](?P\d+.\d+)[\'|\"]\s*/>$') + footer = re.compile('$') + + + def __init__(self, aLine=None, aCustomIgnore=None): + """ + @param aLine Optional first line of a recipe (typically the recipe header) + @param aCustomIgnore Optional compiled regular expression object listing additional + lines to be ignored in this recipe's output. + """ + self.__customIgnore = aCustomIgnore + + self.__detail = { + Recipe.name :"", + Recipe.target :"", + Recipe.host :"", + Recipe.layer :"", + Recipe.component:"", + Recipe.bldinf :"", + Recipe.mmp :"", + Recipe.config :"", + Recipe.platform :"", + Recipe.phase :"", + Recipe.source :"", + Recipe.start :"", + Recipe.elapsed :0.0, + Recipe.exit :"", + Recipe.code :0, + Recipe.attempts :0 + } + + self.__calls = [] + self.__lines = [] + self.__complete = False + + if aLine: + self.addLine(aLine) + + def isComplete(self): + """Signifies that the recipe footer has been reached, the + recipe is complete and so is in a fit state to be queried.""" + return self.__complete + + def __storeDetail(self, aMatchObject): + for key in aMatchObject.groupdict().keys(): + value = aMatchObject.group(key) + if value: + if (key in [Recipe.code,Recipe.attempts]): + value = int(value) + elif key == Recipe.elapsed: + value = float(value) + self.__detail[key] = value + + def addLine(self, aLine): + """Add a log line to an existing recipe object, processing anything + that can be examined at this point in time directly.""" + if Recipe.ignore.match(aLine) or (self.__customIgnore and self.__customIgnore.match(aLine)): + return + + header = Recipe.header.match(aLine) + if header: + self.__storeDetail(header) + return + + call = Recipe.call.match(aLine) + if call: + self.__calls.append(call.group("call")) + return + + time = Recipe.time.match(aLine) + if time: + self.__storeDetail(time) + return + + status = Recipe.status.match(aLine) + if status: + self.__storeDetail(status) + return + + if Recipe.footer.match(aLine): + self.__complete = True + return + + self.__lines.append(aLine) + + def getDetail(self, aItem): + """Retrieve attribute detail from recipe tags. + Class data flags provide known items e.g. getDetail(Recipe.source)""" + if self.__detail.has_key(aItem): + return self.__detail[aItem] + + def getCalls(self): + "Return a list of all '+' prefixed tool calls from this recipe." + return self.__calls + + def isError(self, aLine): + """Convenience matcher for basic errors. + Override in sub-classes to specialise.""" + return True if Recipe.error.match(aLine) else False + + def isWarning(self, aLine): + """Convenience matcher for basic warnings. + Override in sub-classes to specialise.""" + return True if Recipe.warning.match(aLine) else False + + def getOutput(self): + """"Return a list of all output that isn't an error or a warning. + Override in sub-classes to specialise.""" + output = [] + for line in self.__lines: + if not self.isError(line) and not self.isWarning(line): + output.append(line) + return output + + def getErrors(self): + """"Return a list of all output identified as an error. + Override in sub-classes to specialise.""" + errors = [] + for line in self.__lines: + if self.isError(line): + errors.append(line) + return errors + + def getWarnings(self): + """"Return a list of all output identified as a warning. + Override in sub-classes to specialise.""" + warnings = [] + for line in self.__lines: + if self.isWarning(line): + warnings.append(line) + return warnings + + def isSuccess(self): + "Convenience method to get overall recipe status." + return True if self.getDetail(Recipe.exit) == "ok" else False + + +class Win32Recipe(Recipe): + "Win32 tailored recipe class." + def isError(self, aLine): + return True if mwError.match(aLine) else False + + def isWarning(self, aLine): + return True if mwWarning.match(aLine) else False + + +