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