sbsv2/raptor/python/plugins/filter_html.py
changeset 641 8dd670a9f34f
parent 625 a1925fb7753a
child 674 37ee82a83d43
--- a/sbsv2/raptor/python/plugins/filter_html.py	Mon Sep 13 13:11:19 2010 +0800
+++ b/sbsv2/raptor/python/plugins/filter_html.py	Mon Sep 13 14:04:04 2010 +0100
@@ -1,19 +1,14 @@
-#
+
 # Copyright (c) 2010 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: 
-# Filter class for generating HTML summary pages
-#
+
+'''
+Filter class for generating HTML summary pages
+'''
 
 import os
 import re
@@ -21,6 +16,7 @@
 import sys
 import shutil
 import tempfile
+import time
 import filter_interface
 
 class HTML(filter_interface.FilterSAX):
@@ -49,6 +45,10 @@
 				self.regex = self.readregex(str(csv))
 				break
 		
+		# regexes for important "make" errors
+		self.noruletomake = re.compile("No rule to make target `(.+)', needed by `(.+)'")
+		
+		# all our lists are empty
 		self.elements = []
 		self.recipe_tag = None
 		self.error_tag = None
@@ -56,10 +56,15 @@
 		
 		self.components = {}
 		self.configurations = {}
+		self.missed_depends = {}
+		self.parse_start = {}
 		self.totals = Records()
+
+		self.progress_started = 0
+		self.progress_stopped = 0
 		
 		# create all the directories
-		for s in Records.SUBDIRS:
+		for s in Records.CLASSES:
 			dir = os.path.join(self.dirname, s)
 			if not os.path.isdir(dir):
 				try:
@@ -104,9 +109,10 @@
 	def startElement(self, name, attributes):
 		"call the start handler for this element if we defined one."
 		
-		self.generic_start(name)    # tracks element nesting
+		ns_name = name.replace(":", "_")
+		self.generic_start(ns_name)    # tracks element nesting
 		
-		function_name = "start_" + name
+		function_name = "start_" + ns_name
 		try:
 			HTML.__dict__[function_name](self, attributes)
 		except KeyError:
@@ -124,7 +130,7 @@
 	def endElement(self, name):
 		"call the end handler for this element if we defined one."
 		
-		function_name = "end_" + name
+		function_name = "end_" + name.replace(":", "_")
 		try:
 			HTML.__dict__[function_name](self)
 		except KeyError:
@@ -137,6 +143,12 @@
 		self.existencechecks()
 		self.dumptotals()
 		try:
+			if self.progress_started > 0:
+				t_from = time.asctime(time.localtime(self.progress_started))
+				t_to = time.asctime(time.localtime(self.progress_stopped))
+				self.index.write("<p>start: " + t_from + "\n")
+				self.index.write("<br>end&nbsp;&nbsp;: " + t_to + "\n")
+				
 			self.index.write("<p><table><tr><th></th>")
 			
 			for title in Records.TITLES:
@@ -239,6 +251,23 @@
 		except KeyError:
 			pass
 	
+	def char_buildlog(self, char):
+		'''process text in the top-level element.
+		
+		ideally all text will be inside <recipe> tags, but some will not.
+		"make" errors in particular appear inside the buildlog tag itself.'''
+		
+		text = char.strip()
+		if text:
+			match = self.noruletomake.search(text)
+			if match:
+				target = match.group(2)
+				depend = match.group(1)
+				if target in self.missed_depends:
+					self.missed_depends[target].append(depend)
+				else:
+					self.missed_depends[target] = [depend]
+		
 	def end_buildlog(self):
 		pass
 		
@@ -271,6 +300,46 @@
 				self.err("status element not inside a recipe element")
 		except KeyError:
 			pass
