author | Jon Chatten |
Fri, 05 Feb 2010 18:31:38 +0000 | |
branch | fix |
changeset 225 | d401dbd3a410 |
parent 29 | ee00c00df073 |
permissions | -rw-r--r-- |
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.""" |
|
29
ee00c00df073
Catchup to Perforce WIP with timing, python24
timothy.murphy@nokia.com
parents:
3
diff
changeset
|
201 |
if Recipe.error.match(aLine): |
ee00c00df073
Catchup to Perforce WIP with timing, python24
timothy.murphy@nokia.com
parents:
3
diff
changeset
|
202 |
return True |
ee00c00df073
Catchup to Perforce WIP with timing, python24
timothy.murphy@nokia.com
parents:
3
diff
changeset
|
203 |
return False |
3 | 204 |
|
205 |
def isWarning(self, aLine): |
|
206 |
"""Convenience matcher for basic warnings. |
|
207 |
Override in sub-classes to specialise.""" |
|
29
ee00c00df073
Catchup to Perforce WIP with timing, python24
timothy.murphy@nokia.com
parents:
3
diff
changeset
|
208 |
if Recipe.warning.match(aLine): |
ee00c00df073
Catchup to Perforce WIP with timing, python24
timothy.murphy@nokia.com
parents:
3
diff
changeset
|
209 |
return True |
ee00c00df073
Catchup to Perforce WIP with timing, python24
timothy.murphy@nokia.com
parents:
3
diff
changeset
|
210 |
return False |
3 | 211 |
|
212 |
def getOutput(self): |
|
213 |
""""Return a list of all output that isn't an error or a warning. |
|
214 |
Override in sub-classes to specialise.""" |
|
215 |
output = [] |
|
216 |
for line in self.__lines: |
|
217 |
if not self.isError(line) and not self.isWarning(line): |
|
218 |
output.append(line) |
|
219 |
return output |
|
220 |
||
221 |
def getErrors(self): |
|
222 |
""""Return a list of all output identified as an error. |
|
223 |
Override in sub-classes to specialise.""" |
|
224 |
errors = [] |
|
225 |
for line in self.__lines: |
|
226 |
if self.isError(line): |
|
227 |
errors.append(line) |
|
228 |
return errors |
|
229 |
||
230 |
def getWarnings(self): |
|
231 |
""""Return a list of all output identified as a warning. |
|
232 |
Override in sub-classes to specialise.""" |
|
233 |
warnings = [] |
|
234 |
for line in self.__lines: |
|
235 |
if self.isWarning(line): |
|
236 |
warnings.append(line) |
|
237 |
return warnings |
|
238 |
||
239 |
def isSuccess(self): |
|
240 |
"Convenience method to get overall recipe status." |
|
29
ee00c00df073
Catchup to Perforce WIP with timing, python24
timothy.murphy@nokia.com
parents:
3
diff
changeset
|
241 |
return (self.getDetail(Recipe.exit) == "ok") |
3 | 242 |
|
243 |
||
244 |
class Win32Recipe(Recipe): |
|
245 |
"Win32 tailored recipe class." |
|
246 |
def isError(self, aLine): |
|
29
ee00c00df073
Catchup to Perforce WIP with timing, python24
timothy.murphy@nokia.com
parents:
3
diff
changeset
|
247 |
if mwError.match(aLine): |
ee00c00df073
Catchup to Perforce WIP with timing, python24
timothy.murphy@nokia.com
parents:
3
diff
changeset
|
248 |
return True |
ee00c00df073
Catchup to Perforce WIP with timing, python24
timothy.murphy@nokia.com
parents:
3
diff
changeset
|
249 |
return False |
3 | 250 |
|
251 |
def isWarning(self, aLine): |
|
29
ee00c00df073
Catchup to Perforce WIP with timing, python24
timothy.murphy@nokia.com
parents:
3
diff
changeset
|
252 |
if mwWarning.match(aLine): |
ee00c00df073
Catchup to Perforce WIP with timing, python24
timothy.murphy@nokia.com
parents:
3
diff
changeset
|
253 |
return True |
ee00c00df073
Catchup to Perforce WIP with timing, python24
timothy.murphy@nokia.com
parents:
3
diff
changeset
|
254 |
return False |