mirror of
https://git.yoctoproject.org/poky
synced 2026-04-26 09:32:14 +02:00
The various alternative UIs have been updated to once again be functional with the latest bitbake internals. Each of the UIs still have much room for functional improvement. In particular, they have been updated to: - interact with the new process based server - handle the current set of events and notifications fired from the server and its associated subsystems (Bitbake rev: b947e7aa405966262c0614cae02e7978ec637095) Signed-off-by: Bob Foerster <robert@erafx.com> Signed-off-by: Richard Purdie <rpurdie@linux.intel.com>
311 lines
12 KiB
Python
311 lines
12 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
|
|
|
|
class Colors(object):
|
|
OK = "#ffffff"
|
|
RUNNING = "#aaffaa"
|
|
WARNING ="#f88017"
|
|
ERROR = "#ffaaaa"
|
|
|
|
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)
|
|
|
|
class RunningBuild (gobject.GObject):
|
|
__gsignals__ = {
|
|
'build-succeeded' : (gobject.SIGNAL_RUN_LAST,
|
|
gobject.TYPE_NONE,
|
|
()),
|
|
'build-failed' : (gobject.SIGNAL_RUN_LAST,
|
|
gobject.TYPE_NONE,
|
|
())
|
|
}
|
|
pids_to_task = {}
|
|
tasks_to_iter = {}
|
|
|
|
def __init__ (self):
|
|
gobject.GObject.__init__ (self)
|
|
self.model = RunningBuildModel()
|
|
|
|
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.msg.startswith ("Running task")):
|
|
return # don't add these to the list
|
|
|
|
if event.levelno >= logging.ERROR:
|
|
icon = "dialog-error"
|
|
color = Colors.ERROR
|
|
elif event.levelno >= logging.WARNING:
|
|
icon = "dialog-warning"
|
|
color = Colors.WARNING
|
|
else:
|
|
icon = None
|
|
color = Colors.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 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:
|
|
parent = self.model.prepend(None, (None,
|
|
package,
|
|
None,
|
|
"Package: %s" % (package),
|
|
None,
|
|
Colors.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, Colors.RUNNING)
|
|
|
|
# Add an entry in the model for this task
|
|
i = self.model.append (parent, (None,
|
|
package,
|
|
task,
|
|
"Task: %s" % (task),
|
|
"gtk-execute",
|
|
Colors.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 = Colors.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', Colors.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 = Colors.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, Colors.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.model.prepend(None, (None,
|
|
None,
|
|
None,
|
|
"Build Started (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'),
|
|
None,
|
|
Colors.OK,
|
|
0))
|
|
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,
|
|
Colors.OK,
|
|
0))
|
|
|
|
# Emit the appropriate signal depending on the number of failures
|
|
if (failures >= 1):
|
|
self.emit ("build-failed")
|
|
else:
|
|
self.emit ("build-succeeded")
|
|
|
|
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)
|
|
|
|
elif isinstance(event, bb.event.ParseStarted) and pbar:
|
|
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()
|
|
|
|
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):
|
|
gtk.TreeView.__init__ (self)
|
|
|
|
# The icon that indicates whether we're building or failed.
|
|
renderer = gtk.CellRendererPixbuf ()
|
|
col = gtk.TreeViewColumn ("Status", renderer)
|
|
col.add_attribute (renderer, "icon-name", 4)
|
|
self.append_column (col)
|
|
|
|
# The message of the build.
|
|
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', 5)
|
|
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, iter) = selection.get_selected()
|
|
if iter is not None:
|
|
can_paste = model.get(iter, model.COL_LOG)[0]
|
|
if can_paste == 'pastebin':
|
|
# build a simple menu with a pastebin option
|
|
menu = gtk.Menu()
|
|
menuitem = gtk.MenuItem("Send log to pastebin")
|
|
menu.append(menuitem)
|
|
menuitem.connect("activate", self.pastebin_handler, (model, iter))
|
|
menuitem.show()
|
|
menu.show()
|
|
menu.popup(None, None, None, event.button, event.time)
|
|
|
|
def pastebin_handler(self, widget, data):
|
|
"""
|
|
Send the log data to pastebin, then add the new paste url to the
|
|
clipboard.
|
|
"""
|
|
(model, iter) = data
|
|
paste_url = do_pastebin(model.get(iter, model.COL_MESSAGE)[0])
|
|
|
|
# @todo Provide visual feedback to the user that it is done and that
|
|
# it worked.
|
|
print paste_url
|
|
|
|
clipboard = gtk.clipboard_get()
|
|
clipboard.set_text(paste_url)
|
|
clipboard.store() |