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

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: 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 either
10
11 step_name command arg1 arg2...
12 step_name2 command2 arg1 arg2...
13
14 or if --json is specified:
15 {"name": "step_name", "cmd": ["command", "arg1", "arg2"]}
16 {"name": "step_name2", "cmd": ["command2", "arg1"]}
iannucci 2013/02/28 09:56:03 Why not just do json? It's easier to parse and the
17
18 """
19
20 import json
21 import optparse
22 import re
23 import shlex
24 import sys
25 import traceback
26
27 from common import chromium_utils
28
29
30 class AdvancedAnnotationStream(object):
31 """Holds individual annotation generating functions.
32
33 Most callers should use StructuredAnnotationStream to simplify coding and
34 avoid errors. For the rare cases where StructuredAnnotationStream is
35 insufficient (parallel step execution), the indidividual functions are exposed
36 here.
37
38 """
39
40 def __init__(self, stream=sys.stdout):
41 self.stream = stream
42
43 def emit(self, line):
44 print >> self.stream, line
45
46 def seed_step(self, step):
47 self.emit('@@@SEED_STEP %s@@@' % step)
iannucci 2013/02/28 09:56:03 These methods can all be generated from the same l
48
49 def step_cursor(self, step):
50 self.emit('@@@STEP_CURSOR %s@@@' % step)
51
52 def build_step(self, line, step):
53 self.emit('@@@BUILD_STEP %s@@@' % step) # deprecated, use SEED_STEP
54
55 def step_started(self):
56 self.emit('@@@STEP_STARTED@@@')
57
58 def step_closed(self):
59 self.emit('@@@STEP_CLOSED@@@')
60
61 def step_warnings(self):
62 self.emit('@@@STEP_WARNINGS@@@')
63
64 def step_failure(self):
65 self.emit('@@@STEP_FAILURE@@@')
66
67 def step_exception(self):
68 self.emit('@@@STEP_EXCEPTION@@@')
69
70 def halt_on_failure(self):
71 self.emit('@@@HALT_ON_FAILURE@@@')
72
73 def honor_zero_return_code(self):
74 self.emit('@@@HONOR_ZERO_RETURN_CODE@@@')
75
76 def step_clear(self):
77 self.emit('@@@STEP_CLEAR@@@')
78
79 def step_summary_clear(self):
80 self.emit('@@@STEP_SUMMARY_CLEAR@@@')
81
82 def step_text(self, text):
83 self.emit('@@@STEP_TEXT@%s@@@' % text)
84
85 def step_summary_text(self, text):
86 self.emit('@@@STEP_SUMMARY_TEXT@%s@@@' % text)
87
88 def write_log_line(self, logname, line):
89 self.emit('@@@STEP_LOG_LINE@%s@%s@@@' % (logname, line.rstrip('\n')))
90
91 def log_end(self, logname):
92 self.emit('@@@STEP_LOG_END@%s@@@' % logname)
93
94 def log_end_perf(self, logname, perf):
95 self.emit('@@@STEP_LOG_END_PERF@%s@%s@@@' % (logname, perf))
96
97 def write_log_lines(self, logname, lines, perf=None):
98 for line in lines:
99 self.write_log_line(logname, line)
100 if perf:
101 self.log_end_perf(logname, perf)
102 else:
103 self.log_end(logname)
104
105
106 class StructuredAnnotationStream(AdvancedAnnotationStream):
107 """Provides an interface to handle an annotated build.
108
109 StructuredAnnotationStream handles most of the step setup and closure calls
110 for you. All you have to do is execute your code within the steps and set any
111 failures or warnings that come up. You may optionally provide a list of steps
112 to seed before execution.
113
114 Usage:
115
116 stream = StructuredAnnotationStream()
117 with stream.step('compile') as s:
118 # do something
119 if error:
120 s.step_failure()
121 with stream.step('test') as s:
122 # do something
123 if warnings:
124 s.step_warnings()
125 """
126
127 def __init__(self, seed_steps=None, stream=sys.stdout):
128 super(StructuredAnnotationStream, self).__init__(stream=stream)
129 seed_steps = seed_steps or []
130 self.seed_steps = seed_steps
131
132 for step in seed_steps:
133 self.seed_step(step)
134
135 self.current_step = ''
136
137 def __enter__(self):
138 return self
139
140 def __exit__(self, exc_type, exc_value, tb):
141 if exc_type:
142 trace = traceback.format_exception(exc_type, exc_value, tb)
143 unflattened = [l.split('\n') for l in trace]
144 flattened = [item for sublist in unflattened for item in sublist]
145 self.write_log_lines('exception', filter(None, flattened))
146 self.step_exception()
147 self.step_closed()
148 self.current_step = ''
149 return not exc_type
150
151 def step(self, name):
iannucci 2013/02/28 09:56:03 I'm not sure I like using self as both the contain
Mike Stip (use stip instead) 2013/02/28 20:35:22 Done.
152 """Provide a context with which to execute a step."""
153 if self.current_step:
154 raise Exception('Can\'t start step %s while in step %s.' % (
155 name, self.current_step))
156 if name in self.seed_steps:
157 # Seek ahead linearly, skipping steps that weren't emitted in order.
158 # chromium_step.AnnotatedCommands uses the last in case of duplicated
159 # step names, so we do the same here.
160 idx = len(self.seed_steps) - self.seed_steps[::-1].index(name)
161 self.seed_steps = self.seed_steps[idx:]
162 else:
163 self.seed_step(name)
164
165 self.step_cursor(name)
166 self.step_started()
167 self.current_step = name
168 return self
169
170
171 class Match:
172 """Holds annotator line parsing functions."""
173
174 def __init__(self):
175 raise Exception('Don\'t instantiate the Match class!')
176
177 @staticmethod
178 def _parse_line(regex, line):
179 m = re.match(regex, line)
180 if m:
181 return list(m.groups())
182 else:
183 return []
184
185 @staticmethod
186 def log_line(line):
iannucci 2013/02/28 09:56:03 It seems to me like all of these methods should be
Mike Stip (use stip instead) 2013/02/28 20:35:22 I like this idea, but let's save it for a future C
agable 2013/02/28 21:32:08 While a fun trick, that doesn't gain you any perfo
187 return Match._parse_line('^@@@STEP_LOG_LINE@(.*)@(.*)@@@', line)
188
189 @staticmethod
190 def log_end(line):
191 return Match._parse_line('^@@@STEP_LOG_END@(.*)@@@', line)
192
193 @staticmethod
194 def log_end_perf(line):
195 return Match._parse_line('^@@@STEP_LOG_END_PERF@(.*)@(.*)@@@', line)
196
197 @staticmethod
198 def step_link(line):
199 m = Match._parse_line('^@@@STEP_LINK@(.*)@(.*)@@@', line)
200 if not m:
201 return Match._parse_line('^@@@link@(.*)@(.*)@@@', line) # deprecated
202 else:
203 return m
204
205 @staticmethod
206 def step_started(line):
207 return line.startswith('@@@STEP_STARTED@@@')
208
209 @staticmethod
210 def step_closed(line):
211 return line.startswith('@@@STEP_CLOSED@@@')
212
213 @staticmethod
214 def step_warnings(line):
215 return (line.startswith('@@@STEP_WARNINGS@@@') or
216 line.startswith('@@@BUILD_WARNINGS@@@')) # deprecated
217
218 @staticmethod
219 def step_failure(line):
220 return (line.startswith('@@@STEP_FAILURE@@@') or
221 line.startswith('@@@BUILD_FAILED@@@')) # deprecated
222
223 @staticmethod
224 def step_exception(line):
225 return (line.startswith('@@@STEP_EXCEPTION@@@') or
226 line.startswith('@@@BUILD_EXCEPTION@@@')) # deprecated
227
228 @staticmethod
229 def halt_on_failure(line):
230 return line.startswith('@@@HALT_ON_FAILURE@@@')
231
232 @staticmethod
233 def honor_zero_return_code(line):
234 return line.startswith('@@@HONOR_ZERO_RETURN_CODE@@@')
235
236 @staticmethod
237 def step_clear(line):
238 return line.startswith('@@@STEP_CLEAR@@@')
239
240 @staticmethod
241 def step_summary_clear(line):
242 return line.startswith('@@@STEP_SUMMARY_CLEAR@@@')
243
244 @staticmethod
245 def step_text(line):
246 return Match._parse_line('^@@@STEP_TEXT@(.*)@@@', line)
247
248 @staticmethod
249 def step_summary_text(line):
250 return Match._parse_line('^@@@STEP_SUMMARY_TEXT@(.*)@@@', line)
251
252 @staticmethod
253 def seed_step(line):
254 return Match._parse_line('^@@@SEED_STEP (.*)@@@', line)
255
256 @staticmethod
257 def step_cursor(line):
258 return Match._parse_line('^@@@STEP_CURSOR (.*)@@@', line)
259
260 @staticmethod
261 def build_step(line):
262 return Match._parse_line('^@@@BUILD_STEP (.*)@@@', line)
263
264
265 def main():
266 usage = '%s <command list file>' % sys.argv[0]
267 parser = optparse.OptionParser(usage=usage)
268 parser.add_option('-j', '--json', action='store_true',
269 help='Specify that each line is a json blob encoding '
270 'the step.')
271 options, args = parser.parse_args()
272 if not args:
273 parser.error('Must specify an input filename!')
274
275 steps = []
276 with open(args[0], 'rb') as f:
277 for line in f:
278 if options.json:
279 steps.append(json.loads(line))
280 if ('cmd' not in steps[-1] or
281 'name' not in steps[-1]):
282 print 'line \'%s\' is invalid' % line.rstrip()
283 return 1
284 else:
285 step = {}
286 chunks = line.split(' ')
287 step['name'] = chunks[0]
288 if not step['name']:
289 print 'line \'%s\' is invalid' % line.rstrip()
290 return 1
291 command = line[len(step['name'])+1:]
292 if not command:
293 print 'line \'%s\' is invalid' % line.rstrip()
294 return 1
295 step['cmd'] = shlex.split(command)
296 steps.append(step)
297
298 stepnames = [s['name'] for s in steps]
299 stream = StructuredAnnotationStream(seed_steps = stepnames)
300 for step in steps:
301 with stream.step(step['name']) as s:
302 ret = chromium_utils.RunCommand(step['cmd'])
303 if ret != 0:
304 s.step_failure()
iannucci 2013/02/28 09:56:03 Should ret be interpreted to mean 88 == warning (i
305 return 1
iannucci 2013/02/28 09:56:03 Do we need a way to express commands which should
Mike Stip (use stip instead) 2013/02/28 20:35:22 Done.
306
307 return 0
308
309
310 if __name__ == '__main__':
311 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