3
|
1 |
#
|
|
2 |
# Copyright (c) 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 |
# Classes, methods and regex available for use in log filters
|
|
16 |
#
|
|
17 |
|
|
18 |
|
|
19 |
import re
|
|
20 |
|
|
21 |
|
|
22 |
# General log structure
|
|
23 |
logTag = re.compile('</?(?P<name>\?xml|buildlog|info|warning|error|recipe|whatlog|build|export|archive|member|bitmap|resource|stringtable|bmconvcmdfile)[>| ]')
|
|
24 |
logHeader = re.compile('<buildlog sbs_version=[\'|\"](?P<version>.+)[\'|\"] xmlns=[\'|\"](?P<xmlns>.+)[\'|\"] xmlns:xsi=[\'|\"](?P<xsdi>.+)[\'|\"] xsi:schemaLocation=[\'|\"](?P<schemaLocation>.+)[\'|\"]>')
|
|
25 |
clean = re.compile('.*<rm(dir)? (files|dirs)=[\'|\"](?P<removals>.+)[\'|\"] />')
|
|
26 |
exports = re.compile('<info>(Copied|Unzipped (?P<unpacked>\d+) files from) (?P<source>.+) to (?P<destination>.+)</info>')
|
|
27 |
|
|
28 |
# Tool errors and warnings
|
|
29 |
mwError = re.compile('(.+:\d+:(?! (note|warning):) .+|mw(ld|cc)sym2(.exe)?:(?! (note|warning):) .+ \'.+\' .+)')
|
|
30 |
mwWarning = re.compile('.+:\d+: warning: .+|mw(ld|cc)sym2(.exe)?: warning: .+')
|
|
31 |
|
|
32 |
|
|
33 |
class AutoFlushedStream(file):
|
|
34 |
""" Wrapper for STDOUT/STDERR streams to ensure that a flush is performed
|
|
35 |
after write methods.
|
|
36 |
Use to avoid buffering when log output in real time is required."""
|
|
37 |
|
|
38 |
def __init__(self, aStream):
|
|
39 |
self.__stream = aStream
|
|
40 |
|
|
41 |
def write(self, aText):
|
|
42 |
self.__stream.write(aText)
|
|
43 |
self.__stream.flush()
|
|
44 |
|
|
45 |
def writelines(self, aTextList):
|
|
46 |
self.__stream.writelines(aTextList)
|
|
47 |
self.__stream.flush()
|
|
48 |
|
|
49 |
|
|
50 |
class RecipeFactory(object):
|
|
51 |
"Factory class to ease creation of appropriately specialised Recipe objects."
|
|
52 |
|
|
53 |
def newRecipe(self, aLine=None, aCustomIgnore=None):
|
|
54 |
""" Creates objects of base type Recipe depending on the name
|
|
55 |
of the recipe being processed."""
|
|
56 |
|
|
57 |
name = ""
|
|
58 |
header = None
|
|
59 |
if aLine:
|
|
60 |
header = Recipe.header.match(aLine)
|
|
61 |
if header:
|
|
62 |
name = header.group("name")
|
|
63 |
|
|
64 |
if name.startswith("win32"):
|
|
65 |
return Win32Recipe(aLine, aCustomIgnore)
|
|
66 |
else:
|
|
67 |
return Recipe(aLine, aCustomIgnore)
|
|
68 |
|
|
69 |
|
|
70 |
class Recipe(object):
|
|
71 |
""" Recipe base class.
|
|
72 |
Provides a means to get hold of recipe content in a generic way.
|
|
73 |
Includes a basic understanding of errors and warnings - sub-classes can
|
|
74 |
override output, error and warning methods to specialise."""
|
|
75 |
|
|
76 |
# Flags to normalise client access, mapping directly to regex groups
|
|
77 |
name = "name"
|
|
78 |
target = "target"
|
|
79 |
host = "host"
|
|
80 |
layer = "layer"
|
|
81 |
component = "component"
|
|
82 |
bldinf = "bldinf"
|
|
83 |
mmp = "mmp"
|
|
84 |
config = "config"
|
|
85 |
platform = "platform"
|
|
86 |
phase = "phase"
|
|
87 |
source = "source"
|
|
88 |
start = "start"
|
|
89 |
elapsed = "elapsed"
|
|
90 |
exit = "exit"
|
|
91 |
code = "code"
|
|
92 |
attempts = "attempts"
|
|
93 |
|
|
94 |
# Basic errors/warnings
|
|
95 |
error = re.compile('Error: ')
|
|
96 |
warning = re.compile('Warning: ')
|
|
97 |
|
|
98 |
# Recipe metadata
|
|
99 |
header = re.compile('<recipe\s+name=[\'|\"](?P<name>.+)[\'|\"]\s+target=[\'|\"](?P<target>.+)[\'|\"]\s+host=[\'|\"](?P<host>.+)[\'|\"]\s+layer=[\'|\"](?P<layer>.*)[\'|\"]\s+component=[\'|\"](?P<component>.*)[\'|\"]\s+bldinf=[\'|\"](?P<bldinf>.+)[\'|\"]\s+mmp=[\'|\"](?P<mmp>.*)[\'|\"]\s+config=[\'|\"](?P<config>.+)[\'|\"]\s+platform=[\'|\"](?P<platform>.*)[\'|\"]\s+phase=[\'|\"](?P<phase>.+)[\'|\"]\s+source=[\'|\"](?P<source>.*)[\'|\"]\s*>')
|
|
100 |
call = re.compile('^\+ (?P<call>.+)$')
|
|
101 |
status = re.compile('\<status\s+exit=[\'|\"](?P<exit>(ok|failed|retry))[\'|\"](\s+code=[\'|\"](?P<code>\d+)[\'|\"])?\s+attempt=[\'|\"](?P<attempts>\d+)[\'|\"]\s*\/>')
|
|
102 |
ignore = re.compile('<!\[CDATA\[')
|
|
103 |
time = re.compile(']]><time\s+start=[\'|\"](?P<start>\d+\.\d+)[\'|\"]\s+elapsed=[\'|\"](?P<elapsed>\d+.\d+)[\'|\"]\s*/>$')
|
|
104 |
footer = re.compile('</recipe>$')
|
|
105 |
|
|
106 |
|
|
107 |
def __init__(self, aLine=None, aCustomIgnore=None):
|
|
108 |
"""
|
|
109 |
@param aLine Optional first line of a recipe (typically the recipe header)
|
|
110 |
@param aCustomIgnore Optional compiled regular expression object listing additional
|
|
111 |
lines to be ignored in this recipe's output.
|
|
112 |
"""
|
|
113 |
self.__customIgnore = aCustomIgnore
|
|
114 |
|
|
115 |
self.__detail = {
|
|
116 |
Recipe.name :"",
|
|
117 |
Recipe.target :"",
|
|
118 |
Recipe.host :"",
|
|
119 |
Recipe.layer :"",
|
|
120 |
Recipe.component:"",
|
|
121 |
Recipe.bldinf :"",
|
|
122 |
Recipe.mmp :"",
|
|
123 |
Recipe.config :"",
|
|
124 |
Recipe.platform :"",
|
|
125 |
Recipe.phase :"",
|
|
126 |
Recipe.source :"",
|
|
127 |
Recipe.start :"",
|
|
128 |
Recipe.elapsed :0.0,
|
|
129 |
Recipe.exit :"",
|
|
130 |
Recipe.code :0,
|
|
131 |
Recipe.attempts :0
|
|
132 |
}
|
|
133 |
|
|
134 |
self.__calls = []
|
|
135 |
self.__lines = []
|
|
136 |
self.__complete = False
|
|
137 |
|
|
138 |
if aLine:
|
|
139 |
self.addLine(aLine)
|
|
140 |
|
|
141 |
def isComplete(self):
|
|
142 |
"""Signifies that the recipe footer has been reached, the
|
|
143 |
recipe is complete and so is in a fit state to be queried."""
|
|
144 |
return self.__complete
|
|
145 |
|
|
146 |
def __storeDetail(self, aMatchObject):
|
|
147 |
for key in aMatchObject.groupdict().keys():
|
|
148 |
value = aMatchObject.group(key)
|
|
149 |
if value:
|
|
150 |
if (key in [Recipe.code,Recipe.attempts]):
|
|
151 |
value = int(value)
|
|
152 |
elif key == Recipe.elapsed:
|
|
153 |
value = float(value)
|
|
154 |
self.__detail[key] = value
|
|
155 |
|
|
156 |
def addLine(self, aLine):
|
|
157 |
"""Add a log line to an existing recipe object, processing anything
|
|
158 |
that can be examined at this point in time directly."""
|
|
159 |
if Recipe.ignore.match(aLine) or (self.__customIgnore and self.__customIgnore.match(aLine)):
|
|
160 |
return
|
|
161 |
|
|
162 |
header = Recipe.header.match(aLine)
|
|
163 |
if header:
|
|
164 |
self.__storeDetail(header)
|
|
165 |
return
|
|
166 |
|
|
167 |
call = Recipe.call.match(aLine)
|
|
168 |
if call:
|
|
169 |
self.__calls.append(call.group("call"))
|
|
170 |
return
|
|
171 |
|
|
172 |
time = Recipe.time.match(aLine)
|
|
173 |
if time:
|
|
174 |
self.__storeDetail(time)
|
|
175 |
return
|
|
176 |
|
|
177 |
status = Recipe.status.match(aLine)
|
|
178 |
if status:
|
|
179 |
self.__storeDetail(status)
|
|
180 |
return
|
|
181 |
|
|
182 |
if Recipe.footer.match(aLine):
|
|
183 |
self.__complete = True
|
|
184 |
return
|
|
185 |
|
|
186 |
self.__lines.append(aLine)
|
|
187 |
|
|
188 |
def getDetail(self, aItem):
|
|
189 |
"""Retrieve attribute detail from recipe tags.
|
|
190 |
Class data flags provide known items e.g. getDetail(Recipe.source)"""
|
|
191 |
if self.__detail.has_key(aItem):
|
|
192 |
return self.__detail[aItem]
|
|
193 |
|
|
194 |
def getCalls(self):
|
|
195 |
"Return a list of all '+' prefixed tool calls from this recipe."
|
|
196 |
return self.__calls
|
|
197 |
|
|
198 |
def isError(self, aLine):
|
|
199 |
"""Convenience matcher for basic errors.
|
|
200 |
Override in sub-classes to specialise."""
|
|
201 |
return True if Recipe.error.match(aLine) else False
|
|
202 |
|
|
203 |
def isWarning(self, aLine):
|
|
204 |
"""Convenience matcher for basic warnings.
|
|
205 |
Override in sub-classes to specialise."""
|
|
206 |
return True if Recipe.warning.match(aLine) else False
|
|
207 |
|
|
208 |
def getOutput(self):
|
|
209 |
""""Return a list of all output that isn't an error or a warning.
|
|
210 |
Override in sub-classes to specialise."""
|
|
211 |
output = []
|
|
212 |
for line in self.__lines:
|
|
213 |
if not self.isError(line) and not self.isWarning(line):
|
|
214 |
output.append(line)
|
|
215 |
return output
|
|
216 |
|
|
217 |
def getErrors(self):
|
|
218 |
""""Return a list of all output identified as an error.
|
|
219 |
Override in sub-classes to specialise."""
|
|
220 |
errors = []
|
|
221 |
for line in self.__lines:
|
|
222 |
if self.isError(line):
|
|
223 |
errors.append(line)
|
|
224 |
return errors
|
|
225 |
|
|
226 |
def getWarnings(self):
|
|
227 |
""""Return a list of all output identified as a warning.
|
|
228 |
Override in sub-classes to specialise."""
|
|
229 |
warnings = []
|
|
230 |
for line in self.__lines:
|
|
231 |
if self.isWarning(line):
|
|
232 |
warnings.append(line)
|
|
233 |
return warnings
|
|
234 |
|
|
235 |
def isSuccess(self):
|
|
236 |
"Convenience method to get overall recipe status."
|
|
237 |
return True if self.getDetail(Recipe.exit) == "ok" else False
|
|
238 |
|
|
239 |
|
|
240 |
class Win32Recipe(Recipe):
|
|
241 |
"Win32 tailored recipe class."
|
|
242 |
def isError(self, aLine):
|
|
243 |
return True if mwError.match(aLine) else False
|
|
244 |
|
|
245 |
def isWarning(self, aLine):
|
|
246 |
return True if mwWarning.match(aLine) else False
|
|
247 |
|
|
248 |
|
|
249 |
|