|
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 |