123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551 |
- #
- # BitBake Graphical GTK User Interface
- #
- # Copyright (C) 2008 Intel Corporation
- #
- # Authored by Rob Bradford <rob@linux.intel.com>
- #
- # This program is free software; you can redistribute it and/or modify
- # it under the terms of the GNU General Public License version 2 as
- # published by the Free Software Foundation.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License along
- # with this program; if not, write to the Free Software Foundation, Inc.,
- # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- import gtk
- import gobject
- import logging
- import time
- import urllib.request, urllib.parse, urllib.error
- import urllib.request, urllib.error, urllib.parse
- import pango
- from bb.ui.crumbs.hobcolor import HobColors
- from bb.ui.crumbs.hobwidget import HobWarpCellRendererText, HobCellRendererPixbuf
- class RunningBuildModel (gtk.TreeStore):
- (COL_LOG, COL_PACKAGE, COL_TASK, COL_MESSAGE, COL_ICON, COL_COLOR, COL_NUM_ACTIVE) = list(range(7))
- def __init__ (self):
- gtk.TreeStore.__init__ (self,
- gobject.TYPE_STRING,
- gobject.TYPE_STRING,
- gobject.TYPE_STRING,
- gobject.TYPE_STRING,
- gobject.TYPE_STRING,
- gobject.TYPE_STRING,
- gobject.TYPE_INT)
- def failure_model_filter(self, model, it):
- color = model.get(it, self.COL_COLOR)[0]
- if not color:
- return False
- if color == HobColors.ERROR or color == HobColors.WARNING:
- return True
- return False
- def failure_model(self):
- model = self.filter_new()
- model.set_visible_func(self.failure_model_filter)
- return model
- def foreach_cell_func(self, model, path, iter, usr_data=None):
- if model.get_value(iter, self.COL_ICON) == "gtk-execute":
- model.set(iter, self.COL_ICON, "")
- def close_task_refresh(self):
- self.foreach(self.foreach_cell_func, None)
- class RunningBuild (gobject.GObject):
- __gsignals__ = {
- 'build-started' : (gobject.SIGNAL_RUN_LAST,
- gobject.TYPE_NONE,
- ()),
- 'build-succeeded' : (gobject.SIGNAL_RUN_LAST,
- gobject.TYPE_NONE,
- ()),
- 'build-failed' : (gobject.SIGNAL_RUN_LAST,
- gobject.TYPE_NONE,
- ()),
- 'build-complete' : (gobject.SIGNAL_RUN_LAST,
- gobject.TYPE_NONE,
- ()),
- 'build-aborted' : (gobject.SIGNAL_RUN_LAST,
- gobject.TYPE_NONE,
- ()),
- 'task-started' : (gobject.SIGNAL_RUN_LAST,
- gobject.TYPE_NONE,
- (gobject.TYPE_PYOBJECT,)),
- 'log-error' : (gobject.SIGNAL_RUN_LAST,
- gobject.TYPE_NONE,
- ()),
- 'log-warning' : (gobject.SIGNAL_RUN_LAST,
- gobject.TYPE_NONE,
- ()),
- 'disk-full' : (gobject.SIGNAL_RUN_LAST,
- gobject.TYPE_NONE,
- ()),
- 'no-provider' : (gobject.SIGNAL_RUN_LAST,
- gobject.TYPE_NONE,
- (gobject.TYPE_PYOBJECT,)),
- 'log' : (gobject.SIGNAL_RUN_LAST,
- gobject.TYPE_NONE,
- (gobject.TYPE_STRING, gobject.TYPE_PYOBJECT,)),
- }
- pids_to_task = {}
- tasks_to_iter = {}
- def __init__ (self, sequential=False):
- gobject.GObject.__init__ (self)
- self.model = RunningBuildModel()
- self.sequential = sequential
- self.buildaborted = False
- def reset (self):
- self.pids_to_task.clear()
- self.tasks_to_iter.clear()
- self.model.clear()
- def handle_event (self, event, pbar=None):
- # Handle an event from the event queue, this may result in updating
- # the model and thus the UI. Or it may be to tell us that the build
- # has finished successfully (or not, as the case may be.)
- parent = None
- pid = 0
- package = None
- task = None
- # If we have a pid attached to this message/event try and get the
- # (package, task) pair for it. If we get that then get the parent iter
- # for the message.
- if hasattr(event, 'pid'):
- pid = event.pid
- if hasattr(event, 'process'):
- pid = event.process
- if pid and pid in self.pids_to_task:
- (package, task) = self.pids_to_task[pid]
- parent = self.tasks_to_iter[(package, task)]
- if(isinstance(event, logging.LogRecord)):
- if event.taskpid == 0 or event.levelno > logging.INFO:
- self.emit("log", "handle", event)
- # FIXME: this is a hack! More info in Yocto #1433
- # http://bugzilla.pokylinux.org/show_bug.cgi?id=1433, temporarily
- # mask the error message as it's not informative for the user.
- if event.msg.startswith("Execution of event handler 'run_buildstats' failed"):
- return
- if (event.levelno < logging.INFO or
- event.msg.startswith("Running task")):
- return # don't add these to the list
- if event.levelno >= logging.ERROR:
- icon = "dialog-error"
- color = HobColors.ERROR
- self.emit("log-error")
- elif event.levelno >= logging.WARNING:
- icon = "dialog-warning"
- color = HobColors.WARNING
- self.emit("log-warning")
- else:
- icon = None
- color = HobColors.OK
- # if we know which package we belong to, we'll append onto its list.
- # otherwise, we'll jump to the top of the master list
- if self.sequential or not parent:
- tree_add = self.model.append
- else:
- tree_add = self.model.prepend
- tree_add(parent,
- (None,
- package,
- task,
- event.getMessage(),
- icon,
- color,
- 0))
- # if there are warnings while processing a package
- # (parent), mark the task with warning color;
- # in case there are errors, the updates will be
- # handled on TaskFailed.
- if color == HobColors.WARNING and parent:
- self.model.set(parent, self.model.COL_COLOR, color)
- if task: #then we have a parent (package), and update it's color
- self.model.set(self.tasks_to_iter[(package, None)], self.model.COL_COLOR, color)
- elif isinstance(event, bb.build.TaskStarted):
- (package, task) = (event._package, event._task)
- # Save out this PID.
- self.pids_to_task[pid] = (package, task)
- # Check if we already have this package in our model. If so then
- # that can be the parent for the task. Otherwise we create a new
- # top level for the package.
- if ((package, None) in self.tasks_to_iter):
- parent = self.tasks_to_iter[(package, None)]
- else:
- if self.sequential:
- add = self.model.append
- else:
- add = self.model.prepend
- parent = add(None, (None,
- package,
- None,
- "Package: %s" % (package),
- None,
- HobColors.OK,
- 0))
- self.tasks_to_iter[(package, None)] = parent
- # Because this parent package now has an active child mark it as
- # such.
- self.model.set(parent, self.model.COL_ICON, "gtk-execute")
- parent_color = self.model.get(parent, self.model.COL_COLOR)[0]
- if parent_color != HobColors.ERROR and parent_color != HobColors.WARNING:
- self.model.set(parent, self.model.COL_COLOR, HobColors.RUNNING)
- # Add an entry in the model for this task
- i = self.model.append (parent, (None,
- package,
- task,
- "Task: %s" % (task),
- "gtk-execute",
- HobColors.RUNNING,
- 0))
- # update the parent's active task count
- num_active = self.model.get(parent, self.model.COL_NUM_ACTIVE)[0] + 1
- self.model.set(parent, self.model.COL_NUM_ACTIVE, num_active)
- # Save out the iter so that we can find it when we have a message
- # that we need to attach to a task.
- self.tasks_to_iter[(package, task)] = i
- elif isinstance(event, bb.build.TaskBase):
- self.emit("log", "info", event._message)
- current = self.tasks_to_iter[(package, task)]
- parent = self.tasks_to_iter[(package, None)]
- # remove this task from the parent's active count
- num_active = self.model.get(parent, self.model.COL_NUM_ACTIVE)[0] - 1
- self.model.set(parent, self.model.COL_NUM_ACTIVE, num_active)
- if isinstance(event, bb.build.TaskFailed):
- # Mark the task and parent as failed
- icon = "dialog-error"
- color = HobColors.ERROR
- logfile = event.logfile
- if logfile and os.path.exists(logfile):
- with open(logfile) as f:
- logdata = f.read()
- self.model.append(current, ('pastebin', None, None, logdata, 'gtk-error', HobColors.OK, 0))
- for i in (current, parent):
- self.model.set(i, self.model.COL_ICON, icon,
- self.model.COL_COLOR, color)
- else:
- # Mark the parent package and the task as inactive,
- # but make sure to preserve error, warnings and active
- # states
- parent_color = self.model.get(parent, self.model.COL_COLOR)[0]
- task_color = self.model.get(current, self.model.COL_COLOR)[0]
- # Mark the task as inactive
- self.model.set(current, self.model.COL_ICON, None)
- if task_color != HobColors.ERROR:
- if task_color == HobColors.WARNING:
- self.model.set(current, self.model.COL_ICON, 'dialog-warning')
- else:
- self.model.set(current, self.model.COL_COLOR, HobColors.OK)
- # Mark the parent as inactive
- if parent_color != HobColors.ERROR:
- if parent_color == HobColors.WARNING:
- self.model.set(parent, self.model.COL_ICON, "dialog-warning")
- else:
- self.model.set(parent, self.model.COL_ICON, None)
- if num_active == 0:
- self.model.set(parent, self.model.COL_COLOR, HobColors.OK)
- # Clear the iters and the pids since when the task goes away the
- # pid will no longer be used for messages
- del self.tasks_to_iter[(package, task)]
- del self.pids_to_task[pid]
- elif isinstance(event, bb.event.BuildStarted):
- self.emit("build-started")
- self.model.prepend(None, (None,
- None,
- None,
- "Build Started (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'),
- None,
- HobColors.OK,
- 0))
- if pbar:
- pbar.update(0, self.progress_total)
- pbar.set_title(bb.event.getName(event))
- elif isinstance(event, bb.event.BuildCompleted):
- failures = int (event._failures)
- self.model.prepend(None, (None,
- None,
- None,
- "Build Completed (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'),
- None,
- HobColors.OK,
- 0))
- # Emit the appropriate signal depending on the number of failures
- if self.buildaborted:
- self.emit ("build-aborted")
- self.buildaborted = False
- elif (failures >= 1):
- self.emit ("build-failed")
- else:
- self.emit ("build-succeeded")
- # Emit a generic "build-complete" signal for things wishing to
- # handle when the build is finished
- self.emit("build-complete")
- # reset the all cell's icon indicator
- self.model.close_task_refresh()
- if pbar:
- pbar.set_text(event.msg)
- elif isinstance(event, bb.event.DiskFull):
- self.buildaborted = True
- self.emit("disk-full")
- elif isinstance(event, bb.command.CommandFailed):
- self.emit("log", "error", "Command execution failed: %s" % (event.error))
- if event.error.startswith("Exited with"):
- # If the command fails with an exit code we're done, emit the
- # generic signal for the UI to notify the user
- self.emit("build-complete")
- # reset the all cell's icon indicator
- self.model.close_task_refresh()
- elif isinstance(event, bb.event.CacheLoadStarted) and pbar:
- pbar.set_title("Loading cache")
- self.progress_total = event.total
- pbar.update(0, self.progress_total)
- elif isinstance(event, bb.event.CacheLoadProgress) and pbar:
- pbar.update(event.current, self.progress_total)
- elif isinstance(event, bb.event.CacheLoadCompleted) and pbar:
- pbar.update(self.progress_total, self.progress_total)
- pbar.hide()
- elif isinstance(event, bb.event.ParseStarted) and pbar:
- if event.total == 0:
- return
- pbar.set_title("Processing recipes")
- self.progress_total = event.total
- pbar.update(0, self.progress_total)
- elif isinstance(event, bb.event.ParseProgress) and pbar:
- pbar.update(event.current, self.progress_total)
- elif isinstance(event, bb.event.ParseCompleted) and pbar:
- pbar.hide()
- #using runqueue events as many as possible to update the progress bar
- elif isinstance(event, bb.runqueue.runQueueTaskFailed):
- self.emit("log", "error", "Task %s (%s) failed with exit code '%s'" % (event.taskid, event.taskstring, event.exitcode))
- elif isinstance(event, bb.runqueue.sceneQueueTaskFailed):
- self.emit("log", "warn", "Setscene task %s (%s) failed with exit code '%s' - real task will be run instead" \
- % (event.taskid, event.taskstring, event.exitcode))
- elif isinstance(event, (bb.runqueue.runQueueTaskStarted, bb.runqueue.sceneQueueTaskStarted)):
- if isinstance(event, bb.runqueue.sceneQueueTaskStarted):
- self.emit("log", "info", "Running setscene task %d of %d (%s)" % \
- (event.stats.completed + event.stats.active + event.stats.failed + 1,
- event.stats.total, event.taskstring))
- else:
- if event.noexec:
- tasktype = 'noexec task'
- else:
- tasktype = 'task'
- self.emit("log", "info", "Running %s %s of %s (ID: %s, %s)" % \
- (tasktype, event.stats.completed + event.stats.active + event.stats.failed + 1,
- event.stats.total, event.taskid, event.taskstring))
- message = {}
- message["eventname"] = bb.event.getName(event)
- num_of_completed = event.stats.completed + event.stats.failed
- message["current"] = num_of_completed
- message["total"] = event.stats.total
- message["title"] = ""
- message["task"] = event.taskstring
- self.emit("task-started", message)
- elif isinstance(event, bb.event.MultipleProviders):
- self.emit("log", "info", "multiple providers are available for %s%s (%s)" \
- % (event._is_runtime and "runtime " or "", event._item, ", ".join(event._candidates)))
- self.emit("log", "info", "consider defining a PREFERRED_PROVIDER entry to match %s" % (event._item))
- elif isinstance(event, bb.event.NoProvider):
- msg = ""
- if event._runtime:
- r = "R"
- else:
- r = ""
- extra = ''
- if not event._reasons:
- if event._close_matches:
- extra = ". Close matches:\n %s" % '\n '.join(event._close_matches)
- if event._dependees:
- msg = "Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)%s\n" % (r, event._item, ", ".join(event._dependees), r, extra)
- else:
- msg = "Nothing %sPROVIDES '%s'%s\n" % (r, event._item, extra)
- if event._reasons:
- for reason in event._reasons:
- msg += ("%s\n" % reason)
- self.emit("no-provider", msg)
- self.emit("log", "error", msg)
- elif isinstance(event, bb.event.LogExecTTY):
- icon = "dialog-warning"
- color = HobColors.WARNING
- if self.sequential or not parent:
- tree_add = self.model.append
- else:
- tree_add = self.model.prepend
- tree_add(parent,
- (None,
- package,
- task,
- event.msg,
- icon,
- color,
- 0))
- else:
- if not isinstance(event, (bb.event.BuildBase,
- bb.event.StampUpdate,
- bb.event.ConfigParsed,
- bb.event.RecipeParsed,
- bb.event.RecipePreFinalise,
- bb.runqueue.runQueueEvent,
- bb.runqueue.runQueueExitWait,
- bb.event.OperationStarted,
- bb.event.OperationCompleted,
- bb.event.OperationProgress)):
- self.emit("log", "error", "Unknown event: %s" % (event.error if hasattr(event, 'error') else 'error'))
- return
- def do_pastebin(text):
- url = 'http://pastebin.com/api_public.php'
- params = {'paste_code': text, 'paste_format': 'text'}
- req = urllib.request.Request(url, urllib.parse.urlencode(params))
- response = urllib.request.urlopen(req)
- paste_url = response.read()
- return paste_url
- class RunningBuildTreeView (gtk.TreeView):
- __gsignals__ = {
- "button_press_event" : "override"
- }
- def __init__ (self, readonly=False, hob=False):
- gtk.TreeView.__init__ (self)
- self.readonly = readonly
- # The icon that indicates whether we're building or failed.
- # add 'hob' flag because there has not only hob to share this code
- if hob:
- renderer = HobCellRendererPixbuf ()
- else:
- renderer = gtk.CellRendererPixbuf()
- col = gtk.TreeViewColumn ("Status", renderer)
- col.add_attribute (renderer, "icon-name", 4)
- self.append_column (col)
- # The message of the build.
- # add 'hob' flag because there has not only hob to share this code
- if hob:
- self.message_renderer = HobWarpCellRendererText (col_number=1)
- else:
- self.message_renderer = gtk.CellRendererText ()
- self.message_column = gtk.TreeViewColumn ("Message", self.message_renderer, text=3)
- self.message_column.add_attribute(self.message_renderer, 'background', 5)
- self.message_renderer.set_property('editable', (not self.readonly))
- self.append_column (self.message_column)
- def do_button_press_event(self, event):
- gtk.TreeView.do_button_press_event(self, event)
- if event.button == 3:
- selection = super(RunningBuildTreeView, self).get_selection()
- (model, it) = selection.get_selected()
- if it is not None:
- can_paste = model.get(it, model.COL_LOG)[0]
- if can_paste == 'pastebin':
- # build a simple menu with a pastebin option
- menu = gtk.Menu()
- menuitem = gtk.MenuItem("Copy")
- menu.append(menuitem)
- menuitem.connect("activate", self.clipboard_handler, (model, it))
- menuitem.show()
- menuitem = gtk.MenuItem("Send log to pastebin")
- menu.append(menuitem)
- menuitem.connect("activate", self.pastebin_handler, (model, it))
- menuitem.show()
- menu.show()
- menu.popup(None, None, None, event.button, event.time)
- def _add_to_clipboard(self, clipping):
- """
- Add the contents of clipping to the system clipboard.
- """
- clipboard = gtk.clipboard_get()
- clipboard.set_text(clipping)
- clipboard.store()
- def pastebin_handler(self, widget, data):
- """
- Send the log data to pastebin, then add the new paste url to the
- clipboard.
- """
- (model, it) = data
- paste_url = do_pastebin(model.get(it, model.COL_MESSAGE)[0])
- # @todo Provide visual feedback to the user that it is done and that
- # it worked.
- print(paste_url)
- self._add_to_clipboard(paste_url)
- def clipboard_handler(self, widget, data):
- """
- """
- (model, it) = data
- message = model.get(it, model.COL_MESSAGE)[0]
- self._add_to_clipboard(message)
- class BuildFailureTreeView(gtk.TreeView):
- def __init__ (self):
- gtk.TreeView.__init__(self)
- self.set_rules_hint(False)
- self.set_headers_visible(False)
- self.get_selection().set_mode(gtk.SELECTION_SINGLE)
- # The icon that indicates whether we're building or failed.
- renderer = HobCellRendererPixbuf ()
- col = gtk.TreeViewColumn ("Status", renderer)
- col.add_attribute (renderer, "icon-name", RunningBuildModel.COL_ICON)
- self.append_column (col)
- # The message of the build.
- self.message_renderer = HobWarpCellRendererText (col_number=1)
- self.message_column = gtk.TreeViewColumn ("Message", self.message_renderer, text=RunningBuildModel.COL_MESSAGE, background=RunningBuildModel.COL_COLOR)
- self.append_column (self.message_column)
|