]> git.ipfire.org Git - thirdparty/openembedded/openembedded-core-contrib.git/commitdiff
ui/knotty: Add a footer to the build output for interactive terminals as knotty2 UI
authorRichard Purdie <richard.purdie@linuxfoundation.org>
Sat, 26 Nov 2011 13:37:52 +0000 (13:37 +0000)
committerRichard Purdie <richard.purdie@linuxfoundation.org>
Thu, 29 Mar 2012 18:42:29 +0000 (19:42 +0100)
On terminals which support it, add summary information to the end of the
build output about the number of tasks currently running and how many tasks
we've run so far.

This provides a summary at a glace of what the current state of the build is
and what the build is currently doing which is lacking in the current UI.

Also disable echo of characters on stdin since this corrupts the disable,
particularly Crtl+C.

The "waiting for X tasks" code can be merged into this code too since
that is only useful on interactive terminals and this improves the
readability of that output too.

Improvements since v0:

* The tasks are ordered in execution order.
* The display is only updated when the list of tasks changes or there
  is output above the footer.
* Running task x oy y and package messages are supressed from the console

This UI can be accessed with "bitbake -u knotty2".

(From Poky rev: e38b4569648f2916c4370871c79e6a6090eb8bc1)

Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
bin/bitbake
lib/bb/ui/knotty.py
lib/bb/ui/knotty2.py [new file with mode: 0644]

index c06d4e8176ade41d2b07ade5de18037e84ef7110..a90f6c6fd1a2aded9a02957c9d41f849651470f8 100755 (executable)
@@ -69,7 +69,7 @@ def get_ui(config):
         return getattr(module, interface).main
     except AttributeError:
         sys.exit("FATAL: Invalid user interface '%s' specified.\n"
-                 "Valid interfaces: depexp, goggle, ncurses, knotty [default]." % interface)
+                 "Valid interfaces: depexp, goggle, ncurses, hob, knotty [default], knotty2." % interface)
 
 
 # Display bitbake/OE warnings via the BitBake.Warnings logger, ignoring others"""
index 14989d47d9d4e22c90a3edcaff9cae5fc37605aa..f8af4dc5fb47fc6093da401460ac21b08c34a6c4 100644 (file)
@@ -3,7 +3,7 @@
 #
 # Handling output to TTYs or files (no TTY)
 #
-# Copyright (C) 2006-2007 Richard Purdie
+# Copyright (C) 2006-2012 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
@@ -70,7 +70,39 @@ def pluralise(singular, plural, qty):
     else:
         return plural % qty
 
-def main(server, eventHandler):
+class TerminalFilter(object):
+    def __init__(self, main, helper, console, format):
+        self.main = main
+        self.helper = helper
+
+    def clearFooter(self):
+        return
+
+    def updateFooter(self):
+        if not main.shutdown or not self.helper.needUpdate:
+            return
+
+        activetasks = self.helper.running_tasks
+        runningpids = self.helper.running_pids
+
+        if len(runningpids) == 0:
+            return
+
+        tasks = []
+        for t in runningpids:
+            tasks.append("%s (pid %s)" % (activetasks[t]["title"], t))
+
+        if main.shutdown:
+            print("Waiting for %s running tasks to finish:" % len(activetasks))
+        else:
+            print("Currently %s running tasks (%s of %s):" % (len(activetasks), self.helper.tasknumber_current, self.helper.tasknumber_total))
+        for tasknum, task in enumerate(tasks):
+            print("%s: %s" % (tasknum, task))
+
+    def finish(self):
+        return
+
+def main(server, eventHandler, tf = TerminalFilter):
 
     # Get values of variables which control our output
     includelogs = server.runCommand(["getVariable", "BBINCLUDELOGS"])
@@ -106,32 +138,29 @@ def main(server, eventHandler):
         print("XMLRPC Fault getting commandline:\n %s" % x)
         return 1
 
-
     parseprogress = None
     cacheprogress = None
-    shutdown = 0
+    main.shutdown = 0
     interrupted = False
     return_value = 0
     errors = 0
     warnings = 0
     taskfailures = []
+
+    termfilter = tf(main, helper, console, format)
+
     while True:
         try:
+            termfilter.updateFooter()
             event = eventHandler.waitEvent(0.25)
             if event is None:
-                if shutdown > 1:
+                if main.shutdown > 1:
                     break
                 continue
             helper.eventHandler(event)
             if isinstance(event, bb.runqueue.runQueueExitWait):
-                if not shutdown:
-                    shutdown = 1
-            if shutdown and helper.needUpdate:
-                activetasks, failedtasks = helper.getTasks()
-                if activetasks:
-                    print("Waiting for %s active tasks to finish:" % len(activetasks))
-                    for tasknum, task in enumerate(activetasks):
-                        print("%s: %s (pid %s)" % (tasknum, activetasks[task]["title"], task))
+                if not main.shutdown:
+                    main.shutdown = 1
 
             if isinstance(event, logging.LogRecord):
                 if event.levelno >= format.ERROR:
@@ -151,6 +180,7 @@ def main(server, eventHandler):
                 return_value = 1
                 logfile = event.logfile
                 if logfile and os.path.exists(logfile):
