# # BitBake (No)TTY UI Implementation # # Handling output to TTYs or files (no TTY) # # Copyright (C) 2006-2012 Richard Purdie # # SPDX-License-Identifier: GPL-2.0-only # from __future__ import division import os import sys import logging import progressbar import signal import bb.msg import time import fcntl import struct import copy import atexit from itertools import groupby from bb.ui import uihelper featureSet = [bb.cooker.CookerFeatures.SEND_SANITYEVENTS, bb.cooker.CookerFeatures.BASEDATASTORE_TRACKING] logger = logging.getLogger("BitBake") interactive = sys.stdout.isatty() class BBProgress(progressbar.ProgressBar): def __init__(self, msg, maxval, widgets=None, extrapos=-1, resize_handler=None): self.msg = msg self.extrapos = extrapos if not widgets: widgets = [': ', progressbar.Percentage(), ' ', progressbar.Bar(), ' ', progressbar.ETA()] self.extrapos = 5 if resize_handler: self._resize_default = resize_handler else: self._resize_default = signal.getsignal(signal.SIGWINCH) progressbar.ProgressBar.__init__(self, maxval, [self.msg] + widgets, fd=sys.stdout) def _handle_resize(self, signum=None, frame=None): progressbar.ProgressBar._handle_resize(self, signum, frame) if self._resize_default: self._resize_default(signum, frame) def finish(self): progressbar.ProgressBar.finish(self) if self._resize_default: signal.signal(signal.SIGWINCH, self._resize_default) def setmessage(self, msg): self.msg = msg self.widgets[0] = msg def setextra(self, extra): if self.extrapos > -1: if extra: extrastr = str(extra) if extrastr[0] != ' ': extrastr = ' ' + extrastr else: extrastr = '' self.widgets[self.extrapos] = extrastr def _need_update(self): # We always want the bar to print when update() is called return True class NonInteractiveProgress(object): fobj = sys.stdout def __init__(self, msg, maxval): self.msg = msg self.maxval = maxval self.finished = False def start(self, update=True): self.fobj.write("%s..." % self.msg) self.fobj.flush() return self def update(self, value): pass def finish(self): if self.finished: return self.fobj.write("done.\n") self.fobj.flush() self.finished = True def new_progress(msg, maxval): if interactive: return BBProgress(msg, maxval) else: return NonInteractiveProgress(msg, maxval) def pluralise(singular, plural, qty): if(qty == 1): return singular % qty else: return plural % qty class InteractConsoleLogFilter(logging.Filter): def __init__(self, tf): self.tf = tf def filter(self, record): if record.levelno == bb.msg.BBLogFormatter.NOTE and (record.msg.startswith("Running") or record.msg.startswith("recipe ")): return False self.tf.clearFooter() return True class TerminalFilter(object): rows = 25 columns = 80 def sigwinch_handle(self, signum, frame): self.rows, self.columns = self.getTerminalColumns() if self._sigwinch_default: self._sigwinch_default(signum, frame) def getTerminalColumns(self): def ioctl_GWINSZ(fd): try: cr = struct.unpack('hh', fcntl.ioctl(fd, self.termios.TIOCGWINSZ, '1234')) except: return None return cr cr = ioctl_GWINSZ(sys.stdout.fileno()) if not cr: try: fd = os.open(os.ctermid(), os.O_RDONLY) cr = ioctl_GWINSZ(fd) os.close(fd) except: pass if not cr: try: cr = (os.environ['LINES'], os.environ['COLUMNS']) except: cr = (25, 80) return cr def __init__(self, main, helper, handlers, quiet): self.main = main self.helper = helper self.cuu = None self.stdinbackup = None self.interactive = sys.stdout.isatty() self.footer_present = False self.lastpids = [] self.lasttime = None self.quiet = quiet if not self.interactive: return try: import curses except ImportError: sys.exit("FATAL: The knotty ui could not load the required curses python module.") import termios 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() if curses.tigetnum("colors") > 2 and os.environ.get('NO_COLOR', '') == '': for h in handlers: try: h.formatter.enable_color() except AttributeError: pass self.ed = curses.tigetstr("ed") if self.ed: self.cuu = curses.tigetstr("cuu") try: self._sigwinch_default = signal.getsignal(signal.SIGWINCH) signal.signal(signal.SIGWINCH, self.sigwinch_handle) except: pass self.rows, self.columns = self.getTerminalColumns() except: self.cuu = None if not self.cuu: self.interactive = False bb.note("Unable to use interactive mode for this terminal, using fallback") return for h in handlers: h.addFilter(InteractConsoleLogFilter(self)) self.main_progress = None def clearFooter(self): if self.footer_present: lines = self.footer_present sys.stdout.buffer.write(self.curses.tparm(self.cuu, lines)) sys.stdout.buffer.write(self.curses.tparm(self.ed)) sys.stdout.flush() self.footer_present = False def elapsed(self, sec): hrs = int(sec / 3600.0) sec -= hrs * 3600 min = int(sec / 60.0) sec -= min * 60 if hrs > 0: return "%dh%dm%ds" % (hrs, min, sec) elif min > 0: return "%dm%ds" % (min, sec) else: return "%ds" % (sec) def keepAlive(self, t): if not self.cuu: print("Bitbake still alive (no events for %ds). Active tasks:" % t) for t in self.helper.running_tasks: print(t) sys.stdout.flush() def updateFooter(self): if not self.cuu: return activetasks = self.helper.running_tasks failedtasks = self.helper.failed_tasks runningpids = self.helper.running_pids currenttime = time.time() if not self.lasttime or (currenttime - self.lasttime > 5): self.helper.needUpdate = True self.lasttime = currenttime if self.footer_present and not self.helper.needUpdate: return self.helper.needUpdate = False if self.footer_present: self.clearFooter() if (not self.helper.tasknumber_total or self.helper.tasknumber_current == self.helper.tasknumber_total) and not len(activetasks): return tasks = [] for t in runningpids: start_time = activetasks[t].get("starttime", None) if start_time: msg = "%s - %s (pid %s)" % (activetasks[t]["title"], self.elapsed(currenttime - start_time), activetasks[t]["pid"]) else: msg = "%s (pid %s)" % (activetasks[t]["title"], activetasks[t]["pid"]) progress = activetasks[t].get("progress", None) if progress is not None: pbar = activetasks[t].get("progressbar", None) rate = activetasks[t].get("rate", None) if not pbar or pbar.bouncing != (progress < 0): if progress < 0: pbar = BBProgress("0: %s" % msg, 100, widgets=[' ', progressbar.BouncingSlider(), ''], extrapos=3, resize_handler=self.sigwinch_handle) pbar.bouncing = True else: pbar = BBProgress("0: %s" % msg, 100, widgets=[' ', progressbar.Percentage(), ' ', progressbar.Bar(), ''], extrapos=5, resize_handler=self.sigwinch_handle) pbar.bouncing = False activetasks[t]["progressbar"] = pbar tasks.append((pbar, msg, progress, rate, start_time)) else: tasks.append(msg) if self.main.shutdown: content = pluralise("Waiting for %s running task to finish", "Waiting for %s running tasks to finish", len(activetasks)) if not self.quiet: content += ':' print(content) else: scene_tasks = "%s of %s" % (self.helper.setscene_current, self.helper.setscene_total) cur_tasks = "%s of %s" % (self.helper.tasknumber_current, self.helper.tasknumber_total) content = '' if not self.quiet: msg = "Setscene tasks: %s" % scene_tasks content += msg + "\n" print(msg) if self.quiet: msg = "Running tasks (%s, %s)" % (scene_tasks, cur_tasks) elif not len(activetasks): msg = "No currently running tasks (%s)" % cur_tasks else: msg = "Currently %2s running tasks (%s)" % (len(activetasks), cur_tasks) maxtask = self.helper.tasknumber_total if not self.main_progress or self.main_progress.maxval != maxtask: widgets = [' ', progressbar.Percentage(), ' ', progressbar.Bar()] self.main_progress = BBProgress("Running tasks", maxtask, widgets=widgets, resize_handler=self.sigwinch_handle) self.main_progress.start(False) self.main_progress.setmessage(msg) progress = max(0, self.helper.tasknumber_current - 1) content += self.main_progress.update(progress) print('') lines = self.getlines(content) if not self.quiet: for tasknum, task in enumerate(tasks[:(self.rows - 1 - lines)]): if isinstance(task, tuple): pbar, msg, progress, rate, start_time = task if not pbar.start_time: pbar.start(False) if start_time: pbar.start_time = start_time pbar.setmessage('%s: %s' % (tasknum, msg)) pbar.setextra(rate) if progress > -1: content = pbar.update(progress) else: content = pbar.update(1) print('') else: content = "%s: %s" % (tasknum, task) print(content) lines = lines + self.getlines(content) self.footer_present = lines self.lastpids = runningpids[:] self.lastcount = self.helper.tasknumber_current def getlines(self, content): lines = 0 for line in content.split("\n"): lines = lines + 1 + int(len(line) / (self.columns + 1)) return lines def finish(self): if self.stdinbackup: fd = sys.stdin.fileno() self.termios.tcsetattr(fd, self.termios.TCSADRAIN, self.stdinbackup) def print_event_log(event, includelogs, loglines, termfilter): # FIXME refactor this out further logfile = event.logfile if logfile and os.path.exists(logfile): termfilter.clearFooter() bb.error("Logfile of failure stored in: %s" % logfile) if includelogs and not event.errprinted: 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) def _log_settings_from_server(server, observe_only): # Get values of variables which control our output includelogs, error = server.runCommand(["getVariable", "BBINCLUDELOGS"]) if error: logger.error("Unable to get the value of BBINCLUDELOGS variable: %s" % error) raise BaseException(error) loglines, error = server.runCommand(["getVariable", "BBINCLUDELOGS_LINES"]) if error: logger.error("Unable to get the value of BBINCLUDELOGS_LINES variable: %s" % error) raise BaseException(error) if observe_only: cmd = 'getVariable' else: cmd = 'getSetVariable' consolelogfile, error = server.runCommand([cmd, "BB_CONSOLELOG"]) if error: logger.error("Unable to get the value of BB_CONSOLELOG variable: %s" % error) raise BaseException(error) logconfigfile, error = server.runCommand([cmd, "BB_LOGCONFIG"]) if error: logger.error("Unable to get the value of BB_LOGCONFIG variable: %s" % error) raise BaseException(error) return includelogs, loglines, consolelogfile, logconfigfile _evt_list = [ "bb.runqueue.runQueueExitWait", "bb.event.LogExecTTY", "logging.LogRecord", "bb.build.TaskFailed", "bb.build.TaskBase", "bb.event.ParseStarted", "bb.event.ParseProgress", "bb.event.ParseCompleted", "bb.event.CacheLoadStarted", "bb.event.CacheLoadProgress", "bb.event.CacheLoadCompleted", "bb.command.CommandFailed", "bb.command.CommandExit", "bb.command.CommandCompleted", "bb.cooker.CookerExit", "bb.event.MultipleProviders", "bb.event.NoProvider", "bb.runqueue.sceneQueueTaskStarted", "bb.runqueue.runQueueTaskStarted", "bb.runqueue.runQueueTaskFailed", "bb.runqueue.sceneQueueTaskFailed", "bb.event.BuildBase", "bb.build.TaskStarted", "bb.build.TaskSucceeded", "bb.build.TaskFailedSilent", "bb.build.TaskProgress", "bb.event.ProcessStarted", "bb.event.ProcessProgress", "bb.event.ProcessFinished"] def drain_events_errorhandling(eventHandler): # We don't have logging setup, we do need to show any events we see before exiting event = True logger = bb.msg.logger_create('bitbake', sys.stdout) while event: event = eventHandler.waitEvent(0) if isinstance(event, logging.LogRecord): logger.handle(event) def main(server, eventHandler, params, tf = TerminalFilter): try: if not params.observe_only: params.updateToServer(server, os.environ.copy()) includelogs, loglines, consolelogfile, logconfigfile = _log_settings_from_server(server, params.observe_only) loglevel, _ = bb.msg.constructLogOptions() except bb.BBHandledException: drain_events_errorhandling(eventHandler) return 1 except Exception as e: # bitbake-server comms failure early_logger = bb.msg.logger_create('bitbake', sys.stdout) early_logger.fatal("Attempting to set server environment: %s", e) return 1 if params.options.quiet == 0: console_loglevel = loglevel elif params.options.quiet > 2: console_loglevel = bb.msg.BBLogFormatter.ERROR else: console_loglevel = bb.msg.BBLogFormatter.WARNING logconfig = { "version": 1, "handlers": { "BitBake.console": { "class": "logging.StreamHandler", "formatter": "BitBake.consoleFormatter", "level": console_loglevel, "stream": "ext://sys.stdout", "filters": ["BitBake.stdoutFilter"], ".": { "is_console": True, }, }, "BitBake.errconsole": { "class": "logging.StreamHandler", "formatter": "BitBake.consoleFormatter", "level": loglevel, "stream": "ext://sys.stderr", "filters": ["BitBake.stderrFilter"], ".": { "is_console": True, }, }, # This handler can be used if specific loggers should print on # the console at a lower severity than the default. It will # display any messages sent to it that are lower than then # BitBake.console logging level (so as to prevent duplication of # messages). Nothing is attached to this handler by default "BitBake.verbconsole": { "class": "logging.StreamHandler", "formatter": "BitBake.consoleFormatter", "level": 1, "stream": "ext://sys.stdout", "filters": ["BitBake.verbconsoleFilter"], ".": { "is_console": True, }, }, }, "formatters": { # This format instance will get color output enabled by the # terminal "BitBake.consoleFormatter" : { "()": "bb.msg.BBLogFormatter", "format": "%(levelname)s: %(message)s" }, # The file log requires a separate instance so that it doesn't get # color enabled "BitBake.logfileFormatter": { "()": "bb.msg.BBLogFormatter", "format": "%(levelname)s: %(message)s" } }, "filters": { "BitBake.stdoutFilter": { "()": "bb.msg.LogFilterLTLevel", "level": "ERROR" }, "BitBake.stderrFilter": { "()": "bb.msg.LogFilterGEQLevel", "level": "ERROR" }, "BitBake.verbconsoleFilter": { "()": "bb.msg.LogFilterLTLevel", "level": console_loglevel }, }, "loggers": { "BitBake": { "level": loglevel, "handlers": ["BitBake.console", "BitBake.errconsole"], } }, "disable_existing_loggers": False } # Enable the console log file if enabled if consolelogfile and not params.options.show_environment and not params.options.show_versions: logconfig = bb.msg.mergeLoggingConfig(logconfig, { "version": 1, "handlers" : { "BitBake.consolelog": { "class": "logging.FileHandler", "formatter": "BitBake.logfileFormatter", "level": loglevel, "filename": consolelogfile, }, # Just like verbconsole, anything sent here will go to the # log file, unless it would go to BitBake.consolelog "BitBake.verbconsolelog" : { "class": "logging.FileHandler", "formatter": "BitBake.logfileFormatter", "level": 1, "filename": consolelogfile, "filters": ["BitBake.verbconsolelogFilter"], }, }, "filters": { "BitBake.verbconsolelogFilter": { "()": "bb.msg.LogFilterLTLevel", "level": loglevel, }, }, "loggers": { "BitBake": { "handlers": ["BitBake.consolelog"], }, # Other interesting things that we want to keep an eye on # in the log files in case someone has an issue, but not # necessarily show to the user on the console "BitBake.SigGen.HashEquiv": { "level": "VERBOSE", "handlers": ["BitBake.verbconsolelog"], }, "BitBake.RunQueue.HashEquiv": { "level": "VERBOSE", "handlers": ["BitBake.verbconsolelog"], } } }) bb.utils.mkdirhier(os.path.dirname(consolelogfile)) loglink = os.path.join(os.path.dirname(consolelogfile), 'console-latest.log') bb.utils.remove(loglink) try: os.symlink(os.path.basename(consolelogfile), loglink) except OSError: pass # Add the logging domains specified by the user on the command line for (domainarg, iterator) in groupby(params.debug_domains): dlevel = len(tuple(iterator)) l = logconfig["loggers"].setdefault("BitBake.%s" % domainarg, {}) l["level"] = logging.DEBUG - dlevel + 1 l.setdefault("handlers", []).extend(["BitBake.verbconsole"]) conf = bb.msg.setLoggingConfig(logconfig, logconfigfile) if sys.stdin.isatty() and sys.stdout.isatty(): log_exec_tty = True else: log_exec_tty = False helper = uihelper.BBUIHelper() # Look for the specially designated handlers which need to be passed to the # terminal handler console_handlers = [h for h in conf.config['handlers'].values() if getattr(h, 'is_console', False)] bb.utils.set_process_name("KnottyUI") if params.options.remote_server and params.options.kill_server: server.terminateServer() return llevel, debug_domains = bb.msg.constructLogOptions() try: server.runCommand(["setEventMask", server.getEventHandle(), llevel, debug_domains, _evt_list]) except (BrokenPipeError, EOFError) as e: # bitbake-server comms failure logger.fatal("Attempting to set event mask: %s", e) return 1 # The logging_tree module is *extremely* helpful in debugging logging # domains. Uncomment here to dump the logging tree when bitbake starts #import logging_tree #logging_tree.printout() universe = False if not params.observe_only: try: params.updateFromServer(server) except Exception as e: logger.fatal("Fetching command line: %s", e) return 1 cmdline = params.parseActions() if not cmdline: print("Nothing to do. Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.") return 1 if 'msg' in cmdline and cmdline['msg']: logger.error(cmdline['msg']) return 1 if cmdline['action'][0] == "buildTargets" and "universe" in cmdline['action'][1]: universe = True try: ret, error = server.runCommand(cmdline['action']) except (BrokenPipeError, EOFError) as e: # bitbake-server comms failure logger.fatal("Command '{}' failed: %s".format(cmdline), e) return 1 if error: logger.error("Command '%s' failed: %s" % (cmdline, error)) return 1 elif not ret: logger.error("Command '%s' failed: returned %s" % (cmdline, ret)) return 1 parseprogress = None cacheprogress = None main.shutdown = 0 interrupted = False return_value = 0 errors = 0 warnings = 0 taskfailures = [] printintervaldelta = 10 * 60 # 10 minutes printinterval = printintervaldelta pinginterval = 1 * 60 # 1 minute lastevent = lastprint = time.time() termfilter = tf(main, helper, console_handlers, params.options.quiet) atexit.register(termfilter.finish) # shutdown levels # 0 - normal operation # 1 - no new task execution, let current running tasks finish # 2 - interrupting currently executing tasks # 3 - we're done, exit while main.shutdown < 3: try: if (lastprint + printinterval) <= time.time(): termfilter.keepAlive(printinterval) printinterval += printintervaldelta event = eventHandler.waitEvent(0) if event is None: if (lastevent + pinginterval) <= time.time(): ret, error = server.runCommand(["ping"]) if error or not ret: termfilter.clearFooter() print("No reply after pinging server (%s, %s), exiting." % (str(error), str(ret))) return_value = 3 main.shutdown = 3 lastevent = time.time() if not parseprogress: termfilter.updateFooter() event = eventHandler.waitEvent(0.25) if event is None: continue lastevent = time.time() helper.eventHandler(event) if isinstance(event, bb.runqueue.runQueueExitWait): if not main.shutdown: main.shutdown = 1 continue if isinstance(event, bb.event.LogExecTTY): if log_exec_tty: tries = event.retries while tries: print("Trying to run: %s" % event.prog) if os.system(event.prog) == 0: break time.sleep(event.sleep_delay) tries -= 1 if tries: continue logger.warning(event.msg) continue if isinstance(event, logging.LogRecord): lastprint = time.time() printinterval = printintervaldelta if event.levelno >= bb.msg.BBLogFormatter.ERRORONCE: errors = errors + 1 return_value = 1 elif event.levelno == bb.msg.BBLogFormatter.WARNING: warnings = warnings + 1 if event.taskpid != 0: # For "normal" logging conditions, don't show note logs from tasks # but do show them if the user has changed the default log level to # include verbose/debug messages if event.levelno <= bb.msg.BBLogFormatter.NOTE and (event.levelno < llevel or (event.levelno == bb.msg.BBLogFormatter.NOTE and llevel != bb.msg.BBLogFormatter.VERBOSE)): continue # Prefix task messages with recipe/task if event.taskpid in helper.pidmap and event.levelno not in [bb.msg.BBLogFormatter.PLAIN, bb.msg.BBLogFormatter.WARNONCE, bb.msg.BBLogFormatter.ERRORONCE]: taskinfo = helper.running_tasks[helper.pidmap[event.taskpid]] event.msg = taskinfo['title'] + ': ' + event.msg if hasattr(event, 'fn') and event.levelno not in [bb.msg.BBLogFormatter.WARNONCE, bb.msg.BBLogFormatter.ERRORONCE]: event.msg = event.fn + ': ' + event.msg logging.getLogger(event.name).handle(event) continue if isinstance(event, bb.build.TaskFailedSilent): logger.warning("Logfile for failed setscene task is %s" % event.logfile) continue if isinstance(event, bb.build.TaskFailed): return_value = 1 print_event_log(event, includelogs, loglines, termfilter) if isinstance(event, bb.build.TaskBase): logger.info(event._message) continue if isinstance(event, bb.event.ParseStarted): if params.options.quiet > 1: continue if event.total == 0: continue termfilter.clearFooter() parseprogress = new_progress("Parsing recipes", event.total).start() continue if isinstance(event, bb.event.ParseProgress): if params.options.quiet > 1: continue if parseprogress: parseprogress.update(event.current) else: bb.warn("Got ParseProgress event for parsing that never started?") continue if isinstance(event, bb.event.ParseCompleted): if params.options.quiet > 1: continue if not parseprogress: continue parseprogress.finish() parseprogress = None if params.options.quiet == 0: print(("Parsing of %d .bb files complete (%d cached, %d parsed). %d targets, %d skipped, %d masked, %d errors." % ( event.total, event.cached, event.parsed, event.virtuals, event.skipped, event.masked, event.errors))) continue if isinstance(event, bb.event.CacheLoadStarted): if params.options.quiet > 1: continue cacheprogress = new_progress("Loading cache", event.total).start() continue if isinstance(event, bb.event.CacheLoadProgress): if params.options.quiet > 1: continue cacheprogress.update(event.current) continue if isinstance(event, bb.event.CacheLoadCompleted): if params.options.quiet > 1: continue cacheprogress.finish() if params.options.quiet == 0: print("Loaded %d entries from dependency cache." % event.num_entries) continue if isinstance(event, bb.command.CommandFailed): return_value = event.exitcode if event.error: errors = errors + 1 logger.error(str(event)) main.shutdown = 3 continue if isinstance(event, bb.command.CommandExit): if not return_value: return_value = event.exitcode main.shutdown = 3 continue if isinstance(event, (bb.command.CommandCompleted, bb.cooker.CookerExit)): main.shutdown = 3 continue if isinstance(event, bb.event.MultipleProviders): logger.info(str(event)) continue if isinstance(event, bb.event.NoProvider): # For universe builds, only show these as warnings, not errors if not universe: return_value = 1 errors = errors + 1 logger.error(str(event)) else: logger.warning(str(event)) continue if isinstance(event, bb.runqueue.sceneQueueTaskStarted): logger.info("Running setscene task %d of %d (%s)" % (event.stats.setscene_covered + event.stats.setscene_active + event.stats.setscene_notcovered + 1, event.stats.setscene_total, event.taskstring)) continue if isinstance(event, bb.runqueue.runQueueTaskStarted): if event.noexec: tasktype = 'noexec task' else: tasktype = 'task' logger.info("Running %s %d of %d (%s)", tasktype, event.stats.completed + event.stats.active + event.stats.failed + 1, event.stats.total, event.taskstring) continue if isinstance(event, bb.runqueue.runQueueTaskFailed): return_value = 1 taskfailures.append(event.taskstring) logger.error(str(event)) continue if isinstance(event, bb.runqueue.sceneQueueTaskFailed): logger.warning(str(event)) continue if isinstance(event, bb.event.DepTreeGenerated): continue if isinstance(event, bb.event.ProcessStarted): if params.options.quiet > 1: continue termfilter.clearFooter() parseprogress = new_progress(event.processname, event.total) parseprogress.start(False) continue if isinstance(event, bb.event.ProcessProgress): if params.options.quiet > 1: continue if parseprogress: parseprogress.update(event.progress) else: bb.warn("Got ProcessProgress event for someting that never started?") continue if isinstance(event, bb.event.ProcessFinished): if params.options.quiet > 1: continue if parseprogress: parseprogress.finish() parseprogress = None continue # ignore if isinstance(event, (bb.event.BuildBase, bb.event.MetadataEvent, bb.event.ConfigParsed, bb.event.MultiConfigParsed, bb.event.RecipeParsed, bb.event.RecipePreFinalise, bb.runqueue.runQueueEvent, bb.event.OperationStarted, bb.event.OperationCompleted, bb.event.OperationProgress, bb.event.DiskFull, bb.event.HeartbeatEvent, bb.build.TaskProgress)): continue logger.error("Unknown event: %s", event) except (BrokenPipeError, EOFError) as e: # bitbake-server comms failure, don't attempt further comms and exit logger.fatal("Executing event: %s", e) return_value = 1 errors = errors + 1 main.shutdown = 3 except EnvironmentError as ioerror: termfilter.clearFooter() # ignore interrupted io if ioerror.args[0] == 4: continue sys.stderr.write(str(ioerror)) main.shutdown = 2 if not params.observe_only: try: _, error = server.runCommand(["stateForceShutdown"]) except (BrokenPipeError, EOFError) as e: # bitbake-server comms failure, don't attempt further comms and exit logger.fatal("Unable to force shutdown: %s", e) main.shutdown = 3 except KeyboardInterrupt: termfilter.clearFooter() if params.observe_only: print("\nKeyboard Interrupt, exiting observer...") main.shutdown = 2 def state_force_shutdown(): print("\nSecond Keyboard Interrupt, stopping...\n") try: _, error = server.runCommand(["stateForceShutdown"]) if error: logger.error("Unable to cleanly stop: %s" % error) except (BrokenPipeError, EOFError) as e: # bitbake-server comms failure logger.fatal("Unable to cleanly stop: %s", e) if not params.observe_only and main.shutdown == 1: state_force_shutdown() if not params.observe_only and main.shutdown == 0: print("\nKeyboard Interrupt, closing down...\n") interrupted = True # Capture the second KeyboardInterrupt during stateShutdown is running try: _, error = server.runCommand(["stateShutdown"]) if error: logger.error("Unable to cleanly shutdown: %s" % error) except (BrokenPipeError, EOFError) as e: # bitbake-server comms failure logger.fatal("Unable to cleanly shutdown: %s", e) except KeyboardInterrupt: state_force_shutdown() main.shutdown = main.shutdown + 1 except Exception as e: import traceback sys.stderr.write(traceback.format_exc()) main.shutdown = 2 if not params.observe_only: try: _, error = server.runCommand(["stateForceShutdown"]) except (BrokenPipeError, EOFError) as e: # bitbake-server comms failure, don't attempt further comms and exit logger.fatal("Unable to force shutdown: %s", e) main.shudown = 3 return_value = 1 try: termfilter.clearFooter() summary = "" if taskfailures: summary += pluralise("\nSummary: %s task failed:", "\nSummary: %s tasks failed:", len(taskfailures)) for failure in taskfailures: summary += "\n %s" % failure if warnings: summary += pluralise("\nSummary: There was %s WARNING message.", "\nSummary: There were %s WARNING messages.", warnings) if return_value and errors: summary += pluralise("\nSummary: There was %s ERROR message, returning a non-zero exit code.", "\nSummary: There were %s ERROR messages, returning a non-zero exit code.", errors) if summary and params.options.quiet == 0: print(summary) if interrupted: print("Execution was interrupted, returning a non-zero exit code.") if return_value == 0: return_value = 1 except IOError as e: import errno if e.errno == errno.EPIPE: pass logging.shutdown() return return_value