+	
+	def start_time(self, attributes):
+		try:
+			if self.recipe_tag:
+				self.recipe_tag.time = float(attributes['elapsed'])
+			else:
+				self.err("status element not inside a recipe element")
+		except KeyError:
+			pass
+	
+	def start_progress_start(self, attributes):
+		'''on progress:start note the parse starting timestamp.
+		
+		and keep track of the earliest timestamp of all as that shows
+		us when the sbs command was run.'''
+		try:
+			t = float(attributes['time'])
+			if self.progress_started == 0 or t < self.progress_started:
+				self.progress_started = t
+				
+			if attributes['task'] == 'parse':
+				self.parse_start[attributes['key']] = t
+		except KeyError:
+			pass
+		
+	def start_progress_end(self, attributes):
+		'''on progress:end add the elapsed parse time to the total time.
+		
+		also keep track of the latest timestamp of all as that shows
+		us when the sbs command finished.'''
+		try:
+			t = float(attributes['time'])
+			if t > self.progress_stopped:
+				self.progress_stopped = t
+				
+			if attributes['task'] == 'parse':
+				elapsed = t - self.parse_start[attributes['key']]
+				self.totals.inc(Records.TIME, elapsed)
+		except KeyError:
+			pass
 		
 	def start_error(self, attributes):
 		self.error_tag = TaggedText(attributes)
@@ -367,7 +436,34 @@
 			self.tmp.write(self.build_tag.strip() + "\n")
 		except:
 			return self.err("could not write to temporary file")
-				
+	
+	def start_clean(self, attributes):
+		try:
+			for attrib in ['bldinf', 'config']:
+				self.tmp.write("|")
+				if attrib in attributes:
+					self.tmp.write(attributes[attrib])
+			self.tmp.write("\n")
+		except:
+			return self.err("could not write to temporary file")
+	
+	def start_file(self, attributes):
+		'''opening file tag.
+		
+		in the temporary file we need to mark the "clean" targets with a
+		leading ">" character so they can be treated differently from 
+		the "releasable" targets'''
+		self.file_tag = ">"
+		
+	def char_file(self, char):
+		self.file_tag += char
+		
+	def end_file(self):
+		try:
+			self.tmp.write(self.file_tag.strip() + "\n")
+		except:
+			return self.err("could not write to temporary file")
+						
 	# even if we ignore an element we need to mark its coming and going
 	# so that we know which element any character data belongs to.
 	
@@ -436,7 +532,7 @@
 			# we don't want to show successes, just count them
 			return
 		
-		linkname = os.path.join(Records.SUBDIRS[type], "overall.html")
+		linkname = os.path.join(Records.CLASSES[type], "overall.html")
 		filename = os.path.join(self.dirname, linkname)
 		title = Records.TITLES[type] + " for all configurations"
 		try:
@@ -448,17 +544,18 @@
 		except:
 			return self.err("cannot create file '%s'" % filename)
 		
-		self.totals.set(type, 'filename', filename)
-		self.totals.set(type, 'linkname', linkname)
+		self.totals.set_filename(type, filename)
+		self.totals.set_linkname(type, linkname)
 	
 	def appendoverallfile(self, type, taggedtext):
-		self.totals.inc(type, 'N')   # one more and counting
+		self.totals.inc(type)   # one more and counting
+		self.totals.inc(Records.TIME, taggedtext.time)
 		
 		if type == Records.OK:
 			# we don't want to show successes, just count them
 			return
 		
-		filename = self.totals.get(type, 'filename')
+		filename = self.totals.get_filename(type)
 		try:
 			file = open(filename, "a")
 			file.write("<p>component: %s " % taggedtext.bldinf)
@@ -473,7 +570,7 @@
 			# we don't want to show successes, just count them
 			return
 		
-		linkname = os.path.join(Records.SUBDIRS[type], "cfg_" + configuration + ".html")
+		linkname = os.path.join(Records.CLASSES[type], "cfg_" + configuration + ".html")
 		filename = os.path.join(self.dirname, linkname)
 		title = Records.TITLES[type] + " for configuration " + configuration
 		try:
@@ -485,17 +582,18 @@
 		except:
 			return self.err("cannot create file '%s'" % filename)
 		