+                    termfilter.clearFooter()
                     print("ERROR: Logfile of failure stored in: %s" % logfile)
                     if includelogs and not event.errprinted:
                         print("Log data follows:")
@@ -206,14 +236,14 @@ def main(server, eventHandler):
                 return_value = event.exitcode
                 errors = errors + 1
                 logger.error("Command execution failed: %s", event.error)
-                shutdown = 2
+                main.shutdown = 2
                 continue
             if isinstance(event, bb.command.CommandExit):
                 if not return_value:
                     return_value = event.exitcode
                 continue
             if isinstance(event, (bb.command.CommandCompleted, bb.cooker.CookerExit)):
-                shutdown = 2
+                main.shutdown = 2
                 continue
             if isinstance(event, bb.event.MultipleProviders):
                 logger.info("multiple providers are available for %s%s (%s)", event._is_runtime and "runtime " or "",
@@ -281,18 +311,20 @@ def main(server, eventHandler):
             logger.error("Unknown event: %s", event)
 
         except EnvironmentError as ioerror:
+            termfilter.clearFooter()
             # ignore interrupted io
             if ioerror.args[0] == 4:
                 pass
         except KeyboardInterrupt:
-            if shutdown == 1:
+            termfilter.clearFooter()
+            if main.shutdown == 1:
                 print("\nSecond Keyboard Interrupt, stopping...\n")
                 server.runCommand(["stateStop"])
-            if shutdown == 0:
+            if main.shutdown == 0:
                 interrupted = True
                 print("\nKeyboard Interrupt, closing down...\n")
                 server.runCommand(["stateShutdown"])
-            shutdown = shutdown + 1
+            main.shutdown = main.shutdown + 1
             pass
 
     summary = ""
@@ -315,4 +347,6 @@ def main(server, eventHandler):
         if return_value == 0:
             return_value = 1
 
+    termfilter.finish()
+
     return return_value
diff --git a/lib/bb/ui/knotty2.py b/lib/bb/ui/knotty2.py
new file mode 100644 (file)
index 0000000..aa6a408
--- /dev/null
@@ -0,0 +1,109 @@
+#
+# BitBake (No)TTY UI Implementation (v2)
+#
+# Handling output to TTYs or files (no TTY)
+#
+# Copyright (C) 2012 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.
+
+from bb.ui import knotty
+import logging
+import sys
+logger = logging.getLogger("BitBake")
+
+class InteractConsoleLogFilter(logging.Filter):
+    def __init__(self, tf, format):
+        self.tf = tf
+        self.format = format
+
+    def filter(self, record):
+        if record.levelno == self.format.NOTE and (record.msg.startswith("Running") or record.msg.startswith("package ")):
+            return False
+        self.tf.clearFooter()
+        return True
+
+class TerminalFilter2(object):
+    def __init__(self, main, helper, console, format):
+        self.main = main
+        self.helper = helper
+        self.cuu = None
+        self.stdinbackup = None
+        self.interactive = sys.stdout.isatty()
+        self.footer_present = False
+        self.lastpids = []
+
+        if not self.interactive:
+            return
+
+        import curses
+        import termios
+        import copy
+        self.curses = curses
+        self.termios = termios
+        try:
+            fd = sys.stdin.fileno()
+            self.stdinbackup = termios.tcgetattr(fd)
+            new = copy.deepcopy(self.stdinbackup)
+            new[3] = new[3] & ~termios.ECHO
+            termios.tcsetattr(fd, termios.TCSADRAIN, new)
+            curses.setupterm()
+            self.ed = curses.tigetstr("ed")
+            if self.ed:
+                self.cuu = curses.tigetstr("cuu")
+        except:
+            self.cuu = None
+        console.addFilter(InteractConsoleLogFilter(self, format))
+
+    def clearFooter(self):
+        if self.footer_present:
+            lines = self.footer_present
+            sys.stdout.write(self.curses.tparm(self.cuu, lines))
+            sys.stdout.write(self.curses.tparm(self.ed))
+        self.footer_present = False
+
+    def updateFooter(self):
+        if not self.cuu:
+            return
+        activetasks = self.helper.running_tasks
+        failedtasks = self.helper.failed_tasks
+        runningpids = self.helper.running_pids
+        if self.footer_present and (self.lastpids == runningpids):
+            return
+        if self.footer_present:
+            self.clearFooter()
+        if not activetasks:
+            return
+        lines = 1
+        tasks = []
+        for t in runningpids:
+            tasks.append("%s (pid %s)" % (activetasks[t]["title"], t))
+
+        if self.main.shutdown:
+            print("Waiting for %s running tasks to finish:" % len(activetasks))
+        else:
+            print("Currently %s running tasks (%s of %s):" % (len(activetasks), self.helper.tasknumber_current, self.helper.tasknumber_total))
+        for tasknum, task in enumerate(tasks):
+            print("%s: %s" % (tasknum, task))
+            lines = lines + 1
+        self.footer_present = lines
+        self.lastpids = runningpids[:]
+
+    def finish(self):
+        if self.stdinbackup:
+            fd = sys.stdin.fileno()
+            self.termios.tcsetattr(fd, self.termios.TCSADRAIN, self.stdinbackup)
+
+def main(server, eventHandler):
+    bb.ui.knotty.main(server, eventHandler, TerminalFilter2)