summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/bb/ui/crumbs/runningbuild.py
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/bb/ui/crumbs/runningbuild.py')
-rw-r--r--bitbake/lib/bb/ui/crumbs/runningbuild.py533
1 files changed, 533 insertions, 0 deletions
diff --git a/bitbake/lib/bb/ui/crumbs/runningbuild.py b/bitbake/lib/bb/ui/crumbs/runningbuild.py
new file mode 100644
index 0000000000..abd3300149
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/runningbuild.py
@@ -0,0 +1,533 @@
1
2#
3# BitBake Graphical GTK User Interface
4#
5# Copyright (C) 2008 Intel Corporation
6#
7# Authored by Rob Bradford <rob@linux.intel.com>
8#
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License version 2 as
11# published by the Free Software Foundation.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License along
19# with this program; if not, write to the Free Software Foundation, Inc.,
20# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
22import gtk
23import gobject
24import logging
25import time
26import urllib
27import urllib2
28import pango
29from bb.ui.crumbs.hobcolor import HobColors
30from bb.ui.crumbs.hobwidget import HobWarpCellRendererText, HobCellRendererPixbuf
31
32class RunningBuildModel (gtk.TreeStore):
33 (COL_LOG, COL_PACKAGE, COL_TASK, COL_MESSAGE, COL_ICON, COL_COLOR, COL_NUM_ACTIVE) = range(7)
34
35 def __init__ (self):
36 gtk.TreeStore.__init__ (self,
37 gobject.TYPE_STRING,
38 gobject.TYPE_STRING,
39 gobject.TYPE_STRING,
40 gobject.TYPE_STRING,
41 gobject.TYPE_STRING,
42 gobject.TYPE_STRING,
43 gobject.TYPE_INT)
44
45 def failure_model_filter(self, model, it):
46 color = model.get(it, self.COL_COLOR)[0]
47 if not color:
48 return False
49 if color == HobColors.ERROR or color == HobColors.WARNING:
50 return True
51 return False
52
53 def failure_model(self):
54 model = self.filter_new()
55 model.set_visible_func(self.failure_model_filter)
56 return model
57
58 def foreach_cell_func(self, model, path, iter, usr_data=None):
59 if model.get_value(iter, self.COL_ICON) == "gtk-execute":
60 model.set(iter, self.COL_ICON, "")
61
62 def close_task_refresh(self):
63 self.foreach(self.foreach_cell_func, None)
64
65class RunningBuild (gobject.GObject):
66 __gsignals__ = {
67 'build-started' : (gobject.SIGNAL_RUN_LAST,
68 gobject.TYPE_NONE,
69 ()),
70 'build-succeeded' : (gobject.SIGNAL_RUN_LAST,
71 gobject.TYPE_NONE,
72 ()),
73 'build-failed' : (gobject.SIGNAL_RUN_LAST,
74 gobject.TYPE_NONE,
75 ()),
76 'build-complete' : (gobject.SIGNAL_RUN_LAST,
77 gobject.TYPE_NONE,
78 ()),
79 'build-aborted' : (gobject.SIGNAL_RUN_LAST,
80 gobject.TYPE_NONE,
81 ()),
82 'task-started' : (gobject.SIGNAL_RUN_LAST,
83 gobject.TYPE_NONE,
84 (gobject.TYPE_PYOBJECT,)),
85 'log-error' : (gobject.SIGNAL_RUN_LAST,
86 gobject.TYPE_NONE,
87 ()),
88 'log-warning' : (gobject.SIGNAL_RUN_LAST,
89 gobject.TYPE_NONE,
90 ()),
91 'disk-full' : (gobject.SIGNAL_RUN_LAST,
92 gobject.TYPE_NONE,
93 ()),
94 'no-provider' : (gobject.SIGNAL_RUN_LAST,
95 gobject.TYPE_NONE,
96 (gobject.TYPE_PYOBJECT,)),
97 'log' : (gobject.SIGNAL_RUN_LAST,
98 gobject.TYPE_NONE,
99 (gobject.TYPE_STRING, gobject.TYPE_PYOBJECT,)),
100 }
101 pids_to_task = {}
102 tasks_to_iter = {}
103
104 def __init__ (self, sequential=False):
105 gobject.GObject.__init__ (self)
106 self.model = RunningBuildModel()
107 self.sequential = sequential
108 self.buildaborted = False
109
110 def reset (self):
111 self.pids_to_task.clear()
112 self.tasks_to_iter.clear()
113 self.model.clear()
114
115 def handle_event (self, event, pbar=None):
116 # Handle an event from the event queue, this may result in updating
117 # the model and thus the UI. Or it may be to tell us that the build
118 # has finished successfully (or not, as the case may be.)
119
120 parent = None
121 pid = 0
122 package = None
123 task = None
124
125 # If we have a pid attached to this message/event try and get the
126 # (package, task) pair for it. If we get that then get the parent iter
127 # for the message.
128 if hasattr(event, 'pid'):
129 pid = event.pid
130 if hasattr(event, 'process'):
131 pid = event.process
132
133 if pid and pid in self.pids_to_task:
134 (package, task) = self.pids_to_task[pid]
135 parent = self.tasks_to_iter[(package, task)]
136
137 if(isinstance(event, logging.LogRecord)):
138 if event.taskpid == 0 or event.levelno > logging.INFO:
139 self.emit("log", "handle", event)
140 # FIXME: this is a hack! More info in Yocto #1433
141 # http://bugzilla.pokylinux.org/show_bug.cgi?id=1433, temporarily
142 # mask the error message as it's not informative for the user.
143 if event.msg.startswith("Execution of event handler 'run_buildstats' failed"):
144 return
145
146 if (event.levelno < logging.INFO or
147 event.msg.startswith("Running task")):
148 return # don't add these to the list
149
150 if event.levelno >= logging.ERROR:
151 icon = "dialog-error"
152 color = HobColors.ERROR
153 self.emit("log-error")
154 elif event.levelno >= logging.WARNING:
155 icon = "dialog-warning"
156 color = HobColors.WARNING
157 self.emit("log-warning")
158 else:
159 icon = None
160 color = HobColors.OK
161
162 # if we know which package we belong to, we'll append onto its list.
163 # otherwise, we'll jump to the top of the master list
164 if self.sequential or not parent:
165 tree_add = self.model.append
166 else:
167 tree_add = self.model.prepend
168 tree_add(parent,
169 (None,
170 package,
171 task,
172 event.getMessage(),
173 icon,
174 color,
175 0))
176
177 elif isinstance(event, bb.build.TaskStarted):
178 (package, task) = (event._package, event._task)
179
180 # Save out this PID.
181 self.pids_to_task[pid] = (package, task)
182
183 # Check if we already have this package in our model. If so then
184 # that can be the parent for the task. Otherwise we create a new
185 # top level for the package.
186 if ((package, None) in self.tasks_to_iter):
187 parent = self.tasks_to_iter[(package, None)]
188 else:
189 if self.sequential:
190 add = self.model.append
191 else:
192 add = self.model.prepend
193 parent = add(None, (None,
194 package,
195 None,
196 "Package: %s" % (package),
197 None,
198 HobColors.OK,
199 0))
200 self.tasks_to_iter[(package, None)] = parent
201
202 # Because this parent package now has an active child mark it as
203 # such.
204 # @todo if parent is already in error, don't mark it green
205 self.model.set(parent, self.model.COL_ICON, "gtk-execute",
206 self.model.COL_COLOR, HobColors.RUNNING)
207
208 # Add an entry in the model for this task
209 i = self.model.append (parent, (None,
210 package,
211 task,
212 "Task: %s" % (task),
213 "gtk-execute",
214 HobColors.RUNNING,
215 0))
216
217 # update the parent's active task count
218 num_active = self.model.get(parent, self.model.COL_NUM_ACTIVE)[0] + 1
219 self.model.set(parent, self.model.COL_NUM_ACTIVE, num_active)
220
221 # Save out the iter so that we can find it when we have a message
222 # that we need to attach to a task.
223 self.tasks_to_iter[(package, task)] = i
224
225 elif isinstance(event, bb.build.TaskBase):
226 self.emit("log", "info", event._message)
227 current = self.tasks_to_iter[(package, task)]
228 parent = self.tasks_to_iter[(package, None)]
229
230 # remove this task from the parent's active count
231 num_active = self.model.get(parent, self.model.COL_NUM_ACTIVE)[0] - 1
232 self.model.set(parent, self.model.COL_NUM_ACTIVE, num_active)
233
234 if isinstance(event, bb.build.TaskFailed):
235 # Mark the task and parent as failed
236 icon = "dialog-error"
237 color = HobColors.ERROR
238
239 logfile = event.logfile
240 if logfile and os.path.exists(logfile):
241 with open(logfile) as f:
242 logdata = f.read()
243 self.model.append(current, ('pastebin', None, None, logdata, 'gtk-error', HobColors.OK, 0))
244
245 for i in (current, parent):
246 self.model.set(i, self.model.COL_ICON, icon,
247 self.model.COL_COLOR, color)
248 else:
249 icon = None
250 color = HobColors.OK
251
252 # Mark the task as inactive
253 self.model.set(current, self.model.COL_ICON, icon,
254 self.model.COL_COLOR, color)
255
256 # Mark the parent package as inactive, but make sure to
257 # preserve error and active states
258 i = self.tasks_to_iter[(package, None)]
259 if self.model.get(parent, self.model.COL_ICON) != 'dialog-error':
260 self.model.set(parent, self.model.COL_ICON, icon)
261 if num_active == 0:
262 self.model.set(parent, self.model.COL_COLOR, HobColors.OK)
263
264 # Clear the iters and the pids since when the task goes away the
265 # pid will no longer be used for messages
266 del self.tasks_to_iter[(package, task)]
267 del self.pids_to_task[pid]
268
269 elif isinstance(event, bb.event.BuildStarted):
270
271 self.emit("build-started")
272 self.model.prepend(None, (None,
273 None,
274 None,
275 "Build Started (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'),
276 None,
277 HobColors.OK,
278 0))
279 if pbar:
280 pbar.update(0, self.progress_total)
281 pbar.set_title(bb.event.getName(event))
282
283 elif isinstance(event, bb.event.BuildCompleted):
284 failures = int (event._failures)
285 self.model.prepend(None, (None,
286 None,
287 None,
288 "Build Completed (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'),
289 None,
290 HobColors.OK,
291 0))
292
293 # Emit the appropriate signal depending on the number of failures
294 if self.buildaborted:
295 self.emit ("build-aborted")
296 self.buildaborted = False
297 elif (failures >= 1):
298 self.emit ("build-failed")
299 else:
300 self.emit ("build-succeeded")
301 # Emit a generic "build-complete" signal for things wishing to
302 # handle when the build is finished
303 self.emit("build-complete")
304 # reset the all cell's icon indicator
305 self.model.close_task_refresh()
306 if pbar:
307 pbar.set_text(event.msg)
308
309 elif isinstance(event, bb.event.DiskFull):
310 self.buildaborted = True
311 self.emit("disk-full")
312
313 elif isinstance(event, bb.command.CommandFailed):
314 self.emit("log", "error", "Command execution failed: %s" % (event.error))
315 if event.error.startswith("Exited with"):
316 # If the command fails with an exit code we're done, emit the
317 # generic signal for the UI to notify the user
318 self.emit("build-complete")
319 # reset the all cell's icon indicator
320 self.model.close_task_refresh()
321
322 elif isinstance(event, bb.event.CacheLoadStarted) and pbar:
323 pbar.set_title("Loading cache")
324 self.progress_total = event.total
325 pbar.update(0, self.progress_total)
326 elif isinstance(event, bb.event.CacheLoadProgress) and pbar:
327 pbar.update(event.current, self.progress_total)
328 elif isinstance(event, bb.event.CacheLoadCompleted) and pbar:
329 pbar.update(self.progress_total, self.progress_total)
330 pbar.hide()
331 elif isinstance(event, bb.event.ParseStarted) and pbar:
332 if event.total == 0:
333 return
334 pbar.set_title("Processing recipes")
335 self.progress_total = event.total
336 pbar.update(0, self.progress_total)
337 elif isinstance(event, bb.event.ParseProgress) and pbar:
338 pbar.update(event.current, self.progress_total)
339 elif isinstance(event, bb.event.ParseCompleted) and pbar:
340 pbar.hide()
341 #using runqueue events as many as possible to update the progress bar
342 elif isinstance(event, bb.runqueue.runQueueTaskFailed):
343 self.emit("log", "error", "Task %s (%s) failed with exit code '%s'" % (event.taskid, event.taskstring, event.exitcode))
344 elif isinstance(event, bb.runqueue.sceneQueueTaskFailed):
345 self.emit("log", "warn", "Setscene task %s (%s) failed with exit code '%s' - real task will be run instead" \
346 % (event.taskid, event.taskstring, event.exitcode))
347 elif isinstance(event, (bb.runqueue.runQueueTaskStarted, bb.runqueue.sceneQueueTaskStarted)):
348 if isinstance(event, bb.runqueue.sceneQueueTaskStarted):
349 self.emit("log", "info", "Running setscene task %d of %d (%s)" % \
350 (event.stats.completed + event.stats.active + event.stats.failed + 1,
351 event.stats.total, event.taskstring))
352 else:
353 if event.noexec:
354 tasktype = 'noexec task'
355 else:
356 tasktype = 'task'
357 self.emit("log", "info", "Running %s %s of %s (ID: %s, %s)" % \
358 (tasktype, event.stats.completed + event.stats.active + event.stats.failed + 1,
359 event.stats.total, event.taskid, event.taskstring))
360 message = {}
361 message["eventname"] = bb.event.getName(event)
362 num_of_completed = event.stats.completed + event.stats.failed
363 message["current"] = num_of_completed
364 message["total"] = event.stats.total
365 message["title"] = ""
366 message["task"] = event.taskstring
367 self.emit("task-started", message)
368 elif isinstance(event, bb.event.MultipleProviders):
369 self.emit("log", "info", "multiple providers are available for %s%s (%s)" \
370 % (event._is_runtime and "runtime " or "", event._item, ", ".join(event._candidates)))
371 self.emit("log", "info", "consider defining a PREFERRED_PROVIDER entry to match %s" % (event._item))
372 elif isinstance(event, bb.event.NoProvider):
373 msg = ""
374 if event._runtime:
375 r = "R"
376 else:
377 r = ""
378
379 extra = ''
380 if not event._reasons:
381 if event._close_matches:
382 extra = ". Close matches:\n %s" % '\n '.join(event._close_matches)
383
384 if event._dependees:
385 msg = "Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)%s\n" % (r, event._item, ", ".join(event._dependees), r, extra)
386 else:
387 msg = "Nothing %sPROVIDES '%s'%s\n" % (r, event._item, extra)
388 if event._reasons:
389 for reason in event._reasons:
390 msg += ("%s\n" % reason)
391 self.emit("no-provider", msg)
392 self.emit("log", "error", msg)
393 elif isinstance(event, bb.event.LogExecTTY):
394 icon = "dialog-warning"
395 color = HobColors.WARNING
396 if self.sequential or not parent:
397 tree_add = self.model.append
398 else:
399 tree_add = self.model.prepend
400 tree_add(parent,
401 (None,
402 package,
403 task,
404 event.msg,
405 icon,
406 color,
407 0))
408 else:
409 if not isinstance(event, (bb.event.BuildBase,
410 bb.event.StampUpdate,
411 bb.event.ConfigParsed,
412 bb.event.RecipeParsed,
413 bb.event.RecipePreFinalise,
414 bb.runqueue.runQueueEvent,
415 bb.runqueue.runQueueExitWait,
416 bb.event.OperationStarted,
417 bb.event.OperationCompleted,
418 bb.event.OperationProgress)):
419 self.emit("log", "error", "Unknown event: %s" % (event.error if hasattr(event, 'error') else 'error'))
420
421 return
422
423
424def do_pastebin(text):
425 url = 'http://pastebin.com/api_public.php'
426 params = {'paste_code': text, 'paste_format': 'text'}
427
428 req = urllib2.Request(url, urllib.urlencode(params))
429 response = urllib2.urlopen(req)
430 paste_url = response.read()
431
432 return paste_url
433
434
435class RunningBuildTreeView (gtk.TreeView):
436 __gsignals__ = {
437 "button_press_event" : "override"
438 }
439 def __init__ (self, readonly=False, hob=False):
440 gtk.TreeView.__init__ (self)
441 self.readonly = readonly
442
443 # The icon that indicates whether we're building or failed.
444 # add 'hob' flag because there has not only hob to share this code
445 if hob:
446 renderer = HobCellRendererPixbuf ()
447 else:
448 renderer = gtk.CellRendererPixbuf()
449 col = gtk.TreeViewColumn ("Status", renderer)
450 col.add_attribute (renderer, "icon-name", 4)
451 self.append_column (col)
452
453 # The message of the build.
454 # add 'hob' flag because there has not only hob to share this code
455 if hob:
456 self.message_renderer = HobWarpCellRendererText (col_number=1)
457 else:
458 self.message_renderer = gtk.CellRendererText ()
459 self.message_column = gtk.TreeViewColumn ("Message", self.message_renderer, text=3)
460 self.message_column.add_attribute(self.message_renderer, 'background', 5)
461 self.message_renderer.set_property('editable', (not self.readonly))
462 self.append_column (self.message_column)
463
464 def do_button_press_event(self, event):
465 gtk.TreeView.do_button_press_event(self, event)
466
467 if event.button == 3:
468 selection = super(RunningBuildTreeView, self).get_selection()
469 (model, it) = selection.get_selected()
470 if it is not None:
471 can_paste = model.get(it, model.COL_LOG)[0]
472 if can_paste == 'pastebin':
473 # build a simple menu with a pastebin option
474 menu = gtk.Menu()
475 menuitem = gtk.MenuItem("Copy")
476 menu.append(menuitem)
477 menuitem.connect("activate", self.clipboard_handler, (model, it))
478 menuitem.show()
479 menuitem = gtk.MenuItem("Send log to pastebin")
480 menu.append(menuitem)
481 menuitem.connect("activate", self.pastebin_handler, (model, it))
482 menuitem.show()
483 menu.show()
484 menu.popup(None, None, None, event.button, event.time)
485
486 def _add_to_clipboard(self, clipping):
487 """
488 Add the contents of clipping to the system clipboard.
489 """
490 clipboard = gtk.clipboard_get()
491 clipboard.set_text(clipping)
492 clipboard.store()
493
494 def pastebin_handler(self, widget, data):
495 """
496 Send the log data to pastebin, then add the new paste url to the
497 clipboard.
498 """
499 (model, it) = data
500 paste_url = do_pastebin(model.get(it, model.COL_MESSAGE)[0])
501
502 # @todo Provide visual feedback to the user that it is done and that
503 # it worked.
504 print paste_url
505
506 self._add_to_clipboard(paste_url)
507
508 def clipboard_handler(self, widget, data):
509 """
510 """
511 (model, it) = data
512 message = model.get(it, model.COL_MESSAGE)[0]
513
514 self._add_to_clipboard(message)
515
516class BuildFailureTreeView(gtk.TreeView):
517
518 def __init__ (self):
519 gtk.TreeView.__init__(self)
520 self.set_rules_hint(False)
521 self.set_headers_visible(False)
522 self.get_selection().set_mode(gtk.SELECTION_SINGLE)
523
524 # The icon that indicates whether we're building or failed.
525 renderer = HobCellRendererPixbuf ()
526 col = gtk.TreeViewColumn ("Status", renderer)
527 col.add_attribute (renderer, "icon-name", RunningBuildModel.COL_ICON)
528 self.append_column (col)
529
530 # The message of the build.
531 self.message_renderer = HobWarpCellRendererText (col_number=1)
532 self.message_column = gtk.TreeViewColumn ("Message", self.message_renderer, text=RunningBuildModel.COL_MESSAGE, background=RunningBuildModel.COL_COLOR)
533 self.append_column (self.message_column)