Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(21)

Side by Side Diff: scripts/common/annotator.py

Issue 12386016: Initial implementation of annotator library. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: More python 2.6 changes. Created 7 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | scripts/common/unittests/annotator_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright (c) 2013 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """Contains generating and parsing systems of the Chromium Buildbot Annotator.
7
8 When executed as a script, this reads step name / command pairs from a file and
9 executes those lines while annotating the output. The input is json:
10
11 [{"name": "step_name", "cmd": ["command", "arg1", "arg2"]},
12 {"name": "step_name2", "cmd": ["command2", "arg1"]}]
13
14 """
15
16 import json
17 import optparse
18 import re
19 import sys
20 import traceback
21
22 from common import chromium_utils
23
24
25 class StepCommands(object):
26 """Class holding step commands. Intended to be subclassed."""
27 def __init__(self, stream):
28 self.stream = stream
29
30 def emit(self, line):
31 print >> self.stream, line
32
33 def step_warnings(self):
34 self.emit('@@@STEP_WARNINGS@@@')
35
36 def step_failure(self):
37 self.emit('@@@STEP_FAILURE@@@')
38
39 def step_exception(self):
40 self.emit('@@@STEP_EXCEPTION@@@')
41
42 def step_clear(self):
43 self.emit('@@@STEP_CLEAR@@@')
44
45 def step_summary_clear(self):
46 self.emit('@@@STEP_SUMMARY_CLEAR@@@')
47
48 def step_text(self, text):
49 self.emit('@@@STEP_TEXT@%s@@@' % text)
50
51 def step_summary_text(self, text):
52 self.emit('@@@STEP_SUMMARY_TEXT@%s@@@' % text)
53
54 def step_log_line(self, logname, line):
55 self.emit('@@@STEP_LOG_LINE@%s@%s@@@' % (logname, line.rstrip('\n')))
56
57 def step_log_end(self, logname):
58 self.emit('@@@STEP_LOG_END@%s@@@' % logname)
59
60 def step_log_end_perf(self, logname, perf):
61 self.emit('@@@STEP_LOG_END_PERF@%s@%s@@@' % (logname, perf))
62
63 def write_log_lines(self, logname, lines, perf=None):
64 if logname in self.emitted_logs:
65 raise ValueError('Log %s has been emitted multiple times.' % logname)
66 self.emitted_logs.add(logname)
67
68 for line in lines:
69 self.step_log_line(logname, line)
70 if perf:
71 self.step_log_end_perf(logname, perf)
72 else:
73 self.step_log_end(logname)
74
75
76 class StepControlCommands(object):
77 """Subclass holding step control commands. Intended to be subclassed.
78
79 This is subclassed out so callers in StructuredAnnotationStep can't call
80 step_started() or step_closed().
81 """
82 def __init__(self, stream):
83 self.stream = stream
84
85 def emit(self, line):
86 print >> self.stream, line
87
88 def step_started(self):
89 self.emit('@@@STEP_STARTED@@@')
90
91 def step_closed(self):
92 self.emit('@@@STEP_CLOSED@@@')
93
94
95 class StructuredAnnotationStep(StepCommands):
96 """Helper class to provide context for a step."""
97
98 def __init__(self, annotation_stream, *args, **kwargs):
99 self.annotation_stream = annotation_stream
100 super(StructuredAnnotationStep, self).__init__(*args, **kwargs)
101 self.control = StepControlCommands(self.stream)
102 self.emitted_logs = set()
103
104 def emit(self, line):
105 print >> self.stream, line
106
107 def __enter__(self):
108 self.control.step_started()
109 return self
110
111 def __exit__(self, exc_type, exc_value, tb):
112 if exc_type:
113 trace = traceback.format_exception(exc_type, exc_value, tb)
114 trace_lines = ''.join(trace).split('\n')
115 self.write_log_lines('exception', filter(None, trace_lines))
116 self.step_exception()
117
118 self.control.step_closed()
119 self.annotation_stream.current_step = ''
120 return not exc_type
121
122 class AdvancedAnnotationStep(StepCommands, StepControlCommands):
123 """Holds additional step functions for finer step control.
124
125 Most users will want to use StructuredAnnotationSteps generated from a
126 StructuredAnnotationStream as these handle state automatically.
127 """
128
129 def __init__(self, *args, **kwargs):
130 super(AdvancedAnnotationStep, self).__init__(*args, **kwargs)
131
132
133 class AdvancedAnnotationStream(object):
134 """Holds individual annotation generating functions for streams.
135
136 Most callers should use StructuredAnnotationStream to simplify coding and
137 avoid errors. For the rare cases where StructuredAnnotationStream is
138 insufficient (parallel step execution), the indidividual functions are exposed
139 here.
140 """
141
142 def __init__(self, stream=sys.stdout):
143 self.stream = stream
144
145 def emit(self, line):
146 print >> self.stream, line
147
148 def seed_step(self, step):
149 self.emit('@@@SEED_STEP %s@@@' % step)
150
151 def step_cursor(self, step):
152 self.emit('@@@STEP_CURSOR %s@@@' % step)
153
154 def halt_on_failure(self):
155 self.emit('@@@HALT_ON_FAILURE@@@')
156
157 def honor_zero_return_code(self):
158 self.emit('@@@HONOR_ZERO_RETURN_CODE@@@')
159
160
161 class StructuredAnnotationStream(AdvancedAnnotationStream):
162 """Provides an interface to handle an annotated build.
163
164 StructuredAnnotationStream handles most of the step setup and closure calls
165 for you. All you have to do is execute your code within the steps and set any
166 failures or warnings that come up. You may optionally provide a list of steps
167 to seed before execution.
168
169 Usage:
170
171 stream = StructuredAnnotationStream()
172 with stream.step('compile') as s:
173 # do something
174 if error:
175 s.step_failure()
176 with stream.step('test') as s:
177 # do something
178 if warnings:
179 s.step_warnings()
180 """
181
182 def __init__(self, seed_steps=None, stream=sys.stdout):
183 super(StructuredAnnotationStream, self).__init__(stream=stream)
184 seed_steps = seed_steps or []
185 self.seed_steps = seed_steps
186
187 for step in seed_steps:
188 self.seed_step(step)
189
190 self.current_step = ''
191
192 def step(self, name):
193 """Provide a context with which to execute a step."""
194 if self.current_step:
195 raise Exception('Can\'t start step %s while in step %s.' % (
196 name, self.current_step))
197 if name in self.seed_steps:
198 # Seek ahead linearly, skipping steps that weren't emitted in order.
199 # chromium_step.AnnotatedCommands uses the last in case of duplicated
200 # step names, so we do the same here.
201 idx = len(self.seed_steps) - self.seed_steps[::-1].index(name)
202 self.seed_steps = self.seed_steps[idx:]
203 else:
204 self.seed_step(name)
205
206 self.step_cursor(name)
207 self.current_step = name
208 return StructuredAnnotationStep(self, stream=self.stream)
209
210
211 class Match:
212 """Holds annotator line parsing functions."""
213
214 def __init__(self):
215 raise Exception('Don\'t instantiate the Match class!')
216
217 @staticmethod
218 def _parse_line(regex, line):
219 m = re.match(regex, line)
220 if m:
221 return list(m.groups())
222 else:
223 return []
224
225 @staticmethod
226 def log_line(line):
227 return Match._parse_line('^@@@STEP_LOG_LINE@(.*)@(.*)@@@', line)
228
229 @staticmethod
230 def log_end(line):
231 return Match._parse_line('^@@@STEP_LOG_END@(.*)@@@', line)
232
233 @staticmethod
234 def log_end_perf(line):
235 return Match._parse_line('^@@@STEP_LOG_END_PERF@(.*)@(.*)@@@', line)
236
237 @staticmethod
238 def step_link(line):
239 m = Match._parse_line('^@@@STEP_LINK@(.*)@(.*)@@@', line)
240 if not m:
241 return Match._parse_line('^@@@link@(.*)@(.*)@@@', line) # Deprecated.
242 else:
243 return m
244
245 @staticmethod
246 def step_started(line):
247 return line.startswith('@@@STEP_STARTED@@@')
248
249 @staticmethod
250 def step_closed(line):
251 return line.startswith('@@@STEP_CLOSED@@@')
252
253 @staticmethod
254 def step_warnings(line):
255 return (line.startswith('@@@STEP_WARNINGS@@@') or
256 line.startswith('@@@BUILD_WARNINGS@@@')) # Deprecated.
257
258 @staticmethod
259 def step_failure(line):
260 return (line.startswith('@@@STEP_FAILURE@@@') or
261 line.startswith('@@@BUILD_FAILED@@@')) # Deprecated.
262
263 @staticmethod
264 def step_exception(line):
265 return (line.startswith('@@@STEP_EXCEPTION@@@') or
266 line.startswith('@@@BUILD_EXCEPTION@@@')) # Deprecated.
267
268 @staticmethod
269 def halt_on_failure(line):
270 return line.startswith('@@@HALT_ON_FAILURE@@@')
271
272 @staticmethod
273 def honor_zero_return_code(line):
274 return line.startswith('@@@HONOR_ZERO_RETURN_CODE@@@')
275
276 @staticmethod
277 def step_clear(line):
278 return line.startswith('@@@STEP_CLEAR@@@')
279
280 @staticmethod
281 def step_summary_clear(line):
282 return line.startswith('@@@STEP_SUMMARY_CLEAR@@@')
283
284 @staticmethod
285 def step_text(line):
286 return Match._parse_line('^@@@STEP_TEXT@(.*)@@@', line)
287
288 @staticmethod
289 def step_summary_text(line):
290 return Match._parse_line('^@@@STEP_SUMMARY_TEXT@(.*)@@@', line)
291
292 @staticmethod
293 def seed_step(line):
294 return Match._parse_line('^@@@SEED_STEP (.*)@@@', line)
295
296 @staticmethod
297 def step_cursor(line):
298 return Match._parse_line('^@@@STEP_CURSOR (.*)@@@', line)
299
300 @staticmethod
301 def build_step(line):
302 return Match._parse_line('^@@@BUILD_STEP (.*)@@@', line)
303
304
305 def main():
306 usage = '%s <command list file or - for stdin>' % sys.argv[0]
307 parser = optparse.OptionParser(usage=usage)
308 _, args = parser.parse_args()
309 if not args:
310 parser.error('Must specify an input filename.')
311 if len(args) > 1:
312 parser.error('Too many arguments specified.')
313
314 steps = []
315
316 if args[0] == '-':
317 steps.extend(json.load(sys.stdin))
318 else:
319 with open(args[0], 'rb') as f:
320 steps.extend(json.load(f))
321
322 for step in steps:
323 if ('cmd' not in step or
324 'name' not in step):
325 print 'step \'%s\' is invalid' % json.dumps(step)
326 return 1
327
328 # Make sure these steps always run, even if there is a build failure.
329 always_run = {}
330 for step in steps:
331 if step.get('always_run'):
332 always_run[step['name']] = step
333
334 stepnames = [s['name'] for s in steps]
335
336 stream = StructuredAnnotationStream(seed_steps=stepnames)
337 build_failure = False
338 for step in steps:
339 if step['name'] in always_run:
340 del always_run[step['name']]
341 try:
342 with stream.step(step['name']) as s:
343 ret = chromium_utils.RunCommand(step['cmd'])
344 if ret != 0:
345 s.step_failure()
346 build_failure = True
347 break
348 except OSError:
349 # File wasn't found, error has been already reported to stream.
350 build_failure = True
351 break
352
353 for step_name in always_run:
354 with stream.step(step_name) as s:
355 ret = chromium_utils.RunCommand(always_run[step_name]['cmd'])
356 if ret != 0:
357 s.step_failure()
358 build_failure = True
359
360 if build_failure:
361 return 1
362 return 0
363
364
365 if __name__ == '__main__':
366 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | scripts/common/unittests/annotator_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698