-		self.configurations[configuration].set(type, 'filename', filename)
-		self.configurations[configuration].set(type, 'linkname', linkname)
+		self.configurations[configuration].set_filename(type, filename)
+		self.configurations[configuration].set_linkname(type, linkname)
 	
 	def appendconfigurationfile(self, configuration, type, taggedtext):
-		self.configurations[configuration].inc(type, 'N')   # one more and counting
+		self.configurations[configuration].inc(type)   # one more and counting
+		self.configurations[configuration].inc(Records.TIME, taggedtext.time)
 		
 		if type == Records.OK:
 			# we don't want to show successes, just count them
 			return
 		
-		filename = self.configurations[configuration].get(type, 'filename')
+		filename = self.configurations[configuration].get_filename(type)
 		try:
 			file = open(filename, "a")
 			file.write("<p>component: %s\n" % taggedtext.bldinf)
@@ -509,7 +607,7 @@
 			# we don't want to show successes, just count them
 			return
 		
-		linkname = os.path.join(Records.SUBDIRS[type], "bld_" + re.sub("[/:]","_",component) + ".html")
+		linkname = os.path.join(Records.CLASSES[type], "bld_" + re.sub("[/:]","_",component) + ".html")
 		filename = os.path.join(self.dirname, linkname)
 		title = Records.TITLES[type] + " for component " + component
 		try:
@@ -521,17 +619,18 @@
 		except:
 			return self.err("cannot create file '%s'" % filename)
 		
-		self.components[component].set(type, 'filename', filename)
-		self.components[component].set(type, 'linkname', linkname)
+		self.components[component].set_filename(type, filename)
+		self.components[component].set_linkname(type, linkname)
 	
 	def appendcomponentfile(self, component, type, taggedtext):
-		self.components[component].inc(type, 'N')   # one more and counting
+		self.components[component].inc(type)   # one more and counting
+		self.components[component].inc(Records.TIME, taggedtext.time)
 		
 		if type == Records.OK:
 			# we don't want to show successes, just count them
 			return
 		
-		filename = self.components[component].get(type, 'filename')
+		filename = self.components[component].get_filename(type)
 		try:
 			file = open(filename, "a")
 			file.write("<p>config: %s\n" % taggedtext.config)
@@ -556,12 +655,37 @@
 					missing_tag = TaggedText(attribs)
 				else:
 					filename = line.strip()
-					if not filename in missed and not os.path.isfile(filename):
-						missing_tag.text = filename
-						self.record(missing_tag, Records.MISSING)
-						missed.add(filename)
+					if filename.startswith(">"):
+						# a clean target, so we don't care if it exists
+						# but we care if it has a missing dependency
+						filename = filename[1:]
+					else:
+						# a releasable target so it must exist
+						if not filename in missed and not os.path.isfile(filename):
+							missing_tag.text = filename
+							self.record(missing_tag, Records.MISSING)
+							missed.add(filename)
+						
+					if filename in self.missed_depends:
+						missing_tag.text = filename + \
+						"\n\nrequired the following files which could not be found,\n\n"
+						for dep in self.missed_depends[filename]:
+							missing_tag.text += dep + "\n"
+						self.record(missing_tag, Records.ERROR)
+						del self.missed_depends[filename]
 					
 			self.tmp.close()	# this also deletes the temporary file
+			
+			# any missed dependencies left over are not attributable to any
+			# specific component but should still be reported
+			missing_tag = TaggedText({})
+			for filename in self.missed_depends:
+				missing_tag.text = filename + \
+				"\n\nrequired the following files which could not be found,\n\n"
+				for dep in self.missed_depends[filename]:
+					missing_tag.text += dep + "\n"
+				self.record(missing_tag, Records.ERROR)
+						
 		except Exception,e:
 			return self.err("could not close temporary file " + str(e))
 	
@@ -593,8 +717,10 @@
 				try:
 					type = None
 					
-					if row[0] == "CRITICAL" or row[0] == "ERROR":
+					if row[0] == "ERROR":
 						type = Records.ERROR
