sbsv2/raptor/python/plugins/filter_terminal.py
changeset 0 044383f39525
child 3 e1eecf4d390d
child 590 360bd6b35136
equal deleted inserted replaced
-1:000000000000 0:044383f39525
       
     1 #
       
     2 # Copyright (c) 2008-2009 Nokia Corporation and/or its subsidiary(-ies).
       
     3 # All rights reserved.
       
     4 # This component and the accompanying materials are made available
       
     5 # under the terms of the License "Eclipse Public License v1.0"
       
     6 # which accompanies this distribution, and is available
       
     7 # at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
     8 #
       
     9 # Initial Contributors:
       
    10 # Nokia Corporation - initial contribution.
       
    11 #
       
    12 # Contributors:
       
    13 #
       
    14 # Description: 
       
    15 # Filter class for filtering XML logs and generating reports
       
    16 # Prints errors and warnings to stdout
       
    17 #
       
    18 
       
    19 import sys
       
    20 import raptor
       
    21 import filter_interface
       
    22 import generic_path
       
    23 import os
       
    24 import os.path
       
    25 import re
       
    26 
       
    27 class Recipe(object):
       
    28 	"""State machine that parses a recipe
       
    29 	"""
       
    30 
       
    31 	suppress = []
       
    32 	warningRE = re.compile("^.*((Warning:)|(MAKEDEF WARNING:)) .*$", re.DOTALL | re.M | re.I)
       
    33 	infoRE = None
       
    34 	name = [ "default" ]
       
    35 	recipes = []
       
    36 
       
    37 	def __init__(self, text):
       
    38 		self.suppress = self.__class__.suppress
       
    39 		self.text = text
       
    40 		self.warningRE = Recipe.warningRE
       
    41 	
       
    42 	def warnings(self):
       
    43 		return self.warningRE.findall(self.text)
       
    44 
       
    45 	def info(self):
       
    46 		if self.infoRE:
       
    47 			return self.infoRE.findall(self.text)
       
    48 		else:
       
    49 			return []
       
    50 
       
    51 	@classmethod			
       
    52 	def factory(cls, name, text):
       
    53 		for r in Recipe.recipes:
       
    54 			if name in r.name:
       
    55 				return r(text)
       
    56 		return Recipe(text)
       
    57 	
       
    58 
       
    59 class MwLinkerRecipe(Recipe):
       
    60 	suppress = [ 
       
    61 		re.compile(
       
    62 r"^mwldsym2: warning: Cannot locate library \"MSL_All_Static_MSE_Symbian\" specified in #pragma comment\(lib,...\)$"
       
    63 r"[\n\r]*mwldsym2: warning: referenced from.*$"
       
    64 r"[\n\r]*mwldsym2: warning: Option 'Use default libraries' is enabled but linker used.*$"
       
    65 r"[\n\r]*mwldsym2: warning: runtime library from MW\[...\]LibraryFiles \(msl_all_static_mse_symbian_d.lib\);$"
       
    66 r"[\n\r]*mwldsym2: warning: this indicates a potential settings/libraries mismatch.*$"
       
    67 		, re.M)
       
    68 		, re.compile(
       
    69 r"^mwldsym2.exe: warning: Multiply defined symbol: ___get_MSL_init_count in.*$"
       
    70 r"[\n\r]*mwldsym2.exe: warning: files uc_cwhelp.obj \(.*\), startup.win32.c.obj \(msl_all_static_mse_symbian_d.lib\),.*$"
       
    71 r"[\n\r]*mwldsym2.exe: warning: keeping definition in startup.win32.c.obj.*$"
       
    72 		, re.M )
       
    73 		, re.compile(
       
    74 r"^mwldsym2.exe: warning: Option 'Use default libraries' is enabled but linker used.*$"
       
    75 r"[\n\r]*mwldsym2.exe: warning: runtime library from MW\[...\]LibraryFiles \(msl_all_static_mse_symbian_d.lib\);.*$"
       
    76 r"[\n\r]*mwldsym2.exe: warning: this indicates a potential settings/libraries mismatch.*$"
       
    77 	, re.M)
       
    78 	]
       
    79 	name = [ "win32stagetwolink", "win32simplelink" ]
       
    80 
       
    81 	def warnings(self):
       
    82 		edited = self.text
       
    83 		for s in MwLinkerRecipe.suppress:
       
    84 			edited = s.sub("", edited)
       
    85 		return Recipe.warningRE.findall(edited)
       
    86 
       
    87 Recipe.recipes.append(MwLinkerRecipe)
       
    88 
       
    89 
       
    90 class FreezeRecipe(Recipe):
       
    91 	name = [ "freeze" ]
       
    92 	warningRE = re.compile("^(WARNING:) .*$", re.DOTALL | re.M | re.I)
       
    93 	infoRE = re.compile("^(EFREEZE:) .*$", re.DOTALL | re.M | re.I)
       
    94 
       
    95 	def __init__(self, text):
       
    96 		Recipe.__init__(self, text)
       
    97 		self.warningRE = FreezeRecipe.warningRE
       
    98 		self.infoRE = FreezeRecipe.infoRE
       
    99 
       
   100 Recipe.recipes.append(FreezeRecipe)
       
   101 
       
   102 
       
   103 
       
   104 class FilterTerminal(filter_interface.Filter):
       
   105 
       
   106 	attribute_re = re.compile("([a-z][a-z0-9]*)='([^']*)'",re.I)
       
   107 	maxdots = 40 # if one prints dots then don't print masses
       
   108 	recipelinelimit = 200 # don't scan ultra-long recipes in case we run out of memory
       
   109 
       
   110 	# recipes that we think most users are interested in
       
   111 	# and the mapping that we will use to output them as
       
   112 	docare = {
       
   113 		"asmcompile" : "asmcompile" ,
       
   114 		"compile" : "compile" ,
       
   115 		"postlink" : "target",
       
   116 		"resourcecompile" : "resource",
       
   117 		"genstringtable" : "strtable",
       
   118 		"tem" : "tem",
       
   119 		"bitmapcompile" : "bitmap",
       
   120 		"bitmapcopy" : "bitmapcopy",
       
   121 		"win32compile2object" : "compile",
       
   122 		"win32stagetwolink" : "target",
       
   123 		"win32simplelink" : "target",
       
   124 		"tools2install" : "target",
       
   125 		"compile2object" : "compile",
       
   126 		"msvctoolsinstall" : "target",
       
   127 		"msvctoolscompile" : "compile",
       
   128 		"freeze" : "freeze",
       
   129 		"win32archive" : "target"
       
   130 	}
       
   131 
       
   132 	# Determine the width of the largest mapped recipe name
       
   133 	recipewidth = 0
       
   134 	for i in docare:
       
   135 		l = len(docare[i])
       
   136 		if l > recipewidth:
       
   137 			recipewidth = l # justification for printing out recipes.
       
   138 	recipewidth+=1
       
   139 
       
   140 	def __init__(self):
       
   141 		self.analyseonly = False
       
   142 		self.quiet = False
       
   143 		# defaults can use EPOCROOT
       
   144 		if "EPOCROOT" in os.environ:
       
   145 			self.epocroot = str(generic_path.Path(os.environ["EPOCROOT"]))
       
   146 		else:
       
   147 			self.epocroot = str(generic_path.Path('/'))
       
   148 		self.current_recipe_logged = False
       
   149 		self.cleaned = 0  # cleaned files
       
   150 		self.dotcount = 0 # progress dots printed so far
       
   151 		# list of strings to catch make errors (must be lowercase)
       
   152 		self.make_error_expr = set([
       
   153 				"error:",
       
   154 				": ***",
       
   155 				"make: interrupt/exception caught (code =",
       
   156 				"make.exe: interrupt/exception caught (code ="
       
   157 				])
       
   158 		# list of strings to catch make warnings (must be lowercase)
       
   159 		self.make_warning_expr = ["warning:"]
       
   160 
       
   161 		# list of strings to catch recipe warnings (must be lowercase)
       
   162 		self.recipe_warning_expr = ["warning:"]
       
   163 
       
   164 	def isMakeWarning(self, text):
       
   165                 """A simple test for warnings.
       
   166                 Can be extended do to more comprehensive checking."""
       
   167 		# generic warnings checked
       
   168 		# array of make_warning_expr holds all the possible values
       
   169 		for warn in self.make_warning_expr:
       
   170 			if warn in text.lower():
       
   171 				return True
       
   172 	
       
   173 		return False
       
   174 
       
   175 
       
   176 	def isMakeError(self, text):
       
   177 		"""A simple test for errors.	
       
   178 		Can be extended to do more comprehensive checking."""
       
   179 
       
   180 		# make, emake and pvmgmake spit out things like
       
   181 		# make: *** No rule to make target X, needed by Y. Stop.
       
   182 		#
       
   183 		# array of make_error_expr holds all the possible values
       
   184 		for err in self.make_error_expr:
       
   185 			if err in text.lower():
       
   186 				return True
       
   187 		
       
   188 		return False
       
   189 
       
   190 
       
   191 	def open(self, raptor_instance):
       
   192 		"""Set output to stdout for the various I/O methods to write to."""
       
   193 		self.raptor = raptor_instance
       
   194 
       
   195 		# Be totally silent?
       
   196 		if self.raptor.logFileName is None:
       
   197 			self.analyseonly = True
       
   198 
       
   199 		# Only print errors and warnings?
       
   200 		if self.raptor.quiet:
       
   201 			self.quiet = True
       
   202 		
       
   203 		# keep count of errors and warnings
       
   204 		self.err_count = 0
       
   205 		self.warn_count = 0
       
   206 		self.suppressed_warn_count = 0
       
   207 		self.inBody = False
       
   208 		self.inRecipe = False
       
   209 		return True
       
   210 		
       
   211 	def write(self, text):
       
   212 		"""Write errors and warnings to stdout"""
       
   213 		
       
   214 		if text.startswith("<error"):
       
   215 			start = text.find(">")
       
   216 			end = text.rfind("<")
       
   217 			self.err_count += 1
       
   218 			if not self.analyseonly:
       
   219 				sys.stderr.write(str(raptor.name) + ": error: %s\n" \
       
   220 						% text[(start + 1):end])
       
   221 		elif text.startswith("<warning"):
       
   222 			start = text.find(">")
       
   223 			end = text.rfind("<")
       
   224 			self.warn_count += 1
       
   225 			if not self.analyseonly:
       
   226 				sys.stdout.write(str(raptor.name) + ": warning: %s\n" \
       
   227 					% text[(start + 1):end])
       
   228 		elif text.startswith("<status "):
       
   229 			# detect the status report from a recipe
       
   230 			if text.find('failed') != -1:
       
   231 				self.failed = True
       
   232 			else:
       
   233 				self.failed = False
       
   234 			return
       
   235 		elif text.startswith("<recipe "):
       
   236 			# detect the start of a recipe
       
   237 			if self.inRecipe:
       
   238 				sys.stdout.flush()
       
   239 				sys.stderr.write(self.formatError("Opening recipe tag found " \
       
   240 						+ "before closing recipe tag for previous recipe:\n" \
       
   241 						+ "Discarding previous recipe (Possible logfile " \
       
   242 						+ "corruption)"))
       
   243 				sys.stderr.flush()
       
   244 			self.inRecipe = True
       
   245 			self.current_recipe_logged = False
       
   246 			m = FilterTerminal.attribute_re.findall(text)
       
   247 			self.recipe_dict = dict ()
       
   248 			for i in m:
       
   249 				self.recipe_dict[i[0]] = i[1]
       
   250 
       
   251 			# Decide what to tell the user about this recipe
       
   252 			# The target file or the source file?  
       
   253 			name = None
       
   254 			if 'source' in self.recipe_dict:
       
   255 				name = self.recipe_dict['source']
       
   256 
       
   257 			name_to_user = ""
       
   258 			# Make source files relative to the current directory if they are 
       
   259 		 	# not generated files in epocroot.  Also make sure path is in 
       
   260 			# the appropriate format for the user's shell.
       
   261 			if name and (name.find("epoc32") == -1 or name.endswith('.UID.CPP')):
       
   262 				for i in name.rsplit():
       
   263 					name_to_user += " " + generic_path.Path(i).From(generic_path.CurrentDir()).GetShellPath()
       
   264 			else:
       
   265 				# using the target.  Shorten it if it's in epocroot by just chopping off
       
   266 				# epocroot
       
   267 				name_to_user = self.recipe_dict['target']
       
   268 				if name_to_user.find(self.epocroot) != -1:
       
   269 					name_to_user = name_to_user.replace(self.epocroot,"")
       
   270 					if name_to_user.startswith('/') or name_to_user.startswith('\\'):
       
   271 						name_to_user = name_to_user[1:]
       
   272 				name_to_user = generic_path.Path(name_to_user).GetShellPath()	
       
   273 			self.recipe_dict['name_to_user'] = name_to_user
       
   274 			self.recipe_dict['mappedname'] = self.recipe_dict['name'] 
       
   275 
       
   276 			# Status message to indicate that we are building
       
   277 			recipename = self.recipe_dict['name']
       
   278 			if recipename in FilterTerminal.docare:
       
   279 				self.recipe_dict['mappedname'] = FilterTerminal.docare[recipename]
       
   280 				self.logit_if()
       
   281 
       
   282 			# This variable holds all recipe information
       
   283 			self.failed = False # Recipe status
       
   284 			self.recipeBody = []
       
   285 			return		
       
   286 		elif text.startswith("</recipe>"):
       
   287 			# detect the end of a recipe
       
   288 			if not self.inRecipe:
       
   289 				sys.stdout.flush()
       
   290 				sys.stderr.write(self.formatError("Closing recipe tag found " \
       
   291 						+ "before opening recipe tag:\nUnable to print " \
       
   292 						+ "recipe data (Possible logfile corruption)"))
       
   293 				sys.stderr.flush()
       
   294 			else:
       
   295 				self.inRecipe = False
       
   296 				
       
   297 				if self.failed == True:
       
   298 					if not self.analyseonly:
       
   299 						sys.stderr.write("\n FAILED %s for %s: %s\n" % \
       
   300 								(self.recipe_dict['name'],
       
   301 								self.recipe_dict['config'],
       
   302 								self.recipe_dict['name_to_user']))
       
   303 	
       
   304 						mmppath = generic_path.Path(self.recipe_dict['mmp']).From(generic_path.CurrentDir()).GetShellPath()
       
   305 						sys.stderr.write("  mmp: %s\n" % mmppath)
       
   306 						for L in self.recipeBody:
       
   307 							if not L.startswith('+'):
       
   308 								sys.stdout.write("   %s\n" % L.rstrip())
       
   309 					self.err_count += 1
       
   310 				else:
       
   311 					r = Recipe.factory(self.recipe_dict['name'], "".join(self.recipeBody))
       
   312 					warnings = r.warnings()
       
   313 					info = r.info()
       
   314 					if len(warnings) > 0:
       
   315 						if not self.analyseonly:
       
   316 							for L in self.recipeBody:
       
   317 								if not L.startswith('+'):
       
   318 									sys.stdout.write("   %s\n" % L.rstrip())
       
   319 						self.warn_count += len(warnings)
       
   320 	
       
   321 				self.recipeBody = []
       
   322 			return
       
   323 		elif not self.inRecipe and self.isMakeError(text):
       
   324 			# these two statements pick up errors coming from make
       
   325 			self.err_count += 1
       
   326 			sys.stderr.write("    %s\n" % text.rstrip())
       
   327 			return
       
   328 		elif not self.inRecipe and self.isMakeWarning(text):
       
   329 			self.warn_count += 1
       
   330 			sys.stdout.write("    %s\n" % text.rstrip())
       
   331 			return
       
   332 		elif text.startswith("<![CDATA["):
       
   333                 	# save CDATA body during a recipe
       
   334 			if self.inRecipe:
       
   335 				self.inBody = True
       
   336 		elif text.startswith("]]>"):
       
   337 			if self.inRecipe:
       
   338 				self.inBody = False
       
   339 		elif text.startswith("<info>Copied"):
       
   340 			if not self.analyseonly and not self.quiet:
       
   341 				start = text.find(" to ") + 4
       
   342 				end = text.find("</info>",start)
       
   343 				short_target = text[start:end]
       
   344 				if short_target.startswith(self.epocroot):
       
   345 					short_target = short_target.replace(self.epocroot,"")[1:]
       
   346 				short_target = generic_path.Path(short_target).GetShellPath()
       
   347 				sys.stdout.write(" %s: %s\n" % ("export".ljust(FilterTerminal.recipewidth), short_target))
       
   348 			return
       
   349 		elif text.find("<rm files") != -1 or text.find("<rmdir ") != -1:
       
   350 			# search for cleaning output but only if we 
       
   351 			# are not in some recipe (that would be pointless)
       
   352 			if not self.analyseonly and not self.quiet:
       
   353 				if  self.cleaned == 0:
       
   354 					sys.stdout.write("\ncleaning ")
       
   355 					self.cleaned+=1
       
   356 				elif self.dotcount < FilterTerminal.maxdots:
       
   357 					if self.cleaned % 5 == 0:
       
   358 						self.dotcount+=1
       
   359 						sys.stdout.write(".")
       
   360 					self.cleaned+=1
       
   361 			
       
   362 				return
       
   363 		elif self.inBody:
       
   364 			# We are parsing the output from a recipe
       
   365 			# we have to keep the output until we find out
       
   366 			# if the recipe failed. But not all of it if it turns
       
   367 			# out to be very long
       
   368 			if len(self.recipeBody) < FilterTerminal.recipelinelimit:
       
   369 				self.recipeBody.append(text)
       
   370 
       
   371 	def logit(self):
       
   372 		""" log a message """
       
   373 		info = self.recipe_dict['mappedname'].ljust(FilterTerminal.recipewidth)
       
   374 		config = self.recipe_dict['config']
       
   375 		name = self.recipe_dict['name_to_user'].lstrip()
       
   376 		# If its a multifile config, we print source files one below the other in a single
       
   377 		# 'compile:' statement
       
   378 		if config.endswith('multifile'):
       
   379 			files =  self.recipe_dict['name_to_user'].split()
       
   380 			name = ""
       
   381 			for i in files:
       
   382 				if i == files[0]:
       
   383 					name +=  i
       
   384 				else:
       
   385 					name +=  '\n\t      ' + i
       
   386 		sys.stdout.write(" %s: %s  \t[%s]\n" % (info, name, config))
       
   387 
       
   388 	def logit_if(self):
       
   389 		""" Tell the user about the recipe that we are processing """
       
   390 		if not self.analyseonly and not self.quiet:
       
   391 			if self.inRecipe and not self.current_recipe_logged:
       
   392 				self.logit()
       
   393 				self.current_recipe_logged = True
       
   394 	
       
   395 	def summary(self):
       
   396 		"""Errors and warnings summary"""
       
   397 		
       
   398 		if self.raptor.skipAll or self.analyseonly:
       
   399 			return
       
   400 
       
   401 
       
   402 		if self.cleaned != 0:
       
   403 			sys.stdout.write("\n\n")
       
   404 
       
   405 		if self.warn_count > 0 or self.err_count > 0:
       
   406 			sys.stdout.write("\n%s : warnings: %s\n" % (raptor.name,
       
   407 					self.warn_count))
       
   408 			sys.stdout.write("%s : errors: %s\n" % (raptor.name,
       
   409 					self.err_count))
       
   410 		else:
       
   411 			sys.stdout.write("\nno warnings or errors\n")
       
   412 
       
   413 		sys.stdout.write("\nRun time %d seconds\n" % self.raptor.runtime);
       
   414 		sys.stdout.write("\n")
       
   415 		return True
       
   416 	
       
   417 	def close(self):
       
   418 		"""Tell raptor that there were errors."""
       
   419 		if self.err_count > 0:
       
   420 			return False
       
   421 		return True
       
   422