From c30eddb243e7e65f67f656e62848a033cf6f2e5c Mon Sep 17 00:00:00 2001 From: Richard Purdie Date: Tue, 30 Sep 2008 15:08:33 +0000 Subject: Add bitbake-dev to allow ease of testing and development of bitbake trunk git-svn-id: https://svn.o-hand.com/repos/poky/trunk@5337 311d38ba-8fff-0310-9ca6-ca027cbcb966 --- bitbake-dev/lib/bb/ui/__init__.py | 18 ++ bitbake-dev/lib/bb/ui/depexplorer.py | 271 ++++++++++++++++++++++++++++ bitbake-dev/lib/bb/ui/knotty.py | 157 +++++++++++++++++ bitbake-dev/lib/bb/ui/ncurses.py | 333 +++++++++++++++++++++++++++++++++++ bitbake-dev/lib/bb/ui/uievent.py | 127 +++++++++++++ bitbake-dev/lib/bb/ui/uihelper.py | 49 ++++++ 6 files changed, 955 insertions(+) create mode 100644 bitbake-dev/lib/bb/ui/__init__.py create mode 100644 bitbake-dev/lib/bb/ui/depexplorer.py create mode 100644 bitbake-dev/lib/bb/ui/knotty.py create mode 100644 bitbake-dev/lib/bb/ui/ncurses.py create mode 100644 bitbake-dev/lib/bb/ui/uievent.py create mode 100644 bitbake-dev/lib/bb/ui/uihelper.py (limited to 'bitbake-dev/lib/bb/ui') diff --git a/bitbake-dev/lib/bb/ui/__init__.py b/bitbake-dev/lib/bb/ui/__init__.py new file mode 100644 index 000000000..c6a377a8e --- /dev/null +++ b/bitbake-dev/lib/bb/ui/__init__.py @@ -0,0 +1,18 @@ +# +# BitBake UI Implementation +# +# Copyright (C) 2006-2007 Richard Purdie +# +# 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. + diff --git a/bitbake-dev/lib/bb/ui/depexplorer.py b/bitbake-dev/lib/bb/ui/depexplorer.py new file mode 100644 index 000000000..becbb5dd5 --- /dev/null +++ b/bitbake-dev/lib/bb/ui/depexplorer.py @@ -0,0 +1,271 @@ +# +# BitBake Graphical GTK based Dependency Explorer +# +# Copyright (C) 2007 Ross Burton +# Copyright (C) 2007 - 2008 Richard Purdie +# +# 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 gobject +import gtk +import threading + +# Package Model +(COL_PKG_NAME) = (0) + +# Dependency Model +(TYPE_DEP, TYPE_RDEP) = (0, 1) +(COL_DEP_TYPE, COL_DEP_PARENT, COL_DEP_PACKAGE) = (0, 1, 2) + +class PackageDepView(gtk.TreeView): + def __init__(self, model, dep_type, label): + gtk.TreeView.__init__(self) + self.current = None + self.dep_type = dep_type + self.filter_model = model.filter_new() + self.filter_model.set_visible_func(self._filter) + self.set_model(self.filter_model) + #self.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE) + self.append_column(gtk.TreeViewColumn(label, gtk.CellRendererText(), text=COL_DEP_PACKAGE)) + + def _filter(self, model, iter): + (this_type, package) = model.get(iter, COL_DEP_TYPE, COL_DEP_PARENT) + if this_type != self.dep_type: return False + return package == self.current + + def set_current_package(self, package): + self.current = package + self.filter_model.refilter() + +class PackageReverseDepView(gtk.TreeView): + def __init__(self, model, label): + gtk.TreeView.__init__(self) + self.current = None + self.filter_model = model.filter_new() + self.filter_model.set_visible_func(self._filter) + self.set_model(self.filter_model) + self.append_column(gtk.TreeViewColumn(label, gtk.CellRendererText(), text=COL_DEP_PARENT)) + + def _filter(self, model, iter): + package = model.get_value(iter, COL_DEP_PACKAGE) + return package == self.current + + def set_current_package(self, package): + self.current = package + self.filter_model.refilter() + +class DepExplorer(gtk.Window): + def __init__(self): + gtk.Window.__init__(self) + self.set_title("Dependency Explorer") + self.set_default_size(500, 500) + self.connect("delete-event", gtk.main_quit) + + # Create the data models + self.pkg_model = gtk.ListStore(gobject.TYPE_STRING) + self.depends_model = gtk.ListStore(gobject.TYPE_INT, gobject.TYPE_STRING, gobject.TYPE_STRING) + + pane = gtk.HPaned() + pane.set_position(250) + self.add(pane) + + # The master list of packages + scrolled = gtk.ScrolledWindow() + scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scrolled.set_shadow_type(gtk.SHADOW_IN) + self.pkg_treeview = gtk.TreeView(self.pkg_model) + self.pkg_treeview.get_selection().connect("changed", self.on_cursor_changed) + self.pkg_treeview.append_column(gtk.TreeViewColumn("Package", gtk.CellRendererText(), text=COL_PKG_NAME)) + pane.add1(scrolled) + scrolled.add(self.pkg_treeview) + + box = gtk.VBox(homogeneous=True, spacing=4) + + # Runtime Depends + scrolled = gtk.ScrolledWindow() + scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scrolled.set_shadow_type(gtk.SHADOW_IN) + self.rdep_treeview = PackageDepView(self.depends_model, TYPE_RDEP, "Runtime Depends") + self.rdep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE) + scrolled.add(self.rdep_treeview) + box.add(scrolled) + + # Build Depends + scrolled = gtk.ScrolledWindow() + scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scrolled.set_shadow_type(gtk.SHADOW_IN) + self.dep_treeview = PackageDepView(self.depends_model, TYPE_DEP, "Build Depends") + self.dep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE) + scrolled.add(self.dep_treeview) + box.add(scrolled) + pane.add2(box) + + # Reverse Depends + scrolled = gtk.ScrolledWindow() + scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scrolled.set_shadow_type(gtk.SHADOW_IN) + self.revdep_treeview = PackageReverseDepView(self.depends_model, "Reverse Depends") + self.revdep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PARENT) + scrolled.add(self.revdep_treeview) + box.add(scrolled) + pane.add2(box) + + self.show_all() + + def on_package_activated(self, treeview, path, column, data_col): + model = treeview.get_model() + package = model.get_value(model.get_iter(path), data_col) + + pkg_path = [] + def finder(model, path, iter, needle): + package = model.get_value(iter, COL_PKG_NAME) + if package == needle: + pkg_path.append(path) + return True + else: + return False + self.pkg_model.foreach(finder, package) + if pkg_path: + self.pkg_treeview.get_selection().select_path(pkg_path[0]) + self.pkg_treeview.scroll_to_cell(pkg_path[0]) + + def on_cursor_changed(self, selection): + (model, it) = selection.get_selected() + if iter is None: + current_package = None + else: + current_package = model.get_value(it, COL_PKG_NAME) + self.rdep_treeview.set_current_package(current_package) + self.dep_treeview.set_current_package(current_package) + self.revdep_treeview.set_current_package(current_package) + + +def parse(depgraph, pkg_model, depends_model): + + for package in depgraph["pn"]: + pkg_model.set(pkg_model.append(), COL_PKG_NAME, package) + + for package in depgraph["depends"]: + for depend in depgraph["depends"][package]: + depends_model.set (depends_model.append(), + COL_DEP_TYPE, TYPE_DEP, + COL_DEP_PARENT, package, + COL_DEP_PACKAGE, depend) + + for package in depgraph["rdepends-pn"]: + for rdepend in depgraph["rdepends-pn"][package]: + depends_model.set (depends_model.append(), + COL_DEP_TYPE, TYPE_RDEP, + COL_DEP_PARENT, package, + COL_DEP_PACKAGE, rdepend) + +class ProgressBar(gtk.Window): + def __init__(self): + + gtk.Window.__init__(self) + self.set_title("Parsing .bb files, please wait...") + self.set_default_size(500, 0) + self.connect("delete-event", gtk.main_quit) + + self.progress = gtk.ProgressBar() + self.add(self.progress) + self.show_all() + +class gtkthread(threading.Thread): + quit = threading.Event() + def __init__(self, shutdown): + threading.Thread.__init__(self) + self.setDaemon(True) + self.shutdown = shutdown + + def run(self): + gobject.threads_init() + gtk.gdk.threads_init() + gtk.main() + gtkthread.quit.set() + +def init(server, eventHandler): + + try: + cmdline = server.runCommand(["getCmdLineAction"]) + if not cmdline or cmdline[0] != "generateDotGraph": + print "This UI is only compatible with the -g option" + return + ret = server.runCommand(["generateDepTreeEvent", cmdline[1]]) + if ret != True: + print "Couldn't run command! %s" % ret + return + except xmlrpclib.Fault, x: + print "XMLRPC Fault getting commandline:\n %s" % x + return + + shutdown = 0 + + gtkgui = gtkthread(shutdown) + gtkgui.start() + + gtk.gdk.threads_enter() + pbar = ProgressBar() + dep = DepExplorer() + gtk.gdk.threads_leave() + + while True: + try: + event = eventHandler.waitEvent(0.25) + if gtkthread.quit.isSet(): + break + + if event is None: + continue + if event[0].startswith('bb.event.ParseProgress'): + x = event[1]['sofar'] + y = event[1]['total'] + if x == y: + print("\nParsing finished. %d cached, %d parsed, %d skipped, %d masked, %d errors." + % ( event[1]['cached'], event[1]['parsed'], event[1]['skipped'], event[1]['masked'], event[1]['errors'])) + pbar.hide() + gtk.gdk.threads_enter() + pbar.progress.set_fraction(float(x)/float(y)) + pbar.progress.set_text("%d/%d (%2d %%)" % (x, y, x*100/y)) + gtk.gdk.threads_leave() + continue + + if event[0] == "bb.event.DepTreeGenerated": + gtk.gdk.threads_enter() + parse(event[1]['_depgraph'], dep.pkg_model, dep.depends_model) + gtk.gdk.threads_leave() + + if event[0] == 'bb.command.CookerCommandCompleted': + continue + if event[0] == 'bb.command.CookerCommandFailed': + print "Command execution failed: %s" % event[1]['error'] + break + if event[0] == 'bb.cooker.CookerExit': + break + + continue + + except KeyboardInterrupt: + if shutdown == 2: + print "\nThird Keyboard Interrupt, exit.\n" + break + if shutdown == 1: + print "\nSecond Keyboard Interrupt, stopping...\n" + server.runCommand(["stateStop"]) + if shutdown == 0: + print "\nKeyboard Interrupt, closing down...\n" + server.runCommand(["stateShutdown"]) + shutdown = shutdown + 1 + pass + diff --git a/bitbake-dev/lib/bb/ui/knotty.py b/bitbake-dev/lib/bb/ui/knotty.py new file mode 100644 index 000000000..9e8966030 --- /dev/null +++ b/bitbake-dev/lib/bb/ui/knotty.py @@ -0,0 +1,157 @@ +# +# BitBake (No)TTY UI Implementation +# +# Handling output to TTYs or files (no TTY) +# +# Copyright (C) 2006-2007 Richard Purdie +# +# 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 os +import bb +from bb import cooker + +import sys +import time +import itertools +import xmlrpclib + +parsespin = itertools.cycle( r'|/-\\' ) + +def init(server, eventHandler): + + # Get values of variables which control our output + includelogs = server.runCommand(["readVariable", "BBINCLUDELOGS"]) + loglines = server.runCommand(["readVariable", "BBINCLUDELOGS_LINES"]) + + try: + cmdline = server.runCommand(["getCmdLineAction"]) + #print cmdline + if not cmdline: + return 1 + ret = server.runCommand(cmdline) + if ret != True: + print "Couldn't get default commandline! %s" % ret + return 1 + except xmlrpclib.Fault, x: + print "XMLRPC Fault getting commandline:\n %s" % x + return 1 + + shutdown = 0 + return_value = 0 + while True: + try: + event = eventHandler.waitEvent(0.25) + if event is None: + continue + #print event + if event[0].startswith('bb.event.Pkg'): + print "NOTE: %s" % event[1]['_message'] + continue + if event[0].startswith('bb.msg.MsgPlain'): + print event[1]['_message'] + continue + if event[0].startswith('bb.msg.MsgDebug'): + print 'DEBUG: ' + event[1]['_message'] + continue + if event[0].startswith('bb.msg.MsgNote'): + print 'NOTE: ' + event[1]['_message'] + continue + if event[0].startswith('bb.msg.MsgWarn'): + print 'WARNING: ' + event[1]['_message'] + continue + if event[0].startswith('bb.msg.MsgError'): + return_value = 1 + print 'ERROR: ' + event[1]['_message'] + continue + if event[0].startswith('bb.build.TaskFailed'): + return_value = 1 + logfile = event[1]['logfile'] + if logfile: + print "ERROR: Logfile of failure stored in %s." % logfile + if includelogs: + print "Log data follows:" + f = open(logfile, "r") + lines = [] + while True: + l = f.readline() + if l == '': + break + l = l.rstrip() + if loglines: + lines.append(' | %s' % l) + if len(lines) > int(loglines): + lines.pop(0) + else: + print '| %s' % l + f.close() + if lines: + for line in lines: + print line + if event[0].startswith('bb.build.Task'): + print "NOTE: %s" % event[1]['_message'] + continue + if event[0].startswith('bb.event.ParseProgress'): + x = event[1]['sofar'] + y = event[1]['total'] + if os.isatty(sys.stdout.fileno()): + sys.stdout.write("\rNOTE: Handling BitBake files: %s (%04d/%04d) [%2d %%]" % ( parsespin.next(), x, y, x*100/y ) ) + sys.stdout.flush() + else: + if x == 1: + sys.stdout.write("Parsing .bb files, please wait...") + sys.stdout.flush() + if x == y: + sys.stdout.write("done.") + sys.stdout.flush() + if x == y: + print("\nParsing finished. %d cached, %d parsed, %d skipped, %d masked, %d errors." + % ( event[1]['cached'], event[1]['parsed'], event[1]['skipped'], event[1]['masked'], event[1]['errors'])) + continue + + if event[0] == 'bb.command.CookerCommandCompleted': + break + if event[0] == 'bb.command.CookerCommandFailed': + return_value = 1 + print "Command execution failed: %s" % event[1]['error'] + break + if event[0] == 'bb.cooker.CookerExit': + break + + # ignore + if event[0].startswith('bb.event.BuildStarted'): + continue + if event[0].startswith('bb.event.BuildCompleted'): + continue + if event[0].startswith('bb.event.MultipleProviders'): + continue + if event[0].startswith('bb.runqueue.runQueue'): + continue + if event[0].startswith('bb.event.StampUpdate'): + continue + print "Unknown Event: %s" % event + + except KeyboardInterrupt: + if shutdown == 2: + print "\nThird Keyboard Interrupt, exit.\n" + break + if shutdown == 1: + print "\nSecond Keyboard Interrupt, stopping...\n" + server.runCommand(["stateStop"]) + if shutdown == 0: + print "\nKeyboard Interrupt, closing down...\n" + server.runCommand(["stateShutdown"]) + shutdown = shutdown + 1 + pass + return return_value diff --git a/bitbake-dev/lib/bb/ui/ncurses.py b/bitbake-dev/lib/bb/ui/ncurses.py new file mode 100644 index 000000000..1476baa61 --- /dev/null +++ b/bitbake-dev/lib/bb/ui/ncurses.py @@ -0,0 +1,333 @@ +# +# BitBake Curses UI Implementation +# +# Implements an ncurses frontend for the BitBake utility. +# +# Copyright (C) 2006 Michael 'Mickey' Lauer +# Copyright (C) 2006-2007 Richard Purdie +# +# 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. + +""" + We have the following windows: + + 1.) Main Window: Shows what we are ultimately building and how far we are. Includes status bar + 2.) Thread Activity Window: Shows one status line for every concurrent bitbake thread. + 3.) Command Line Window: Contains an interactive command line where you can interact w/ Bitbake. + + Basic window layout is like that: + + |---------------------------------------------------------| + |
| | + | | 0: foo do_compile complete| + | Building Gtk+-2.6.10 | 1: bar do_patch complete | + | Status: 60% | ... | + | | ... | + | | ... | + |---------------------------------------------------------| + | | + |>>> which virtual/kernel | + |openzaurus-kernel | + |>>> _ | + |---------------------------------------------------------| + +""" + +import os, sys, curses, time, random, threading, itertools, time +from curses.textpad import Textbox +import bb +from bb import ui +from bb.ui import uihelper + +parsespin = itertools.cycle( r'|/-\\' ) + +X = 0 +Y = 1 +WIDTH = 2 +HEIGHT = 3 + +MAXSTATUSLENGTH = 32 + +class NCursesUI: + """ + NCurses UI Class + """ + class Window: + """Base Window Class""" + def __init__( self, x, y, width, height, fg=curses.COLOR_BLACK, bg=curses.COLOR_WHITE ): + self.win = curses.newwin( height, width, y, x ) + self.dimensions = ( x, y, width, height ) + """ + if curses.has_colors(): + color = 1 + curses.init_pair( color, fg, bg ) + self.win.bkgdset( ord(' '), curses.color_pair(color) ) + else: + self.win.bkgdset( ord(' '), curses.A_BOLD ) + """ + self.erase() + self.setScrolling() + self.win.noutrefresh() + + def erase( self ): + self.win.erase() + + def setScrolling( self, b = True ): + self.win.scrollok( b ) + self.win.idlok( b ) + + def setBoxed( self ): + self.boxed = True + self.win.box() + self.win.noutrefresh() + + def setText( self, x, y, text, *args ): + self.win.addstr( y, x, text, *args ) + self.win.noutrefresh() + + def appendText( self, text, *args ): + self.win.addstr( text, *args ) + self.win.noutrefresh() + + def drawHline( self, y ): + self.win.hline( y, 0, curses.ACS_HLINE, self.dimensions[WIDTH] ) + self.win.noutrefresh() + + class DecoratedWindow( Window ): + """Base class for windows with a box and a title bar""" + def __init__( self, title, x, y, width, height, fg=curses.COLOR_BLACK, bg=curses.COLOR_WHITE ): + NCursesUI.Window.__init__( self, x+1, y+3, width-2, height-4, fg, bg ) + self.decoration = NCursesUI.Window( x, y, width, height, fg, bg ) + self.decoration.setBoxed() + self.decoration.win.hline( 2, 1, curses.ACS_HLINE, width-2 ) + self.setTitle( title ) + + def setTitle( self, title ): + self.decoration.setText( 1, 1, title.center( self.dimensions[WIDTH]-2 ), curses.A_BOLD ) + + #-------------------------------------------------------------------------# +# class TitleWindow( Window ): + #-------------------------------------------------------------------------# +# """Title Window""" +# def __init__( self, x, y, width, height ): +# NCursesUI.Window.__init__( self, x, y, width, height ) +# version = bb.__version__ +# title = "BitBake %s" % version +# credit = "(C) 2003-2007 Team BitBake" +# #self.win.hline( 2, 1, curses.ACS_HLINE, width-2 ) +# self.win.border() +# self.setText( 1, 1, title.center( self.dimensions[WIDTH]-2 ), curses.A_BOLD ) +# self.setText( 1, 2, credit.center( self.dimensions[WIDTH]-2 ), curses.A_BOLD ) + + #-------------------------------------------------------------------------# + class ThreadActivityWindow( DecoratedWindow ): + #-------------------------------------------------------------------------# + """Thread Activity Window""" + def __init__( self, x, y, width, height ): + NCursesUI.DecoratedWindow.__init__( self, "Thread Activity", x, y, width, height ) + + def setStatus( self, thread, text ): + line = "%02d: %s" % ( thread, text ) + width = self.dimensions[WIDTH] + if ( len(line) > width ): + line = line[:width-3] + "..." + else: + line = line.ljust( width ) + self.setText( 0, thread, line ) + + #-------------------------------------------------------------------------# + class MainWindow( DecoratedWindow ): + #-------------------------------------------------------------------------# + """Main Window""" + def __init__( self, x, y, width, height ): + self.StatusPosition = width - MAXSTATUSLENGTH + NCursesUI.DecoratedWindow.__init__( self, None, x, y, width, height ) + curses.nl() + + def setTitle( self, title ): + title = "BitBake %s" % bb.__version__ + self.decoration.setText( 2, 1, title, curses.A_BOLD ) + self.decoration.setText( self.StatusPosition - 8, 1, "Status:", curses.A_BOLD ) + + def setStatus(self, status): + while len(status) < MAXSTATUSLENGTH: + status = status + " " + self.decoration.setText( self.StatusPosition, 1, status, curses.A_BOLD ) + + + #-------------------------------------------------------------------------# + class ShellOutputWindow( DecoratedWindow ): + #-------------------------------------------------------------------------# + """Interactive Command Line Output""" + def __init__( self, x, y, width, height ): + NCursesUI.DecoratedWindow.__init__( self, "Command Line Window", x, y, width, height ) + + #-------------------------------------------------------------------------# + class ShellInputWindow( Window ): + #-------------------------------------------------------------------------# + """Interactive Command Line Input""" + def __init__( self, x, y, width, height ): + NCursesUI.Window.__init__( self, x, y, width, height ) + +# self.textbox = Textbox( self.win ) +# t = threading.Thread() +# t.run = self.textbox.edit +# t.start() + + #-------------------------------------------------------------------------# + def main(self, stdscr, server, eventHandler): + #-------------------------------------------------------------------------# + height, width = stdscr.getmaxyx() + + # for now split it like that: + # MAIN_y + THREAD_y = 2/3 screen at the top + # MAIN_x = 2/3 left, THREAD_y = 1/3 right + # CLI_y = 1/3 of screen at the bottom + # CLI_x = full + + main_left = 0 + main_top = 0 + main_height = ( height / 3 * 2 ) + main_width = ( width / 3 ) * 2 + clo_left = main_left + clo_top = main_top + main_height + clo_height = height - main_height - main_top - 1 + clo_width = width + cli_left = main_left + cli_top = clo_top + clo_height + cli_height = 1 + cli_width = width + thread_left = main_left + main_width + thread_top = main_top + thread_height = main_height + thread_width = width - main_width + + #tw = self.TitleWindow( 0, 0, width, main_top ) + mw = self.MainWindow( main_left, main_top, main_width, main_height ) + taw = self.ThreadActivityWindow( thread_left, thread_top, thread_width, thread_height ) + clo = self.ShellOutputWindow( clo_left, clo_top, clo_width, clo_height ) + cli = self.ShellInputWindow( cli_left, cli_top, cli_width, cli_height ) + cli.setText( 0, 0, "BB>" ) + + mw.setStatus("Idle") + + helper = uihelper.BBUIHelper() + shutdown = 0 + + try: + cmdline = server.runCommand(["getCmdLineAction"]) + if not cmdline: + return + ret = server.runCommand(cmdline) + if ret != True: + print "Couldn't get default commandlind! %s" % ret + return + except xmlrpclib.Fault, x: + print "XMLRPC Fault getting commandline:\n %s" % x + return + + exitflag = False + while not exitflag: + try: + event = eventHandler.waitEvent(0.25) + if not event: + continue + helper.eventHandler(event) + #mw.appendText("%s\n" % event[0]) + if event[0].startswith('bb.event.Pkg'): + mw.appendText("NOTE: %s\n" % event[1]['_message']) + if event[0].startswith('bb.build.Task'): + mw.appendText("NOTE: %s\n" % event[1]['_message']) + if event[0].startswith('bb.msg.MsgDebug'): + mw.appendText('DEBUG: ' + event[1]['_message'] + '\n') + if event[0].startswith('bb.msg.MsgNote'): + mw.appendText('NOTE: ' + event[1]['_message'] + '\n') + if event[0].startswith('bb.msg.MsgWarn'): + mw.appendText('WARNING: ' + event[1]['_message'] + '\n') + if event[0].startswith('bb.msg.MsgError'): + mw.appendText('ERROR: ' + event[1]['_message'] + '\n') + if event[0].startswith('bb.msg.MsgFatal'): + mw.appendText('FATAL: ' + event[1]['_message'] + '\n') + if event[0].startswith('bb.event.ParseProgress'): + x = event[1]['sofar'] + y = event[1]['total'] + if x == y: + mw.setStatus("Idle") + mw.appendText("Parsing finished. %d cached, %d parsed, %d skipped, %d masked." + % ( event[1]['cached'], event[1]['parsed'], event[1]['skipped'], event[1]['masked'] )) + else: + mw.setStatus("Parsing: %s (%04d/%04d) [%2d %%]" % ( parsespin.next(), x, y, x*100/y ) ) +# if event[0].startswith('bb.build.TaskFailed'): +# if event[1]['logfile']: +# if data.getVar("BBINCLUDELOGS", d): +# bb.msg.error(bb.msg.domain.Build, "log data follows (%s)" % logfile) +# number_of_lines = data.getVar("BBINCLUDELOGS_LINES", d) +# if number_of_lines: +# os.system('tail -n%s %s' % (number_of_lines, logfile)) +# else: +# f = open(logfile, "r") +# while True: +# l = f.readline() +# if l == '': +# break +# l = l.rstrip() +# print '| %s' % l +# f.close() +# else: +# bb.msg.error(bb.msg.domain.Build, "see log in %s" % logfile) + + if event[0] == 'bb.command.CookerCommandCompleted': + exitflag = True + if event[0] == 'bb.command.CookerCommandFailed': + mw.appendText("Command execution failed: %s" % event[1]['error']) + time.sleep(2) + exitflag = True + if event[0] == 'bb.cooker.CookerExit': + exitflag = True + + if helper.needUpdate: + activetasks, failedtasks = helper.getTasks() + taw.erase() + taw.setText(0, 0, "") + if activetasks: + taw.appendText("Active Tasks:\n") + for task in activetasks: + taw.appendText(task) + if failedtasks: + taw.appendText("Failed Tasks:\n") + for task in failedtasks: + taw.appendText(task) + + curses.doupdate() + except KeyboardInterrupt: + if shutdown == 2: + mw.appendText("Third Keyboard Interrupt, exit.\n") + exitflag = True + if shutdown == 1: + mw.appendText("Second Keyboard Interrupt, stopping...\n") + server.runCommand(["stateStop"]) + if shutdown == 0: + mw.appendText("Keyboard Interrupt, closing down...\n") + server.runCommand(["stateShutdown"]) + shutdown = shutdown + 1 + pass + +def init(server, eventHandler): + ui = NCursesUI() + try: + curses.wrapper(ui.main, server, eventHandler) + except: + import traceback + traceback.print_exc() + diff --git a/bitbake-dev/lib/bb/ui/uievent.py b/bitbake-dev/lib/bb/ui/uievent.py new file mode 100644 index 000000000..9d724d7fc --- /dev/null +++ b/bitbake-dev/lib/bb/ui/uievent.py @@ -0,0 +1,127 @@ +# ex:ts=4:sw=4:sts=4:et +# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- +# +# Copyright (C) 2006 - 2007 Michael 'Mickey' Lauer +# Copyright (C) 2006 - 2007 Richard Purdie +# +# 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. + + +""" +Use this class to fork off a thread to recieve event callbacks from the bitbake +server and queue them for the UI to process. This process must be used to avoid +client/server deadlocks. +""" + +import sys, socket, threading +from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler + +class BBUIEventQueue: + def __init__(self, BBServer): + + self.eventQueue = [] + self.eventQueueLock = threading.Lock() + self.eventQueueNotify = threading.Event() + + self.BBServer = BBServer + + self.t = threading.Thread() + self.t.setDaemon(True) + self.t.run = self.startCallbackHandler + self.t.start() + + def getEvent(self): + + self.eventQueueLock.acquire() + + if len(self.eventQueue) == 0: + self.eventQueueLock.release() + return None + + item = self.eventQueue.pop(0) + + if len(self.eventQueue) == 0: + self.eventQueueNotify.clear() + + self.eventQueueLock.release() + + return item + + def waitEvent(self, delay): + self.eventQueueNotify.wait(delay) + return self.getEvent() + + def queue_event(self, event): + + self.eventQueueLock.acquire() + self.eventQueue.append(event) + self.eventQueueNotify.set() + self.eventQueueLock.release() + + def startCallbackHandler(self): + + server = UIXMLRPCServer() + self.host, self.port = server.socket.getsockname() + + server.register_function( self.system_quit, "event.quit" ) + server.register_function( self.queue_event, "event.send" ) + server.socket.settimeout(1) + + self.EventHandle = self.BBServer.registerEventHandler(self.host, self.port) + + self.server = server + while not server.quit: + server.handle_request() + server.server_close() + + def system_quit( self ): + """ + Shut down the callback thread + """ + try: + self.BBServer.unregisterEventHandler(self.EventHandle) + except: + pass + self.server.quit = True + +class UIXMLRPCServer (SimpleXMLRPCServer): + + def __init__( self, interface = ("localhost", 0) ): + self.quit = False + SimpleXMLRPCServer.__init__( self, + interface, + requestHandler=SimpleXMLRPCRequestHandler, + logRequests=False, allow_none=True) + + def get_request(self): + while not self.quit: + try: + sock, addr = self.socket.accept() + sock.settimeout(1) + return (sock, addr) + except socket.timeout: + pass + return (None,None) + + def close_request(self, request): + if request is None: + return + SimpleXMLRPCServer.close_request(self, request) + + def process_request(self, request, client_address): + if request is None: + return + SimpleXMLRPCServer.process_request(self, request, client_address) + + diff --git a/bitbake-dev/lib/bb/ui/uihelper.py b/bitbake-dev/lib/bb/ui/uihelper.py new file mode 100644 index 000000000..246844c9d --- /dev/null +++ b/bitbake-dev/lib/bb/ui/uihelper.py @@ -0,0 +1,49 @@ +# ex:ts=4:sw=4:sts=4:et +# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- +# +# Copyright (C) 2006 - 2007 Michael 'Mickey' Lauer +# Copyright (C) 2006 - 2007 Richard Purdie +# +# 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. + +class BBUIHelper: + def __init__(self): + self.needUpdate = False + self.running_tasks = {} + self.failed_tasks = {} + + def eventHandler(self, event): + if event[0].startswith('bb.build.TaskStarted'): + self.running_tasks["%s %s\n" % (event[1]['_package'], event[1]['_task'])] = "" + self.needUpdate = True + if event[0].startswith('bb.build.TaskSucceeded'): + del self.running_tasks["%s %s\n" % (event[1]['_package'], event[1]['_task'])] + self.needUpdate = True + if event[0].startswith('bb.build.TaskFailed'): + del self.running_tasks["%s %s\n" % (event[1]['_package'], event[1]['_task'])] + self.failed_tasks["%s %s\n" % (event[1]['_package'], event[1]['_task'])] = "" + self.needUpdate = True + + # Add runqueue event handling + #if event[0].startswith('bb.runqueue.runQueueTaskCompleted'): + # a = 1 + #if event[0].startswith('bb.runqueue.runQueueTaskStarted'): + # a = 1 + #if event[0].startswith('bb.runqueue.runQueueTaskFailed'): + # a = 1 + #if event[0].startswith('bb.runqueue.runQueueExitWait'): + # a = 1 + + def getTasks(self): + return (self.running_tasks, self.failed_tasks) -- cgit v1.2.3