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

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: Only use json, add always_run property. 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/master/chromium_step.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 lines of 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 AdvancedAnnotationStep(object):
26 """Holds individual annotation generating functions for steps.
27
28 Most users will want to use StructuredAnnotationSteps generated from a
29 StructuredAnnotationStream as these handle state automatically.
30 """
31
32 def __init__(self, stream=sys.stdout):
33 self.stream = stream
34
35 def emit(self, line):
36 print >> self.stream, line
37
38 def step_started(self):
39 self.emit('@@@STEP_STARTED@@@')
40
41 def step_closed(self):
42 self.emit('@@@STEP_CLOSED@@@')
43
44 def step_warnings(self):
45 self.emit('@@@STEP_WARNINGS@@@')
46
47 def step_failure(self):
48 self.emit('@@@STEP_FAILURE@@@')
49
50 def step_exception(self):
51 self.emit('@@@STEP_EXCEPTION@@@')
52
53 def step_clear(self):
54 self.emit('@@@STEP_CLEAR@@@')
55
56 def step_summary_clear(self):
57 self.emit('@@@STEP_SUMMARY_CLEAR@@@')
58
59 def step_text(self, text):
60 self.emit('@@@STEP_TEXT@%s@@@' % text)
61
62 def step_summary_text(self, text):
63 self.emit('@@@STEP_SUMMARY_TEXT@%s@@@' % text)
64
65 def write_log_line(self, logname, line):
agable 2013/02/28 21:32:08 Method names should continue to match @@@ANNOTATIO
66 self.emit('@@@STEP_LOG_LINE@%s@%s@@@' % (logname, line.rstrip('\n')))
67
68 def log_end(self, logname):
69 self.emit('@@@STEP_LOG_END@%s@@@' % logname)
70
71 def log_end_perf(self, logname, perf):
72 self.emit('@@@STEP_LOG_END_PERF@%s@%s@@@' % (logname, perf))
73
74 def write_log_lines(self, logname, lines, perf=None):
75 for line in lines:
76 self.write_log_line(logname, line)
77 if perf:
78 self.log_end_perf(logname, perf)
79 else:
80 self.log_end(logname)
81
82
83 class AdvancedAnnotationStream(object):
84 """Holds individual annotation generating functions for streams.
85
86 Most callers should use StructuredAnnotationStream to simplify coding and
87 avoid errors. For the rare cases where StructuredAnnotationStream is
88 insufficient (parallel step execution), the indidividual functions are exposed
89 here.
90
91 """
92
93 def __init__(self, stream=sys.stdout):
94 self.stream = stream
95
96 def emit(self, line):
97 print >> self.stream, line
98
99 def seed_step(self, step):
100 self.emit('@@@SEED_STEP %s@@@' % step)
101
102 def step_cursor(self, step):
103 self.emit('@@@STEP_CURSOR %s@@@' % step)
104
105 def build_step(self, line, step):
106 self.emit('@@@BUILD_STEP %s@@@' % step) # deprecated, use SEED_STEP
agable 2013/02/28 21:32:08 Newline between methods.
agable 2013/02/28 21:32:08 Why even give this to users? BUILD_STEP is depreca
107 def halt_on_failure(self):
108 self.emit('@@@HALT_ON_FAILURE@@@')
109
110 def honor_zero_return_code(self):
111 self.emit('@@@HONOR_ZERO_RETURN_CODE@@@')
112
113
114 class StructuredAnnotationStep(AdvancedAnnotationStream):
agable 2013/02/28 21:32:08 This should inherit from AdvancedAnnotationStep, n
Mike Stip (use stip instead) 2013/03/01 22:49:36 thanks for catching that
115 """Helper class to provide context for a step."""
116
117 def __init__(self, annotation_stream, *args, **kwargs):
118 self.annotation_stream = annotation_stream
119 super(StructuredAnnotationStep, self).__init__(*args, **kwargs)
120
121 def __enter__(self):
122 self.step_started()
123 return self
124
125 def __exit__(self, exc_type, exc_value, tb):
126 if exc_type:
127 trace = traceback.format_exception(exc_type, exc_value, tb)
128 unflattened = [l.split('\n') for l in trace]
129 flattened = [item for sublist in unflattened for item in sublist]
130 self.write_log_lines('exception', filter(None, flattened))
131 self.step_exception()
132
133 self.step_closed()
134 self.annotation_stream.current_step = ''
135 return not exc_type
136
137
138 class StructuredAnnotationStream(AdvancedAnnotationStream):
139 """Provides an interface to handle an annotated build.
140
141 StructuredAnnotationStream handles most of the step setup and closure calls
142 for you. All you have to do is execute your code within the steps and set any
143 failures or warnings that come up. You may optionally provide a list of steps
144 to seed before execution.
145
146 Usage:
147
148 stream = StructuredAnnotationStream()
149 with stream.step('compile') as s:
150 # do something
151 if error:
152 s.step_failure()
153 with stream.step('test') as s:
154 # do something
155 if warnings:
156 s.step_warnings()
157 """
158
159 def __init__(self, seed_steps=None, stream=sys.stdout):
160 super(StructuredAnnotationStream, self).__init__(stream=stream)
161 seed_steps = seed_steps or []
162 self.seed_steps = seed_steps
163
164 for step in seed_steps:
165 self.seed_step(step)
166
167 self.current_step = ''
168
169 def step(self, name):
170 """Provide a context with which to execute a step."""
171 if self.current_step:
172 raise Exception('Can\'t start step %s while in step %s.' % (
173 name, self.current_step))
174 if name in self.seed_steps:
175 # Seek ahead linearly, skipping steps that weren't emitted in order.
176 # chromium_step.AnnotatedCommands uses the last in case of duplicated
177 # step names, so we do the same here.
178 idx = len(self.seed_steps) - self.seed_steps[::-1].index(name)
179 self.seed_steps = self.seed_steps[idx:]
180 else:
181 self.seed_step(name)
182
183 self.step_cursor(name)
184 self.current_step = name
185 return StructuredAnnotationStep(self, stream=self.stream)
186
187
188 class Match:
189 """Holds annotator line parsing functions."""
190
191 def __init__(self):
192 raise Exception('Don\'t instantiate the Match class!')
193
194 @staticmethod
195 def _parse_line(regex, line):
196 m = re.match(regex, line)
197 if m:
198 return list(m.groups())
199 else:
200 return []
201
202 @staticmethod
203 def log_line(line):
204 return Match._parse_line('^@@@STEP_LOG_LINE@(.*)@(.*)@@@', line)
205
206 @staticmethod
207 def log_end(line):
208 return Match._parse_line('^@@@STEP_LOG_END@(.*)@@@', line)
209
210 @staticmethod
211 def log_end_perf(line):
212 return Match._parse_line('^@@@STEP_LOG_END_PERF@(.*)@(.*)@@@', line)
213
214 @staticmethod
215 def step_link(line):
216 m = Match._parse_line('^@@@STEP_LINK@(.*)@(.*)@@@', line)
217 if not m:
218 return Match._parse_line('^@@@link@(.*)@(.*)@@@', line) # deprecated
219 else:
220 return m
221
222 @staticmethod
223 def step_started(line):
224 return line.startswith('@@@STEP_STARTED@@@')
225
226 @staticmethod
227 def step_closed(line):
228 return line.startswith('@@@STEP_CLOSED@@@')
229
230 @staticmethod
231 def step_warnings(line):
232 return (line.startswith('@@@STEP_WARNINGS@@@') or
233 line.startswith('@@@BUILD_WARNINGS@@@')) # deprecated
234
235 @staticmethod
236 def step_failure(line):
237 return (line.startswith('@@@STEP_FAILURE@@@') or
238 line.startswith('@@@BUILD_FAILED@@@')) # deprecated
239
240 @staticmethod
241 def step_exception(line):
242 return (line.startswith('@@@STEP_EXCEPTION@@@') or
243 line.startswith('@@@BUILD_EXCEPTION@@@')) # deprecated
244
245 @staticmethod
246 def halt_on_failure(line):
247 return line.startswith('@@@HALT_ON_FAILURE@@@')
248
249 @staticmethod
250 def honor_zero_return_code(line):
251 return line.startswith('@@@HONOR_ZERO_RETURN_CODE@@@')
252
253 @staticmethod
254 def step_clear(line):
255 return line.startswith('@@@STEP_CLEAR@@@')
256
257 @staticmethod
258 def step_summary_clear(line):
259 return line.startswith('@@@STEP_SUMMARY_CLEAR@@@')
260
261 @staticmethod
262 def step_text(line):
263 return Match._parse_line('^@@@STEP_TEXT@(.*)@@@', line)
264
265 @staticmethod
266 def step_summary_text(line):
267 return Match._parse_line('^@@@STEP_SUMMARY_TEXT@(.*)@@@', line)
268
269 @staticmethod
270 def seed_step(line):
271 return Match._parse_line('^@@@SEED_STEP (.*)@@@', line)
272
273 @staticmethod
274 def step_cursor(line):
275 return Match._parse_line('^@@@STEP_CURSOR (.*)@@@', line)
276
277 @staticmethod
278 def build_step(line):
279 return Match._parse_line('^@@@BUILD_STEP (.*)@@@', line)
280
281
282 def main():
283 usage = '%s <command list file>' % sys.argv[0]
284 parser = optparse.OptionParser(usage=usage)
285 _, args = parser.parse_args()
286 if not args:
287 parser.error('Must specify an input filename!')
288
289 steps = []
290 with open(args[0], 'rb') as f:
291 for line in f:
292 steps.append(json.loads(line))
293 if ('cmd' not in steps[-1] or
294 'name' not in steps[-1]):
295 print 'line \'%s\' is invalid' % line.rstrip()
296 return 1
297
298 # make sure these steps always run, even if there is a build failure
agable 2013/02/28 21:32:08 nit: capitalization and periods on inline comments
299 always_run = {}
300 for step in steps:
301 if step.get('always_run'):
302 always_run[step['name']] = step
303
304 stepnames = [s['name'] for s in steps]
305
306 stream = StructuredAnnotationStream(seed_steps=stepnames)
307 build_failure = False
308 for step in steps:
309 if step['name'] in always_run:
310 del always_run[step['name']]
311 try:
312 with stream.step(step['name']) as s:
313 ret = chromium_utils.RunCommand(step['cmd'])
314 if ret != 0:
315 s.step_failure()
316 build_failure = True
317 break
318 except OSError:
319 # file wasn't found, error has been already reported to stream
320 build_failure = True
321 break
322
323 for step in always_run:
324 with stream.step(step['name']) as s:
325 ret = chromium_utils.RunCommand(step['cmd'])
326 if ret != 0:
327 s.step_failure()
328 build_failure = True
329
330 if build_failure:
331 return 1
332 return 0
333
334
335 if __name__ == '__main__':
336 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | scripts/master/chromium_step.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698