74211e0372
If NoProvider event is received, we will handle it in runningbuild module and send notification to Hob instance, avoiding stepping into the final page with no image built out. This fixes [YOCTO #2249] (Bitbake rev: 067bc46a0fbc542fef1fcaa406ad3737a4c5a55a) Signed-off-by: Dongxiao Xu <dongxiao.xu@intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
454 lines
18 KiB
Python
454 lines
18 KiB
Python
|
|
#
|
|
# 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
|
|
import urllib2
|
|
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) = 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:
|
|
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,
|
|
()),
|
|
'task-started' : (gobject.SIGNAL_RUN_LAST,
|
|
gobject.TYPE_NONE,
|
|
(gobject.TYPE_PYOBJECT,)),
|
|
'log-error' : (gobject.SIGNAL_RUN_LAST,
|
|
gobject.TYPE_NONE,
|
|
()),
|
|
'no-provider' : (gobject.SIGNAL_RUN_LAST,
|
|
gobject.TYPE_NONE,
|
|
(gobject.TYPE_PYOBJECT,)),
|
|
}
|
|
pids_to_task = {}
|
|
tasks_to_iter = {}
|
|
|
|
def __init__ (self, sequential=False):
|
|
gobject.GObject.__init__ (self)
|
|
self.model = RunningBuildModel()
|
|
self.sequential = sequential
|
|
|
|
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)):
|
|
# 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
|
|
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))
|
|
|
|
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.
|
|
# @todo if parent is already in error, don't mark it green
|
|
self.model.set(parent, self.model.COL_ICON, "gtk-execute",
|
|
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):
|
|
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:
|
|
icon = None
|
|
color = HobColors.OK
|
|
|
|
# Mark the task as inactive
|
|
self.model.set(current, self.model.COL_ICON, icon,
|
|
self.model.COL_COLOR, color)
|
|
|
|
# Mark the parent package as inactive, but make sure to
|
|
# preserve error and active states
|
|
i = self.tasks_to_iter[(package, None)]
|
|
if self.model.get(parent, self.model.COL_ICON) != 'dialog-error':
|
|
self.model.set(parent, self.model.COL_ICON, icon)
|
|
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 (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.command.CommandFailed):
|
|
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.runQueueTaskStarted, bb.runqueue.sceneQueueTaskStarted)):
|
|
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.NoProvider):
|
|
msg = ""
|
|
if event._runtime:
|
|
r = "R"
|
|
else:
|
|
r = ""
|
|
if event._dependees:
|
|
msg = "Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)\n" % (r, event._item, ", ".join(event._dependees), r)
|
|
else:
|
|
msg = "Nothing %sPROVIDES '%s'\n" % (r, event._item)
|
|
if event._reasons:
|
|
for reason in event._reasons:
|
|
msg += ("%s\n" % reason)
|
|
self.emit("no-provider", msg)
|
|
|
|
return
|
|
|
|
|
|
def do_pastebin(text):
|
|
url = 'http://pastebin.com/api_public.php'
|
|
params = {'paste_code': text, 'paste_format': 'text'}
|
|
|
|
req = urllib2.Request(url, urllib.urlencode(params))
|
|
response = urllib2.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)
|