summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/bb/ui/knotty.py
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/bb/ui/knotty.py')
-rw-r--r--bitbake/lib/bb/ui/knotty.py550
1 files changed, 550 insertions, 0 deletions
diff --git a/bitbake/lib/bb/ui/knotty.py b/bitbake/lib/bb/ui/knotty.py
new file mode 100644
index 0000000000..41f1ba83af
--- /dev/null
+++ b/bitbake/lib/bb/ui/knotty.py
@@ -0,0 +1,550 @@
1#
2# BitBake (No)TTY UI Implementation
3#
4# Handling output to TTYs or files (no TTY)
5#
6# Copyright (C) 2006-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 __future__ import division
22
23import os
24import sys
25import xmlrpclib
26import logging
27import progressbar
28import signal
29import bb.msg
30import time
31import fcntl
32import struct
33import copy
34import atexit
35from bb.ui import uihelper
36
37featureSet = [bb.cooker.CookerFeatures.SEND_SANITYEVENTS]
38
39logger = logging.getLogger("BitBake")
40interactive = sys.stdout.isatty()
41
42class BBProgress(progressbar.ProgressBar):
43 def __init__(self, msg, maxval):
44 self.msg = msg
45 widgets = [progressbar.Percentage(), ' ', progressbar.Bar(), ' ',
46 progressbar.ETA()]
47
48 try:
49 self._resize_default = signal.getsignal(signal.SIGWINCH)
50 except:
51 self._resize_default = None
52 progressbar.ProgressBar.__init__(self, maxval, [self.msg + ": "] + widgets, fd=sys.stdout)
53
54 def _handle_resize(self, signum, frame):
55 progressbar.ProgressBar._handle_resize(self, signum, frame)
56 if self._resize_default:
57 self._resize_default(signum, frame)
58 def finish(self):
59 progressbar.ProgressBar.finish(self)
60 if self._resize_default:
61 signal.signal(signal.SIGWINCH, self._resize_default)
62
63class NonInteractiveProgress(object):
64 fobj = sys.stdout
65
66 def __init__(self, msg, maxval):
67 self.msg = msg
68 self.maxval = maxval
69
70 def start(self):
71 self.fobj.write("%s..." % self.msg)
72 self.fobj.flush()
73 return self
74
75 def update(self, value):
76 pass
77
78 def finish(self):
79 self.fobj.write("done.\n")
80 self.fobj.flush()
81
82def new_progress(msg, maxval):
83 if interactive:
84 return BBProgress(msg, maxval)
85 else:
86 return NonInteractiveProgress(msg, maxval)
87
88def pluralise(singular, plural, qty):
89 if(qty == 1):
90 return singular % qty
91 else:
92 return plural % qty
93
94
95class InteractConsoleLogFilter(logging.Filter):
96 def __init__(self, tf, format):
97 self.tf = tf
98 self.format = format
99
100 def filter(self, record):
101 if record.levelno == self.format.NOTE and (record.msg.startswith("Running") or record.msg.startswith("recipe ")):
102 return False
103 self.tf.clearFooter()
104 return True
105
106class TerminalFilter(object):
107 columns = 80
108
109 def sigwinch_handle(self, signum, frame):
110 self.columns = self.getTerminalColumns()
111 if self._sigwinch_default:
112 self._sigwinch_default(signum, frame)
113
114 def getTerminalColumns(self):
115 def ioctl_GWINSZ(fd):
116 try:
117 cr = struct.unpack('hh', fcntl.ioctl(fd, self.termios.TIOCGWINSZ, '1234'))
118 except:
119 return None
120 return cr
121 cr = ioctl_GWINSZ(sys.stdout.fileno())
122 if not cr:
123 try:
124 fd = os.open(os.ctermid(), os.O_RDONLY)
125 cr = ioctl_GWINSZ(fd)
126 os.close(fd)
127 except:
128 pass
129 if not cr:
130 try:
131 cr = (env['LINES'], env['COLUMNS'])
132 except:
133 cr = (25, 80)
134 return cr[1]
135
136 def __init__(self, main, helper, console, errconsole, format):
137 self.main = main
138 self.helper = helper
139 self.cuu = None
140 self.stdinbackup = None
141 self.interactive = sys.stdout.isatty()
142 self.footer_present = False
143 self.lastpids = []
144
145 if not self.interactive:
146 return
147
148 try:
149 import curses
150 except ImportError:
151 sys.exit("FATAL: The knotty ui could not load the required curses python module.")
152
153 import termios
154 self.curses = curses
155 self.termios = termios
156 try:
157 fd = sys.stdin.fileno()
158 self.stdinbackup = termios.tcgetattr(fd)
159 new = copy.deepcopy(self.stdinbackup)
160 new[3] = new[3] & ~termios.ECHO
161 termios.tcsetattr(fd, termios.TCSADRAIN, new)
162 curses.setupterm()
163 if curses.tigetnum("colors") > 2:
164 format.enable_color()
165 self.ed = curses.tigetstr("ed")
166 if self.ed:
167 self.cuu = curses.tigetstr("cuu")
168 try:
169 self._sigwinch_default = signal.getsignal(signal.SIGWINCH)
170 signal.signal(signal.SIGWINCH, self.sigwinch_handle)
171 except:
172 pass
173 self.columns = self.getTerminalColumns()
174 except:
175 self.cuu = None
176 console.addFilter(InteractConsoleLogFilter(self, format))
177 errconsole.addFilter(InteractConsoleLogFilter(self, format))
178
179 def clearFooter(self):
180 if self.footer_present:
181 lines = self.footer_present
182 sys.stdout.write(self.curses.tparm(self.cuu, lines))
183 sys.stdout.write(self.curses.tparm(self.ed))
184 self.footer_present = False
185
186 def updateFooter(self):
187 if not self.cuu:
188 return
189 activetasks = self.helper.running_tasks
190 failedtasks = self.helper.failed_tasks
191 runningpids = self.helper.running_pids
192 if self.footer_present and (self.lastcount == self.helper.tasknumber_current) and (self.lastpids == runningpids):
193 return
194 if self.footer_present:
195 self.clearFooter()
196 if (not self.helper.tasknumber_total or self.helper.tasknumber_current == self.helper.tasknumber_total) and not len(activetasks):
197 return
198 tasks = []
199 for t in runningpids:
200 tasks.append("%s (pid %s)" % (activetasks[t]["title"], t))
201
202 if self.main.shutdown:
203 content = "Waiting for %s running tasks to finish:" % len(activetasks)
204 elif not len(activetasks):
205 content = "No currently running tasks (%s of %s)" % (self.helper.tasknumber_current, self.helper.tasknumber_total)
206 else:
207 content = "Currently %s running tasks (%s of %s):" % (len(activetasks), self.helper.tasknumber_current, self.helper.tasknumber_total)
208 print(content)
209 lines = 1 + int(len(content) / (self.columns + 1))
210 for tasknum, task in enumerate(tasks):
211 content = "%s: %s" % (tasknum, task)
212 print(content)
213 lines = lines + 1 + int(len(content) / (self.columns + 1))
214 self.footer_present = lines
215 self.lastpids = runningpids[:]
216 self.lastcount = self.helper.tasknumber_current
217
218 def finish(self):
219 if self.stdinbackup:
220 fd = sys.stdin.fileno()
221 self.termios.tcsetattr(fd, self.termios.TCSADRAIN, self.stdinbackup)
222
223def _log_settings_from_server(server):
224 # Get values of variables which control our output
225 includelogs, error = server.runCommand(["getVariable", "BBINCLUDELOGS"])
226 if error:
227 logger.error("Unable to get the value of BBINCLUDELOGS variable: %s" % error)
228 raise BaseException(error)
229 loglines, error = server.runCommand(["getVariable", "BBINCLUDELOGS_LINES"])
230 if error:
231 logger.error("Unable to get the value of BBINCLUDELOGS_LINES variable: %s" % error)
232 raise BaseException(error)
233 consolelogfile, error = server.runCommand(["getVariable", "BB_CONSOLELOG"])
234 if error:
235 logger.error("Unable to get the value of BB_CONSOLELOG variable: %s" % error)
236 raise BaseException(error)
237 return includelogs, loglines, consolelogfile
238
239_evt_list = [ "bb.runqueue.runQueueExitWait", "bb.event.LogExecTTY", "logging.LogRecord",
240 "bb.build.TaskFailed", "bb.build.TaskBase", "bb.event.ParseStarted",
241 "bb.event.ParseProgress", "bb.event.ParseCompleted", "bb.event.CacheLoadStarted",
242 "bb.event.CacheLoadProgress", "bb.event.CacheLoadCompleted", "bb.command.CommandFailed",
243 "bb.command.CommandExit", "bb.command.CommandCompleted", "bb.cooker.CookerExit",
244 "bb.event.MultipleProviders", "bb.event.NoProvider", "bb.runqueue.sceneQueueTaskStarted",
245 "bb.runqueue.runQueueTaskStarted", "bb.runqueue.runQueueTaskFailed", "bb.runqueue.sceneQueueTaskFailed",
246 "bb.event.BuildBase", "bb.build.TaskStarted", "bb.build.TaskSucceeded", "bb.build.TaskFailedSilent"]
247
248def main(server, eventHandler, params, tf = TerminalFilter):
249
250 includelogs, loglines, consolelogfile = _log_settings_from_server(server)
251
252 if sys.stdin.isatty() and sys.stdout.isatty():
253 log_exec_tty = True
254 else:
255 log_exec_tty = False
256
257 helper = uihelper.BBUIHelper()
258
259 console = logging.StreamHandler(sys.stdout)
260 errconsole = logging.StreamHandler(sys.stderr)
261 format_str = "%(levelname)s: %(message)s"
262 format = bb.msg.BBLogFormatter(format_str)
263 bb.msg.addDefaultlogFilter(console, bb.msg.BBLogFilterStdOut)
264 bb.msg.addDefaultlogFilter(errconsole, bb.msg.BBLogFilterStdErr)
265 console.setFormatter(format)
266 errconsole.setFormatter(format)
267 logger.addHandler(console)
268 logger.addHandler(errconsole)
269
270 if params.options.remote_server and params.options.kill_server:
271 server.terminateServer()
272 return
273
274 if consolelogfile and not params.options.show_environment:
275 bb.utils.mkdirhier(os.path.dirname(consolelogfile))
276 conlogformat = bb.msg.BBLogFormatter(format_str)
277 consolelog = logging.FileHandler(consolelogfile)
278 bb.msg.addDefaultlogFilter(consolelog)
279 consolelog.setFormatter(conlogformat)
280 logger.addHandler(consolelog)
281
282 llevel, debug_domains = bb.msg.constructLogOptions()
283 server.runCommand(["setEventMask", server.getEventHandle(), llevel, debug_domains, _evt_list])
284
285 if not params.observe_only:
286 params.updateFromServer(server)
287 cmdline = params.parseActions()
288 if not cmdline:
289 print("Nothing to do. Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.")
290 return 1
291 if 'msg' in cmdline and cmdline['msg']:
292 logger.error(cmdline['msg'])
293 return 1
294
295 ret, error = server.runCommand(cmdline['action'])
296 if error:
297 logger.error("Command '%s' failed: %s" % (cmdline, error))
298 return 1
299 elif ret != True:
300 logger.error("Command '%s' failed: returned %s" % (cmdline, ret))
301 return 1
302
303
304 parseprogress = None
305 cacheprogress = None
306 main.shutdown = 0
307 interrupted = False
308 return_value = 0
309 errors = 0
310 warnings = 0
311 taskfailures = []
312
313 termfilter = tf(main, helper, console, errconsole, format)
314 atexit.register(termfilter.finish)
315
316 while True:
317 try:
318 event = eventHandler.waitEvent(0)
319 if event is None:
320 if main.shutdown > 1:
321 break
322 termfilter.updateFooter()
323 event = eventHandler.waitEvent(0.25)
324 if event is None:
325 continue
326 helper.eventHandler(event)
327 if isinstance(event, bb.runqueue.runQueueExitWait):
328 if not main.shutdown:
329 main.shutdown = 1
330 continue
331 if isinstance(event, bb.event.LogExecTTY):
332 if log_exec_tty:
333 tries = event.retries
334 while tries:
335 print("Trying to run: %s" % event.prog)
336 if os.system(event.prog) == 0:
337 break
338 time.sleep(event.sleep_delay)
339 tries -= 1
340 if tries:
341 continue
342 logger.warn(event.msg)
343 continue
344
345 if isinstance(event, logging.LogRecord):
346 if event.levelno >= format.ERROR:
347 errors = errors + 1
348 return_value = 1
349 elif event.levelno == format.WARNING:
350 warnings = warnings + 1
351 # For "normal" logging conditions, don't show note logs from tasks
352 # but do show them if the user has changed the default log level to
353 # include verbose/debug messages
354 if event.taskpid != 0 and event.levelno <= format.NOTE:
355 continue
356 logger.handle(event)
357 continue
358
359 if isinstance(event, bb.build.TaskFailedSilent):
360 logger.warn("Logfile for failed setscene task is %s" % event.logfile)
361 continue
362 if isinstance(event, bb.build.TaskFailed):
363 return_value = 1
364 logfile = event.logfile
365 if logfile and os.path.exists(logfile):
366 termfilter.clearFooter()
367 bb.error("Logfile of failure stored in: %s" % logfile)
368 if includelogs and not event.errprinted:
369 print("Log data follows:")
370 f = open(logfile, "r")
371 lines = []
372 while True:
373 l = f.readline()
374 if l == '':
375 break
376 l = l.rstrip()
377 if loglines:
378 lines.append(' | %s' % l)
379 if len(lines) > int(loglines):
380 lines.pop(0)
381 else:
382 print('| %s' % l)
383 f.close()
384 if lines:
385 for line in lines:
386 print(line)
387 if isinstance(event, bb.build.TaskBase):
388 logger.info(event._message)
389 continue
390 if isinstance(event, bb.event.ParseStarted):
391 if event.total == 0:
392 continue
393 parseprogress = new_progress("Parsing recipes", event.total).start()
394 continue
395 if isinstance(event, bb.event.ParseProgress):
396 parseprogress.update(event.current)
397 continue
398 if isinstance(event, bb.event.ParseCompleted):
399 if not parseprogress:
400 continue
401
402 parseprogress.finish()
403 print(("Parsing of %d .bb files complete (%d cached, %d parsed). %d targets, %d skipped, %d masked, %d errors."
404 % ( event.total, event.cached, event.parsed, event.virtuals, event.skipped, event.masked, event.errors)))
405 continue
406
407 if isinstance(event, bb.event.CacheLoadStarted):
408 cacheprogress = new_progress("Loading cache", event.total).start()
409 continue
410 if isinstance(event, bb.event.CacheLoadProgress):
411 cacheprogress.update(event.current)
412 continue
413 if isinstance(event, bb.event.CacheLoadCompleted):
414 cacheprogress.finish()
415 print("Loaded %d entries from dependency cache." % event.num_entries)
416 continue
417
418 if isinstance(event, bb.command.CommandFailed):
419 return_value = event.exitcode
420 if event.error:
421 errors = errors + 1
422 logger.error("Command execution failed: %s", event.error)
423 main.shutdown = 2
424 continue
425 if isinstance(event, bb.command.CommandExit):
426 if not return_value:
427 return_value = event.exitcode
428 continue
429 if isinstance(event, (bb.command.CommandCompleted, bb.cooker.CookerExit)):
430 main.shutdown = 2
431 continue
432 if isinstance(event, bb.event.MultipleProviders):
433 logger.info("multiple providers are available for %s%s (%s)", event._is_runtime and "runtime " or "",
434 event._item,
435 ", ".join(event._candidates))
436 logger.info("consider defining a PREFERRED_PROVIDER entry to match %s", event._item)
437 continue
438 if isinstance(event, bb.event.NoProvider):
439 return_value = 1
440 errors = errors + 1
441 if event._runtime:
442 r = "R"
443 else:
444 r = ""
445
446 extra = ''
447 if not event._reasons:
448 if event._close_matches:
449 extra = ". Close matches:\n %s" % '\n '.join(event._close_matches)
450
451 if event._dependees:
452 logger.error("Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)%s", r, event._item, ", ".join(event._dependees), r, extra)
453 else:
454 logger.error("Nothing %sPROVIDES '%s'%s", r, event._item, extra)
455 if event._reasons:
456 for reason in event._reasons:
457 logger.error("%s", reason)
458 continue
459
460 if isinstance(event, bb.runqueue.sceneQueueTaskStarted):
461 logger.info("Running setscene task %d of %d (%s)" % (event.stats.completed + event.stats.active + event.stats.failed + 1, event.stats.total, event.taskstring))
462 continue
463
464 if isinstance(event, bb.runqueue.runQueueTaskStarted):
465 if event.noexec:
466 tasktype = 'noexec task'
467 else:
468 tasktype = 'task'
469 logger.info("Running %s %s of %s (ID: %s, %s)",
470 tasktype,
471 event.stats.completed + event.stats.active +
472 event.stats.failed + 1,
473 event.stats.total, event.taskid, event.taskstring)
474 continue
475
476 if isinstance(event, bb.runqueue.runQueueTaskFailed):
477 taskfailures.append(event.taskstring)
478 logger.error("Task %s (%s) failed with exit code '%s'",
479 event.taskid, event.taskstring, event.exitcode)
480 continue
481
482 if isinstance(event, bb.runqueue.sceneQueueTaskFailed):
483 logger.warn("Setscene task %s (%s) failed with exit code '%s' - real task will be run instead",
484 event.taskid, event.taskstring, event.exitcode)
485 continue
486
487 if isinstance(event, bb.event.DepTreeGenerated):
488 continue
489
490 # ignore
491 if isinstance(event, (bb.event.BuildBase,
492 bb.event.MetadataEvent,
493 bb.event.StampUpdate,
494 bb.event.ConfigParsed,
495 bb.event.RecipeParsed,
496 bb.event.RecipePreFinalise,
497 bb.runqueue.runQueueEvent,
498 bb.event.OperationStarted,
499 bb.event.OperationCompleted,
500 bb.event.OperationProgress,
501 bb.event.DiskFull)):
502 continue
503
504 logger.error("Unknown event: %s", event)
505
506 except EnvironmentError as ioerror:
507 termfilter.clearFooter()
508 # ignore interrupted io
509 if ioerror.args[0] == 4:
510 pass
511 except KeyboardInterrupt:
512 termfilter.clearFooter()
513 if params.observe_only:
514 print("\nKeyboard Interrupt, exiting observer...")
515 main.shutdown = 2
516 if not params.observe_only and main.shutdown == 1:
517 print("\nSecond Keyboard Interrupt, stopping...\n")
518 _, error = server.runCommand(["stateForceShutdown"])
519 if error:
520 logger.error("Unable to cleanly stop: %s" % error)
521 if not params.observe_only and main.shutdown == 0:
522 print("\nKeyboard Interrupt, closing down...\n")
523 interrupted = True
524 _, error = server.runCommand(["stateShutdown"])
525 if error:
526 logger.error("Unable to cleanly shutdown: %s" % error)
527 main.shutdown = main.shutdown + 1
528 pass
529
530 summary = ""
531 if taskfailures:
532 summary += pluralise("\nSummary: %s task failed:",
533 "\nSummary: %s tasks failed:", len(taskfailures))
534 for failure in taskfailures:
535 summary += "\n %s" % failure
536 if warnings:
537 summary += pluralise("\nSummary: There was %s WARNING message shown.",
538 "\nSummary: There were %s WARNING messages shown.", warnings)
539 if return_value and errors:
540 summary += pluralise("\nSummary: There was %s ERROR message shown, returning a non-zero exit code.",
541 "\nSummary: There were %s ERROR messages shown, returning a non-zero exit code.", errors)
542 if summary:
543 print(summary)
544
545 if interrupted:
546 print("Execution was interrupted, returning a non-zero exit code.")
547 if return_value == 0:
548 return_value = 1
549
550 return return_value