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.py551
1 files changed, 0 insertions, 551 deletions
diff --git a/bitbake/lib/bb/ui/crumbs/runningbuild.py b/bitbake/lib/bb/ui/crumbs/runningbuild.py
deleted file mode 100644
index 9b695ac2ed..0000000000
--- a/bitbake/lib/bb/ui/crumbs/runningbuild.py
+++ /dev/null
@@ -1,551 +0,0 @@
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.request, urllib.parse, urllib.error
27import urllib.request, urllib.error, urllib.parse
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) = list(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 # if there are warnings while processing a package
178 # (parent), mark the task with warning color;
179 # in case there are errors, the updates will be
180 # handled on TaskFailed.
181 if color == HobColors.WARNING and parent:
182 self.model.set(parent, self.model.COL_COLOR, color)
183 if task: #then we have a parent (package), and update it's color
184 self.model.set(self.tasks_to_iter[(package, None)], self.model.COL_COLOR, color)
185
186 elif isinstance(event, bb.build.TaskStarted):
187 (package, task) = (event._package, event._task)
188
189 # Save out this PID.
190 self.pids_to_task[pid] = (package, task)
191
192 # Check if we already have this package in our model. If so then
193 # that can be the parent for the task. Otherwise we create a new
194 # top level for the package.
195 if ((package, None) in self.tasks_to_iter):
196 parent = self.tasks_to_iter[(package, None)]
197 else:
198 if self.sequential:
199 add = self.model.append
200 else:
201 add = self.model.prepend
202 parent = add(None, (None,
203 package,
204 None,
205 "Package: %s" % (package),
206 None,
207 HobColors.OK,
208 0))
209 self.tasks_to_iter[(package, None)] = parent
210
211 # Because this parent package now has an active child mark it as
212 # such.
213 self.model.set(parent, self.model.COL_ICON, "gtk-execute")
214 parent_color = self.model.get(parent, self.model.COL_COLOR)[0]
215 if parent_color != HobColors.ERROR and parent_color != HobColors.WARNING:
216 self.model.set(parent, self.model.COL_COLOR, HobColors.RUNNING)
217
218 # Add an entry in the model for this task
219 i = self.model.append (parent, (None,
220 package,
221 task,
222 "Task: %s" % (task),
223 "gtk-execute",
224 HobColors.RUNNING,
225 0))
226
227 # update the parent's active task count
228 num_active = self.model.get(parent, self.model.COL_NUM_ACTIVE)[0] + 1
229 self.model.set(parent, self.model.COL_NUM_ACTIVE, num_active)
230
231 # Save out the iter so that we can find it when we have a message
232 # that we need to attach to a task.
233 self.tasks_to_iter[(package, task)] = i
234
235 elif isinstance(event, bb.build.TaskBase):
236 self.emit("log", "info", event._message)
237 current = self.tasks_to_iter[(package, task)]
238 parent = self.tasks_to_iter[(package, None)]
239
240 # remove this task from the parent's active count
241 num_active = self.model.get(parent, self.model.COL_NUM_ACTIVE)[0] - 1
242 self.model.set(parent, self.model.COL_NUM_ACTIVE, num_active)
243
244 if isinstance(event, bb.build.TaskFailed):
245 # Mark the task and parent as failed
246 icon = "dialog-error"
247 color = HobColors.ERROR
248
249 logfile = event.logfile
250 if logfile and os.path.exists(logfile):
251 with open(logfile) as f:
252 logdata = f.read()
253 self.model.append(current, ('pastebin', None, None, logdata, 'gtk-error', HobColors.OK, 0))
254
255 for i in (current, parent):
256 self.model.set(i, self.model.COL_ICON, icon,
257 self.model.COL_COLOR, color)
258 else:
259 # Mark the parent package and the task as inactive,
260 # but make sure to preserve error, warnings and active
261 # states
262 parent_color = self.model.get(parent, self.model.COL_COLOR)[0]
263 task_color = self.model.get(current, self.model.COL_COLOR)[0]
264
265 # Mark the task as inactive
266 self.model.set(current, self.model.COL_ICON, None)
267 if task_color != HobColors.ERROR:
268 if task_color == HobColors.WARNING:
269 self.model.set(current, self.model.COL_ICON, 'dialog-warning')
270 else:
271 self.model.set(current, self.model.COL_COLOR, HobColors.OK)
272
273 # Mark the parent as inactive
274 if parent_color != HobColors.ERROR:
275 if parent_color == HobColors.WARNING:
276 self.model.set(parent, self.model.COL_ICON, "dialog-warning")
277 else:
278 self.model.set(parent, self.model.COL_ICON, None)
279 if num_active == 0:
280 self.model.set(parent, self.model.COL_COLOR, HobColors.OK)
281
282 # Clear the iters and the pids since when the task goes away the
283 # pid will no longer be used for messages
284 del self.tasks_to_iter[(package, task)]
285 del self.pids_to_task[pid]
286
287 elif isinstance(event, bb.event.BuildStarted):
288
289 self.emit("build-started")
290 self.model.prepend(None, (None,
291 None,
292 None,
293 "Build Started (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'),
294 None,
295 HobColors.OK,
296 0))
297 if pbar:
298 pbar.update(0, self.progress_total)
299 pbar.set_title(bb.event.getName(event))
300
301 elif isinstance(event, bb.event.BuildCompleted):
302 failures = int (event._failures)
303 self.model.prepend(None, (None,
304 None,
305 None,
306 "Build Completed (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'),
307 None,
308 HobColors.OK,
309 0))
310
311 # Emit the appropriate signal depending on the number of failures
312 if self.buildaborted:
313 self.emit ("build-aborted")
314 self.buildaborted = False
315 elif (failures >= 1):
316 self.emit ("build-failed")
317 else:
318 self.emit ("build-succeeded")
319 # Emit a generic "build-complete" signal for things wishing to
320 # handle when the build is finished
321 self.emit("build-complete")
322 # reset the all cell's icon indicator
323 self.model.close_task_refresh()
324 if pbar:
325 pbar.set_text(event.msg)
326
327 elif isinstance(event, bb.event.DiskFull):
328 self.buildaborted = True
329 self.emit("disk-full")
330
331 elif isinstance(event, bb.command.CommandFailed):
332 self.emit("log", "error", "Command execution failed: %s" % (event.error))
333 if event.error.startswith("Exited with"):
334 # If the command fails with an exit code we're done, emit the
335 # generic signal for the UI to notify the user
336 self.emit("build-complete")
337 # reset the all cell's icon indicator
338 self.model.close_task_refresh()
339
340 elif isinstance(event, bb.event.CacheLoadStarted) and pbar:
341 pbar.set_title("Loading cache")
342 self.progress_total = event.total
343 pbar.update(0, self.progress_total)
344 elif isinstance(event, bb.event.CacheLoadProgress) and pbar:
345 pbar.update(event.current, self.progress_total)
346 elif isinstance(event, bb.event.CacheLoadCompleted) and pbar:
347 pbar.update(self.progress_total, self.progress_total)
348 pbar.hide()
349 elif isinstance(event, bb.event.ParseStarted) and pbar:
350 if event.total == 0:
351 return
352 pbar.set_title("Processing recipes")
353 self.progress_total = event.total
354 pbar.update(0, self.progress_total)
355 elif isinstance(event, bb.event.ParseProgress) and pbar:
356 pbar.update(event.current, self.progress_total)
357 elif isinstance(event, bb.event.ParseCompleted) and pbar:
358 pbar.hide()
359 #using runqueue events as many as possible to update the progress bar
360 elif isinstance(event, bb.runqueue.runQueueTaskFailed):
361 self.emit("log", "error", "Task %s (%s) failed with exit code '%s'" % (event.taskid, event.taskstring, event.exitcode))
362 elif isinstance(event, bb.runqueue.sceneQueueTaskFailed):
363 self.emit("log", "warn", "Setscene task %s (%s) failed with exit code '%s' - real task will be run instead" \
364 % (event.taskid, event.taskstring, event.exitcode))
365 elif isinstance(event, (bb.runqueue.runQueueTaskStarted, bb.runqueue.sceneQueueTaskStarted)):
366 if isinstance(event, bb.runqueue.sceneQueueTaskStarted):
367 self.emit("log", "info", "Running setscene task %d of %d (%s)" % \
368 (event.stats.completed + event.stats.active + event.stats.failed + 1,
369 event.stats.total, event.taskstring))
370 else:
371 if event.noexec:
372 tasktype = 'noexec task'
373 else:
374 tasktype = 'task'
375 self.emit("log", "info", "Running %s %s of %s (ID: %s, %s)" % \
376 (tasktype, event.stats.completed + event.stats.active + event.stats.failed + 1,
377 event.stats.total, event.taskid, event.taskstring))
378 message = {}
379 message["eventname"] = bb.event.getName(event)
380 num_of_completed = event.stats.completed + event.stats.failed
381 message["current"] = num_of_completed
382 message["total"] = event.stats.total
383 message["title"] = ""
384 message["task"] = event.taskstring
385 self.emit("task-started", message)
386 elif isinstance(event, bb.event.MultipleProviders):
387 self.emit("log", "info", "multiple providers are available for %s%s (%s)" \
388 % (event._is_runtime and "runtime " or "", event._item, ", ".join(event._candidates)))
389 self.emit("log", "info", "consider defining a PREFERRED_PROVIDER entry to match %s" % (event._item))
390 elif isinstance(event, bb.event.NoProvider):
391 msg = ""
392 if event._runtime:
393 r = "R"
394 else:
395 r = ""
396
397 extra = ''
398 if not event._reasons:
399 if event._close_matches:
400 extra = ". Close matches:\n %s" % '\n '.join(event._close_matches)
401
402 if event._dependees:
403 msg = "Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)%s\n" % (r, event._item, ", ".join(event._dependees), r, extra)
404 else:
405 msg = "Nothing %sPROVIDES '%s'%s\n" % (r, event._item, extra)
406 if event._reasons:
407 for reason in event._reasons:
408 msg += ("%s\n" % reason)
409 self.emit("no-provider", msg)
410 self.emit("log", "error", msg)
411 elif isinstance(event, bb.event.LogExecTTY):
412 icon = "dialog-warning"
413 color = HobColors.WARNING
414 if self.sequential or not parent:
415 tree_add = self.model.append
416 else:
417 tree_add = self.model.prepend
418 tree_add(parent,
419 (None,
420 package,
421 task,
422 event.msg,
423 icon,
424 color,
425 0))
426 else:
427 if not isinstance(event, (bb.event.BuildBase,
428 bb.event.StampUpdate,
429 bb.event.ConfigParsed,
430 bb.event.RecipeParsed,
431 bb.event.RecipePreFinalise,
432 bb.runqueue.runQueueEvent,
433 bb.runqueue.runQueueExitWait,
434 bb.event.OperationStarted,
435 bb.event.OperationCompleted,
436 bb.event.OperationProgress)):
437 self.emit("log", "error", "Unknown event: %s" % (event.error if hasattr(event, 'error') else 'error'))
438
439 return
440
441
442def do_pastebin(text):
443 url = 'http://pastebin.com/api_public.php'
444 params = {'paste_code': text, 'paste_format': 'text'}
445
446 req = urllib.request.Request(url, urllib.parse.urlencode(params))
447 response = urllib.request.urlopen(req)
448 paste_url = response.read()
449
450 return paste_url
451
452
453class RunningBuildTreeView (gtk.TreeView):
454 __gsignals__ = {
455 "button_press_event" : "override"
456 }
457 def __init__ (self, readonly=False, hob=False):
458 gtk.TreeView.__init__ (self)
459 self.readonly = readonly
460
461 # The icon that indicates whether we're building or failed.
462 # add 'hob' flag because there has not only hob to share this code
463 if hob:
464 renderer = HobCellRendererPixbuf ()
465 else:
466 renderer = gtk.CellRendererPixbuf()
467 col = gtk.TreeViewColumn ("Status", renderer)
468 col.add_attribute (renderer, "icon-name", 4)
469 self.append_column (col)
470
471 # The message of the build.
472 # add 'hob' flag because there has not only hob to share this code
473 if hob:
474 self.message_renderer = HobWarpCellRendererText (col_number=1)
475 else:
476 self.message_renderer = gtk.CellRendererText ()
477 self.message_column = gtk.TreeViewColumn ("Message", self.message_renderer, text=3)
478 self.message_column.add_attribute(self.message_renderer, 'background', 5)
479 self.message_renderer.set_property('editable', (not self.readonly))
480 self.append_column (self.message_column)
481
482 def do_button_press_event(self, event):
483 gtk.TreeView.do_button_press_event(self, event)
484
485 if event.button == 3:
486 selection = super(RunningBuildTreeView, self).get_selection()
487 (model, it) = selection.get_selected()
488 if it is not None:
489 can_paste = model.get(it, model.COL_LOG)[0]
490 if can_paste == 'pastebin':
491 # build a simple menu with a pastebin option
492 menu = gtk.Menu()
493 menuitem = gtk.MenuItem("Copy")
494 menu.append(menuitem)
495 menuitem.connect("activate", self.clipboard_handler, (model, it))
496 menuitem.show()
497 menuitem = gtk.MenuItem("Send log to pastebin")
498 menu.append(menuitem)
499 menuitem.connect("activate", self.pastebin_handler, (model, it))
500 menuitem.show()
501 menu.show()
502 menu.popup(None, None, None, event.button, event.time)
503
504 def _add_to_clipboard(self, clipping):
505 """
506 Add the contents of clipping to the system clipboard.
507 """
508 clipboard = gtk.clipboard_get()
509 clipboard.set_text(clipping)
510 clipboard.store()
511
512 def pastebin_handler(self, widget, data):
513 """
514 Send the log data to pastebin, then add the new paste url to the
515 clipboard.
516 """
517 (model, it) = data
518 paste_url = do_pastebin(model.get(it, model.COL_MESSAGE)[0])
519
520 # @todo Provide visual feedback to the user that it is done and that
521 # it worked.
522 print(paste_url)
523
524 self._add_to_clipboard(paste_url)
525
526 def clipboard_handler(self, widget, data):
527 """
528 """
529 (model, it) = data
530 message = model.get(it, model.COL_MESSAGE)[0]
531
532 self._add_to_clipboard(message)
533
534class BuildFailureTreeView(gtk.TreeView):
535
536 def __init__ (self):
537 gtk.TreeView.__init__(self)
538 self.set_rules_hint(False)
539 self.set_headers_visible(False)
540 self.get_selection().set_mode(gtk.SELECTION_SINGLE)
541
542 # The icon that indicates whether we're building or failed.
543 renderer = HobCellRendererPixbuf ()
544 col = gtk.TreeViewColumn ("Status", renderer)
545 col.add_attribute (renderer, "icon-name", RunningBuildModel.COL_ICON)
546 self.append_column (col)
547
548 # The message of the build.
549 self.message_renderer = HobWarpCellRendererText (col_number=1)
550 self.message_column = gtk.TreeViewColumn ("Message", self.message_renderer, text=RunningBuildModel.COL_MESSAGE, background=RunningBuildModel.COL_COLOR)
551 self.append_column (self.message_column)