+					elif row[0] == "CRITICAL":
+						type = Records.CRITICAL
 					elif row[0] == "WARNING":
 						type = Records.WARNING
 					elif row[0] == "REMARK":
@@ -615,52 +741,65 @@
 			return []
 		
 		return regexlist
+
+class CountItem(object):
+	def __init__(self):
+		self.N = 0
+		self.filename = None
+		self.linkname = None
+
+	def num_str(self):
+		return str(self.N)
 	
+class TimeItem(CountItem):
+	def num_str(self):
+		return time.strftime("%H:%M:%S", time.gmtime(self.N + 0.5))
+		
 class Records(object):
 	"a group of related records e.g. errors, warnings and remarks."
 	
 	# the different types of record we want to group together
-	OK      = 0
-	ERROR   = 1
-	WARNING = 2
-	REMARK  = 3
-	MISSING = 4
+	TIME     = 0
+	OK       = 1
+	ERROR    = 2
+	CRITICAL = 3
+	WARNING  = 4
+	REMARK   = 5
+	MISSING  = 6
 	
-	SUBDIRS = [ "ok", "error", "warning", "remark", "missing" ]
-	TITLES = [ "OK", "Errors", "Warnings", "Remarks", "Missing files" ]
+	CLASSES = [ "time", "ok", "error", "critical", "warning", "remark", "missing" ]
+	TITLES = [ "CPU Time", "OK", "Errors", "Criticals", "Warnings", "Remarks", "Missing files" ]
 	
 	def __init__(self):
-		self.data = [ {'N':0}, {'N':0}, {'N':0}, {'N':0}, {'N':0} ]
-	
-	def get(self, index, item):
-		try:
-			return self.data[index][item]
-		except KeyError:
-			return None
+		self.data = [ TimeItem(), CountItem(), CountItem(), CountItem(), CountItem(), CountItem(), CountItem() ]
+		
+	def get_filename(self, index):
+		return self.data[index].filename
 		
-	def inc(self, index, item):
-		self.data[index][item] += 1
-	
+	def inc(self, index, increment=1):
+		self.data[index].N += increment
+
 	def isempty(self, index):
-		return (self.data[index]['N'] == 0)
+		return (self.data[index].N == 0)
 		
-	def set(self, index, item, value):
-		self.data[index][item] = value
+	def set_filename(self, index, value):
+		self.data[index].filename = value
 	
+	def set_linkname(self, index, value):
+		self.data[index].linkname = value
+		
 	def tablerow(self, name):
 		row = '<tr><td class="name">%s</td>' % name
-
+		
 		for i,datum in enumerate(self.data):
-			number = datum['N']
-			if number == 0:
+			if datum.N == 0:
 				row += '<td class="zero">0</td>'
 			else:
-				row += '<td class="' + Records.SUBDIRS[i] + '">'
-				try:
-					link = datum['linkname']
-					row += '<a href="%s">%d</a></td>' % (link,number)
-				except KeyError:
-					row += '%d</td>' % number
+				row += '<td class="' + Records.CLASSES[i] + '">'
+				if datum.linkname:
+					row += '<a href="%s">%s</a></td>' % (datum.linkname,datum.num_str())
+				else:
+					row += '%s</td>' % datum.num_str()
 							
 		row += "</tr>"
 		return row
@@ -668,12 +807,11 @@
 	def textdump(self):
 		text = ""
 		for i,datum in enumerate(self.data):
-			number = datum['N']
-			if number == 0:
+			if datum.N == 0:
 				style = "zero"
 			else:
-				style = Records.SUBDIRS[i]
-			text += str(i) + ',' + style + "," + str(number) + "\n"
+				style = Records.CLASSES[i]
+			text += str(i) + ',' + style + "," + str(datum.N) + "\n"
 		return text
 				
 class TaggedText(object):
@@ -687,5 +825,6 @@
 					self.__dict__[attrib] = value
 
 		self.text = ""
+		self.time = 0.0
 		
 # the end
\ No newline at end of file