summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRichard Purdie <richard.purdie@linuxfoundation.org>2011-11-26 13:37:52 +0000
committerRichard Purdie <richard.purdie@linuxfoundation.org>2012-03-29 19:44:01 +0100
commiteeb0529e138fa95db8fc6ed74bcaee87804fcb6c (patch)
tree45353a0b77672a788682066b6300b5ee5c457595
parent5b84c902ebbd0b034c6f328eb44054e2f92556d3 (diff)
downloadpoky-eeb0529e138fa95db8fc6ed74bcaee87804fcb6c.tar.gz
ui/knotty: Add a footer to the build output for interactive terminals as knotty2 UI
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) (Bitbake rev: 156189c799d2bb1f69bdaa04b5cd718fe7881425) Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
-rwxr-xr-xbitbake/bin/bitbake2
-rw-r--r--bitbake/lib/bb/ui/knotty.py70
-rw-r--r--bitbake/lib/bb/ui/knotty2.py109
3 files changed, 162 insertions, 19 deletions
diff --git a/bitbake/bin/bitbake b/bitbake/bin/bitbake
index c06d4e8176..a90f6c6fd1 100755
--- a/bitbake/bin/bitbake
+++ b/bitbake/bin/bitbake
@@ -69,7 +69,7 @@ def get_ui(config):
69 return getattr(module, interface).main 69 return getattr(module, interface).main
70 except AttributeError: 70 except AttributeError:
71 sys.exit("FATAL: Invalid user interface '%s' specified.\n" 71 sys.exit("FATAL: Invalid user interface '%s' specified.\n"
72 "Valid interfaces: depexp, goggle, ncurses, knotty [default]." % interface) 72 "Valid interfaces: depexp, goggle, ncurses, hob, knotty [default], knotty2." % interface)
73 73
74 74
75# Display bitbake/OE warnings via the BitBake.Warnings logger, ignoring others""" 75# Display bitbake/OE warnings via the BitBake.Warnings logger, ignoring others"""
diff --git a/bitbake/lib/bb/ui/knotty.py b/bitbake/lib/bb/ui/knotty.py
index 14989d47d9..f8af4dc5fb 100644
--- a/bitbake/lib/bb/ui/knotty.py
+++ b/bitbake/lib/bb/ui/knotty.py
@@ -3,7 +3,7 @@
3# 3#
4# Handling output to TTYs or files (no TTY) 4# Handling output to TTYs or files (no TTY)
5# 5#
6# Copyright (C) 2006-2007 Richard Purdie 6# Copyright (C) 2006-2012 Richard Purdie
7# 7#
8# This program is free software; you can redistribute it and/or modify 8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as 9# it under the terms of the GNU General Public License version 2 as
@@ -70,7 +70,39 @@ def pluralise(singular, plural, qty):
70 else: 70 else:
71 return plural % qty 71 return plural % qty
72 72
73def main(server, eventHandler): 73class TerminalFilter(object):
74 def __init__(self, main, helper, console, format):
75 self.main = main
76 self.helper = helper
77
78 def clearFooter(self):
79 return
80
81 def updateFooter(self):
82 if not main.shutdown or not self.helper.needUpdate:
83 return
84
85 activetasks = self.helper.running_tasks
86 runningpids = self.helper.running_pids
87
88 if len(runningpids) == 0:
89 return
90
91 tasks = []
92 for t in runningpids:
93 tasks.append("%s (pid %s)" % (activetasks[t]["title"], t))
94
95 if main.shutdown:
96 print("Waiting for %s running tasks to finish:" % len(activetasks))
97 else:
98 print("Currently %s running tasks (%s of %s):" % (len(activetasks), self.helper.tasknumber_current, self.helper.tasknumber_total))
99 for tasknum, task in enumerate(tasks):
100 print("%s: %s" % (tasknum, task))
101
102 def finish(self):
103 return
104
105def main(server, eventHandler, tf = TerminalFilter):
74 106
75 # Get values of variables which control our output 107 # Get values of variables which control our output
76 includelogs = server.runCommand(["getVariable", "BBINCLUDELOGS"]) 108 includelogs = server.runCommand(["getVariable", "BBINCLUDELOGS"])
@@ -106,32 +138,29 @@ def main(server, eventHandler):
106 print("XMLRPC Fault getting commandline:\n %s" % x) 138 print("XMLRPC Fault getting commandline:\n %s" % x)
107 return 1 139 return 1
108 140
109
110 parseprogress = None 141 parseprogress = None
111 cacheprogress = None 142 cacheprogress = None
112 shutdown = 0 143 main.shutdown = 0
113 interrupted = False 144 interrupted = False
114 return_value = 0 145 return_value = 0
115 errors = 0 146 errors = 0
116 warnings = 0 147 warnings = 0
117 taskfailures = [] 148 taskfailures = []
149
150 termfilter = tf(main, helper, console, format)
151
118 while True: 152 while True:
119 try: 153 try:
154 termfilter.updateFooter()
120 event = eventHandler.waitEvent(0.25) 155 event = eventHandler.waitEvent(0.25)
121 if event is None: 156 if event is None:
122 if shutdown > 1: 157 if main.shutdown > 1:
123 break 158 break
124 continue 159 continue
125 helper.eventHandler(event) 160 helper.eventHandler(event)
126 if isinstance(event, bb.runqueue.runQueueExitWait): 161 if isinstance(event, bb.runqueue.runQueueExitWait):
127 if not shutdown: 162 if not main.shutdown:
128 shutdown = 1 163 main.shutdown = 1
129 if shutdown and helper.needUpdate:
130 activetasks, failedtasks = helper.getTasks()
131 if activetasks:
132 print("Waiting for %s active tasks to finish:" % len(activetasks))
133 for tasknum, task in enumerate(activetasks):
134 print("%s: %s (pid %s)" % (tasknum, activetasks[task]["title"], task))
135 164
136 if isinstance(event, logging.LogRecord): 165 if isinstance(event, logging.LogRecord):
137 if event.levelno >= format.ERROR: 166 if event.levelno >= format.ERROR:
@@ -151,6 +180,7 @@ def main(server, eventHandler):
151 return_value = 1 180 return_value = 1
152 logfile = event.logfile 181 logfile = event.logfile
153 if logfile and os.path.exists(logfile): 182 if logfile and os.path.exists(logfile):
183 termfilter.clearFooter()
154 print("ERROR: Logfile of failure stored in: %s" % logfile) 184 print("ERROR: Logfile of failure stored in: %s" % logfile)
155 if includelogs and not event.errprinted: 185 if includelogs and not event.errprinted:
156 print("Log data follows:") 186 print("Log data follows:")
@@ -206,14 +236,14 @@ def main(server, eventHandler):
206 return_value = event.exitcode 236 return_value = event.exitcode
207 errors = errors + 1 237 errors = errors + 1
208 logger.error("Command execution failed: %s", event.error) 238 logger.error("Command execution failed: %s", event.error)
209 shutdown = 2 239 main.shutdown = 2
210 continue 240 continue
211 if isinstance(event, bb.command.CommandExit): 241 if isinstance(event, bb.command.CommandExit):
212 if not return_value: 242 if not return_value:
213 return_value = event.exitcode 243 return_value = event.exitcode
214 continue 244 continue
215 if isinstance(event, (bb.command.CommandCompleted, bb.cooker.CookerExit)): 245 if isinstance(event, (bb.command.CommandCompleted, bb.cooker.CookerExit)):
216 shutdown = 2 246 main.shutdown = 2
217 continue 247 continue
218 if isinstance(event, bb.event.MultipleProviders): 248 if isinstance(event, bb.event.MultipleProviders):
219 logger.info("multiple providers are available for %s%s (%s)", event._is_runtime and "runtime " or "", 249 logger.info("multiple providers are available for %s%s (%s)", event._is_runtime and "runtime " or "",
@@ -281,18 +311,20 @@ def main(server, eventHandler):
281 logger.error("Unknown event: %s", event) 311 logger.error("Unknown event: %s", event)
282 312
283 except EnvironmentError as ioerror: 313 except EnvironmentError as ioerror:
314 termfilter.clearFooter()
284 # ignore interrupted io 315 # ignore interrupted io
285 if ioerror.args[0] == 4: 316 if ioerror.args[0] == 4:
286 pass 317 pass
287 except KeyboardInterrupt: 318 except KeyboardInterrupt:
288 if shutdown == 1: 319 termfilter.clearFooter()
320 if main.shutdown == 1:
289 print("\nSecond Keyboard Interrupt, stopping...\n") 321 print("\nSecond Keyboard Interrupt, stopping...\n")
290 server.runCommand(["stateStop"]) 322 server.runCommand(["stateStop"])
291 if shutdown == 0: 323 if main.shutdown == 0:
292 interrupted = True 324 interrupted = True
293 print("\nKeyboard Interrupt, closing down...\n") 325 print("\nKeyboard Interrupt, closing down...\n")
294 server.runCommand(["stateShutdown"]) 326 server.runCommand(["stateShutdown"])
295 shutdown = shutdown + 1 327 main.shutdown = main.shutdown + 1
296 pass 328 pass
297 329
298 summary = "" 330 summary = ""
@@ -315,4 +347,6 @@ def main(server, eventHandler):
315 if return_value == 0: 347 if return_value == 0:
316 return_value = 1 348 return_value = 1
317 349
350 termfilter.finish()
351
318 return return_value 352 return return_value
diff --git a/bitbake/lib/bb/ui/knotty2.py b/bitbake/lib/bb/ui/knotty2.py
new file mode 100644
index 0000000000..aa6a4080e2
--- /dev/null
+++ b/bitbake/lib/bb/ui/knotty2.py
@@ -0,0 +1,109 @@
1#
2# BitBake (No)TTY UI Implementation (v2)
3#
4# Handling output to TTYs or files (no TTY)
5#
6# Copyright (C) 2012 Richard Purdie
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21from bb.ui import knotty
22import logging
23import sys
24logger = logging.getLogger("BitBake")
25
26class InteractConsoleLogFilter(logging.Filter):
27 def __init__(self, tf, format):
28 self.tf = tf
29 self.format = format
30
31 def filter(self, record):
32 if record.levelno == self.format.NOTE and (record.msg.startswith("Running") or record.msg.startswith("package ")):
33 return False
34 self.tf.clearFooter()
35 return True
36
37class TerminalFilter2(object):
38 def __init__(self, main, helper, console, format):
39 self.main = main
40 self.helper = helper
41 self.cuu = None
42 self.stdinbackup = None
43 self.interactive = sys.stdout.isatty()
44 self.footer_present = False
45 self.lastpids = []
46
47 if not self.interactive:
48 return
49
50 import curses
51 import termios
52 import copy
53 self.curses = curses
54 self.termios = termios
55 try:
56 fd = sys.stdin.fileno()
57 self.stdinbackup = termios.tcgetattr(fd)
58 new = copy.deepcopy(self.stdinbackup)
59 new[3] = new[3] & ~termios.ECHO
60 termios.tcsetattr(fd, termios.TCSADRAIN, new)
61 curses.setupterm()
62 self.ed = curses.tigetstr("ed")
63 if self.ed:
64 self.cuu = curses.tigetstr("cuu")
65 except:
66 self.cuu = None
67 console.addFilter(InteractConsoleLogFilter(self, format))
68
69 def clearFooter(self):
70 if self.footer_present:
71 lines = self.footer_present
72 sys.stdout.write(self.curses.tparm(self.cuu, lines))
73 sys.stdout.write(self.curses.tparm(self.ed))
74 self.footer_present = False
75
76 def updateFooter(self):
77 if not self.cuu:
78 return
79 activetasks = self.helper.running_tasks
80 failedtasks = self.helper.failed_tasks
81 runningpids = self.helper.running_pids
82 if self.footer_present and (self.lastpids == runningpids):
83 return
84 if self.footer_present:
85 self.clearFooter()
86 if not activetasks:
87 return
88 lines = 1
89 tasks = []
90 for t in runningpids:
91 tasks.append("%s (pid %s)" % (activetasks[t]["title"], t))
92
93 if self.main.shutdown:
94 print("Waiting for %s running tasks to finish:" % len(activetasks))
95 else:
96 print("Currently %s running tasks (%s of %s):" % (len(activetasks), self.helper.tasknumber_current, self.helper.tasknumber_total))
97 for tasknum, task in enumerate(tasks):
98 print("%s: %s" % (tasknum, task))
99 lines = lines + 1
100 self.footer_present = lines
101 self.lastpids = runningpids[:]
102
103 def finish(self):
104 if self.stdinbackup:
105 fd = sys.stdin.fileno()
106 self.termios.tcsetattr(fd, self.termios.TCSADRAIN, self.stdinbackup)
107
108def main(server, eventHandler):
109 bb.ui.knotty.main(server, eventHandler, TerminalFilter2)