xref: /OK3568_Linux_fs/yocto/poky/bitbake/lib/bb/ui/knotty.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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# SPDX-License-Identifier: GPL-2.0-only
9#
10
11from __future__ import division
12
13import os
14import sys
15import logging
16import progressbar
17import signal
18import bb.msg
19import time
20import fcntl
21import struct
22import copy
23import atexit
24from itertools import groupby
25
26from bb.ui import uihelper
27
28featureSet = [bb.cooker.CookerFeatures.SEND_SANITYEVENTS, bb.cooker.CookerFeatures.BASEDATASTORE_TRACKING]
29
30logger = logging.getLogger("BitBake")
31interactive = sys.stdout.isatty()
32
33class BBProgress(progressbar.ProgressBar):
34    def __init__(self, msg, maxval, widgets=None, extrapos=-1, resize_handler=None):
35        self.msg = msg
36        self.extrapos = extrapos
37        if not widgets:
38            widgets = [': ', progressbar.Percentage(), ' ', progressbar.Bar(),
39                       ' ', progressbar.ETA()]
40            self.extrapos = 5
41
42        if resize_handler:
43            self._resize_default = resize_handler
44        else:
45            self._resize_default = signal.getsignal(signal.SIGWINCH)
46        progressbar.ProgressBar.__init__(self, maxval, [self.msg] + widgets, fd=sys.stdout)
47
48    def _handle_resize(self, signum=None, frame=None):
49        progressbar.ProgressBar._handle_resize(self, signum, frame)
50        if self._resize_default:
51            self._resize_default(signum, frame)
52
53    def finish(self):
54        progressbar.ProgressBar.finish(self)
55        if self._resize_default:
56            signal.signal(signal.SIGWINCH, self._resize_default)
57
58    def setmessage(self, msg):
59        self.msg = msg
60        self.widgets[0] = msg
61
62    def setextra(self, extra):
63        if self.extrapos > -1:
64            if extra:
65                extrastr = str(extra)
66                if extrastr[0] != ' ':
67                    extrastr = ' ' + extrastr
68            else:
69                extrastr = ''
70            self.widgets[self.extrapos] = extrastr
71
72    def _need_update(self):
73        # We always want the bar to print when update() is called
74        return True
75
76class NonInteractiveProgress(object):
77    fobj = sys.stdout
78
79    def __init__(self, msg, maxval):
80        self.msg = msg
81        self.maxval = maxval
82        self.finished = False
83
84    def start(self, update=True):
85        self.fobj.write("%s..." % self.msg)
86        self.fobj.flush()
87        return self
88
89    def update(self, value):
90        pass
91
92    def finish(self):
93        if self.finished:
94            return
95        self.fobj.write("done.\n")
96        self.fobj.flush()
97        self.finished = True
98
99def new_progress(msg, maxval):
100    if interactive:
101        return BBProgress(msg, maxval)
102    else:
103        return NonInteractiveProgress(msg, maxval)
104
105def pluralise(singular, plural, qty):
106    if(qty == 1):
107        return singular % qty
108    else:
109        return plural % qty
110
111
112class InteractConsoleLogFilter(logging.Filter):
113    def __init__(self, tf):
114        self.tf = tf
115
116    def filter(self, record):
117        if record.levelno == bb.msg.BBLogFormatter.NOTE and (record.msg.startswith("Running") or record.msg.startswith("recipe ")):
118            return False
119        self.tf.clearFooter()
120        return True
121
122class TerminalFilter(object):
123    rows = 25
124    columns = 80
125
126    def sigwinch_handle(self, signum, frame):
127        self.rows, self.columns = self.getTerminalColumns()
128        if self._sigwinch_default:
129            self._sigwinch_default(signum, frame)
130
131    def getTerminalColumns(self):
132        def ioctl_GWINSZ(fd):
133            try:
134                cr = struct.unpack('hh', fcntl.ioctl(fd, self.termios.TIOCGWINSZ, '1234'))
135            except:
136                return None
137            return cr
138        cr = ioctl_GWINSZ(sys.stdout.fileno())
139        if not cr:
140            try:
141                fd = os.open(os.ctermid(), os.O_RDONLY)
142                cr = ioctl_GWINSZ(fd)
143                os.close(fd)
144            except:
145                pass
146        if not cr:
147            try:
148                cr = (os.environ['LINES'], os.environ['COLUMNS'])
149            except:
150                cr = (25, 80)
151        return cr
152
153    def __init__(self, main, helper, handlers, quiet):
154        self.main = main
155        self.helper = helper
156        self.cuu = None
157        self.stdinbackup = None
158        self.interactive = sys.stdout.isatty()
159        self.footer_present = False
160        self.lastpids = []
161        self.lasttime = None
162        self.quiet = quiet
163
164        if not self.interactive:
165            return
166
167        try:
168            import curses
169        except ImportError:
170            sys.exit("FATAL: The knotty ui could not load the required curses python module.")
171
172        import termios
173        self.curses = curses
174        self.termios = termios
175        try:
176            fd = sys.stdin.fileno()
177            self.stdinbackup = termios.tcgetattr(fd)
178            new = copy.deepcopy(self.stdinbackup)
179            new[3] = new[3] & ~termios.ECHO
180            termios.tcsetattr(fd, termios.TCSADRAIN, new)
181            curses.setupterm()
182            if curses.tigetnum("colors") > 2:
183                for h in handlers:
184                    try:
185                        h.formatter.enable_color()
186                    except AttributeError:
187                        pass
188            self.ed = curses.tigetstr("ed")
189            if self.ed:
190                self.cuu = curses.tigetstr("cuu")
191            try:
192                self._sigwinch_default = signal.getsignal(signal.SIGWINCH)
193                signal.signal(signal.SIGWINCH, self.sigwinch_handle)
194            except:
195                pass
196            self.rows, self.columns = self.getTerminalColumns()
197        except:
198            self.cuu = None
199        if not self.cuu:
200            self.interactive = False
201            bb.note("Unable to use interactive mode for this terminal, using fallback")
202            return
203
204        for h in handlers:
205            h.addFilter(InteractConsoleLogFilter(self))
206
207        self.main_progress = None
208
209    def clearFooter(self):
210        if self.footer_present:
211            lines = self.footer_present
212            sys.stdout.buffer.write(self.curses.tparm(self.cuu, lines))
213            sys.stdout.buffer.write(self.curses.tparm(self.ed))
214            sys.stdout.flush()
215        self.footer_present = False
216
217    def elapsed(self, sec):
218        hrs = int(sec / 3600.0)
219        sec -= hrs * 3600
220        min = int(sec / 60.0)
221        sec -= min * 60
222        if hrs > 0:
223            return "%dh%dm%ds" % (hrs, min, sec)
224        elif min > 0:
225            return "%dm%ds" % (min, sec)
226        else:
227            return "%ds" % (sec)
228
229    def keepAlive(self, t):
230        if not self.cuu:
231            print("Bitbake still alive (no events for %ds). Active tasks:" % t)
232            for t in self.helper.running_tasks:
233                print(t)
234            sys.stdout.flush()
235
236    def updateFooter(self):
237        if not self.cuu:
238            return
239        activetasks = self.helper.running_tasks
240        failedtasks = self.helper.failed_tasks
241        runningpids = self.helper.running_pids
242        currenttime = time.time()
243        if not self.lasttime or (currenttime - self.lasttime > 5):
244            self.helper.needUpdate = True
245            self.lasttime = currenttime
246        if self.footer_present and not self.helper.needUpdate:
247            return
248        self.helper.needUpdate = False
249        if self.footer_present:
250            self.clearFooter()
251        if (not self.helper.tasknumber_total or self.helper.tasknumber_current == self.helper.tasknumber_total) and not len(activetasks):
252            return
253        tasks = []
254        for t in runningpids:
255            start_time = activetasks[t].get("starttime", None)
256            if start_time:
257                msg = "%s - %s (pid %s)" % (activetasks[t]["title"], self.elapsed(currenttime - start_time), activetasks[t]["pid"])
258            else:
259                msg = "%s (pid %s)" % (activetasks[t]["title"], activetasks[t]["pid"])
260            progress = activetasks[t].get("progress", None)
261            if progress is not None:
262                pbar = activetasks[t].get("progressbar", None)
263                rate = activetasks[t].get("rate", None)
264                if not pbar or pbar.bouncing != (progress < 0):
265                    if progress < 0:
266                        pbar = BBProgress("0: %s" % msg, 100, widgets=[' ', progressbar.BouncingSlider(), ''], extrapos=3, resize_handler=self.sigwinch_handle)
267                        pbar.bouncing = True
268                    else:
269                        pbar = BBProgress("0: %s" % msg, 100, widgets=[' ', progressbar.Percentage(), ' ', progressbar.Bar(), ''], extrapos=5, resize_handler=self.sigwinch_handle)
270                        pbar.bouncing = False
271                    activetasks[t]["progressbar"] = pbar
272                tasks.append((pbar, msg, progress, rate, start_time))
273            else:
274                tasks.append(msg)
275
276        if self.main.shutdown:
277            content = pluralise("Waiting for %s running task to finish",
278                                "Waiting for %s running tasks to finish", len(activetasks))
279            if not self.quiet:
280                content += ':'
281            print(content)
282        else:
283            scene_tasks = "%s of %s" % (self.helper.setscene_current, self.helper.setscene_total)
284            cur_tasks = "%s of %s" % (self.helper.tasknumber_current, self.helper.tasknumber_total)
285
286            content = ''
287            if not self.quiet:
288                msg = "Setscene tasks: %s" % scene_tasks
289                content += msg + "\n"
290                print(msg)
291
292            if self.quiet:
293                msg = "Running tasks (%s, %s)" % (scene_tasks, cur_tasks)
294            elif not len(activetasks):
295                msg = "No currently running tasks (%s)" % cur_tasks
296            else:
297                msg = "Currently %2s running tasks (%s)" % (len(activetasks), cur_tasks)
298            maxtask = self.helper.tasknumber_total
299            if not self.main_progress or self.main_progress.maxval != maxtask:
300                widgets = [' ', progressbar.Percentage(), ' ', progressbar.Bar()]
301                self.main_progress = BBProgress("Running tasks", maxtask, widgets=widgets, resize_handler=self.sigwinch_handle)
302                self.main_progress.start(False)
303            self.main_progress.setmessage(msg)
304            progress = max(0, self.helper.tasknumber_current - 1)
305            content += self.main_progress.update(progress)
306            print('')
307        lines = self.getlines(content)
308        if not self.quiet:
309            for tasknum, task in enumerate(tasks[:(self.rows - 1 - lines)]):
310                if isinstance(task, tuple):
311                    pbar, msg, progress, rate, start_time = task
312                    if not pbar.start_time:
313                        pbar.start(False)
314                        if start_time:
315                            pbar.start_time = start_time
316                    pbar.setmessage('%s: %s' % (tasknum, msg))
317                    pbar.setextra(rate)
318                    if progress > -1:
319                        content = pbar.update(progress)
320                    else:
321                        content = pbar.update(1)
322                    print('')
323                else:
324                    content = "%s: %s" % (tasknum, task)
325                    print(content)
326                lines = lines + self.getlines(content)
327        self.footer_present = lines
328        self.lastpids = runningpids[:]
329        self.lastcount = self.helper.tasknumber_current
330
331    def getlines(self, content):
332        lines = 0
333        for line in content.split("\n"):
334            lines = lines + 1 + int(len(line) / (self.columns + 1))
335        return lines
336
337    def finish(self):
338        if self.stdinbackup:
339            fd = sys.stdin.fileno()
340            self.termios.tcsetattr(fd, self.termios.TCSADRAIN, self.stdinbackup)
341
342def print_event_log(event, includelogs, loglines, termfilter):
343    # FIXME refactor this out further
344    logfile = event.logfile
345    if logfile and os.path.exists(logfile):
346        termfilter.clearFooter()
347        bb.error("Logfile of failure stored in: %s" % logfile)
348        if includelogs and not event.errprinted:
349            print("Log data follows:")
350            f = open(logfile, "r")
351            lines = []
352            while True:
353                l = f.readline()
354                if l == '':
355                    break
356                l = l.rstrip()
357                if loglines:
358                    lines.append(' | %s' % l)
359                    if len(lines) > int(loglines):
360                        lines.pop(0)
361                else:
362                    print('| %s' % l)
363            f.close()
364            if lines:
365                for line in lines:
366                    print(line)
367
368def _log_settings_from_server(server, observe_only):
369    # Get values of variables which control our output
370    includelogs, error = server.runCommand(["getVariable", "BBINCLUDELOGS"])
371    if error:
372        logger.error("Unable to get the value of BBINCLUDELOGS variable: %s" % error)
373        raise BaseException(error)
374    loglines, error = server.runCommand(["getVariable", "BBINCLUDELOGS_LINES"])
375    if error:
376        logger.error("Unable to get the value of BBINCLUDELOGS_LINES variable: %s" % error)
377        raise BaseException(error)
378    if observe_only:
379        cmd = 'getVariable'
380    else:
381        cmd = 'getSetVariable'
382    consolelogfile, error = server.runCommand([cmd, "BB_CONSOLELOG"])
383    if error:
384        logger.error("Unable to get the value of BB_CONSOLELOG variable: %s" % error)
385        raise BaseException(error)
386    logconfigfile, error = server.runCommand([cmd, "BB_LOGCONFIG"])
387    if error:
388        logger.error("Unable to get the value of BB_LOGCONFIG variable: %s" % error)
389        raise BaseException(error)
390    return includelogs, loglines, consolelogfile, logconfigfile
391
392_evt_list = [ "bb.runqueue.runQueueExitWait", "bb.event.LogExecTTY", "logging.LogRecord",
393              "bb.build.TaskFailed", "bb.build.TaskBase", "bb.event.ParseStarted",
394              "bb.event.ParseProgress", "bb.event.ParseCompleted", "bb.event.CacheLoadStarted",
395              "bb.event.CacheLoadProgress", "bb.event.CacheLoadCompleted", "bb.command.CommandFailed",
396              "bb.command.CommandExit", "bb.command.CommandCompleted",  "bb.cooker.CookerExit",
397              "bb.event.MultipleProviders", "bb.event.NoProvider", "bb.runqueue.sceneQueueTaskStarted",
398              "bb.runqueue.runQueueTaskStarted", "bb.runqueue.runQueueTaskFailed", "bb.runqueue.sceneQueueTaskFailed",
399              "bb.event.BuildBase", "bb.build.TaskStarted", "bb.build.TaskSucceeded", "bb.build.TaskFailedSilent",
400              "bb.build.TaskProgress", "bb.event.ProcessStarted", "bb.event.ProcessProgress", "bb.event.ProcessFinished"]
401
402def drain_events_errorhandling(eventHandler):
403    # We don't have logging setup, we do need to show any events we see before exiting
404    event = True
405    logger = bb.msg.logger_create('bitbake', sys.stdout)
406    while event:
407        event = eventHandler.waitEvent(0)
408        if isinstance(event, logging.LogRecord):
409            logger.handle(event)
410
411def main(server, eventHandler, params, tf = TerminalFilter):
412
413    try:
414        if not params.observe_only:
415            params.updateToServer(server, os.environ.copy())
416
417        includelogs, loglines, consolelogfile, logconfigfile = _log_settings_from_server(server, params.observe_only)
418
419        loglevel, _ = bb.msg.constructLogOptions()
420    except bb.BBHandledException:
421        drain_events_errorhandling(eventHandler)
422        return 1
423
424    if params.options.quiet == 0:
425        console_loglevel = loglevel
426    elif params.options.quiet > 2:
427        console_loglevel = bb.msg.BBLogFormatter.ERROR
428    else:
429        console_loglevel = bb.msg.BBLogFormatter.WARNING
430
431    logconfig = {
432        "version": 1,
433        "handlers": {
434            "BitBake.console": {
435                "class": "logging.StreamHandler",
436                "formatter": "BitBake.consoleFormatter",
437                "level": console_loglevel,
438                "stream": "ext://sys.stdout",
439                "filters": ["BitBake.stdoutFilter"],
440                ".": {
441                    "is_console": True,
442                },
443            },
444            "BitBake.errconsole": {
445                "class": "logging.StreamHandler",
446                "formatter": "BitBake.consoleFormatter",
447                "level": loglevel,
448                "stream": "ext://sys.stderr",
449                "filters": ["BitBake.stderrFilter"],
450                ".": {
451                    "is_console": True,
452                },
453            },
454            # This handler can be used if specific loggers should print on
455            # the console at a lower severity than the default. It will
456            # display any messages sent to it that are lower than then
457            # BitBake.console logging level (so as to prevent duplication of
458            # messages). Nothing is attached to this handler by default
459            "BitBake.verbconsole": {
460                "class": "logging.StreamHandler",
461                "formatter": "BitBake.consoleFormatter",
462                "level": 1,
463                "stream": "ext://sys.stdout",
464                "filters": ["BitBake.verbconsoleFilter"],
465                ".": {
466                    "is_console": True,
467                },
468            },
469        },
470        "formatters": {
471            # This format instance will get color output enabled by the
472            # terminal
473            "BitBake.consoleFormatter" : {
474                "()": "bb.msg.BBLogFormatter",
475                "format": "%(levelname)s: %(message)s"
476            },
477            # The file log requires a separate instance so that it doesn't get
478            # color enabled
479            "BitBake.logfileFormatter": {
480                "()": "bb.msg.BBLogFormatter",
481                "format": "%(levelname)s: %(message)s"
482            }
483        },
484        "filters": {
485            "BitBake.stdoutFilter": {
486                "()": "bb.msg.LogFilterLTLevel",
487                "level": "ERROR"
488            },
489            "BitBake.stderrFilter": {
490                "()": "bb.msg.LogFilterGEQLevel",
491                "level": "ERROR"
492            },
493            "BitBake.verbconsoleFilter": {
494                "()": "bb.msg.LogFilterLTLevel",
495                "level": console_loglevel
496            },
497        },
498        "loggers": {
499            "BitBake": {
500                "level": loglevel,
501                "handlers": ["BitBake.console", "BitBake.errconsole"],
502            }
503        },
504        "disable_existing_loggers": False
505    }
506
507    # Enable the console log file if enabled
508    if consolelogfile and not params.options.show_environment and not params.options.show_versions:
509        logconfig = bb.msg.mergeLoggingConfig(logconfig, {
510                "version": 1,
511                "handlers" : {
512                    "BitBake.consolelog": {
513                        "class": "logging.FileHandler",
514                        "formatter": "BitBake.logfileFormatter",
515                        "level": loglevel,
516                        "filename": consolelogfile,
517                    },
518                    # Just like verbconsole, anything sent here will go to the
519                    # log file, unless it would go to BitBake.consolelog
520                    "BitBake.verbconsolelog" : {
521                        "class": "logging.FileHandler",
522                        "formatter": "BitBake.logfileFormatter",
523                        "level": 1,
524                        "filename": consolelogfile,
525                        "filters": ["BitBake.verbconsolelogFilter"],
526                    },
527                },
528                "filters": {
529                    "BitBake.verbconsolelogFilter": {
530                        "()": "bb.msg.LogFilterLTLevel",
531                        "level": loglevel,
532                    },
533                },
534                "loggers": {
535                    "BitBake": {
536                        "handlers": ["BitBake.consolelog"],
537                    },
538
539                    # Other interesting things that we want to keep an eye on
540                    # in the log files in case someone has an issue, but not
541                    # necessarily show to the user on the console
542                    "BitBake.SigGen.HashEquiv": {
543                        "level": "VERBOSE",
544                        "handlers": ["BitBake.verbconsolelog"],
545                    },
546                    "BitBake.RunQueue.HashEquiv": {
547                        "level": "VERBOSE",
548                        "handlers": ["BitBake.verbconsolelog"],
549                    }
550                }
551            })
552
553        bb.utils.mkdirhier(os.path.dirname(consolelogfile))
554        loglink = os.path.join(os.path.dirname(consolelogfile), 'console-latest.log')
555        bb.utils.remove(loglink)
556        try:
557           os.symlink(os.path.basename(consolelogfile), loglink)
558        except OSError:
559           pass
560
561    # Add the logging domains specified by the user on the command line
562    for (domainarg, iterator) in groupby(params.debug_domains):
563        dlevel = len(tuple(iterator))
564        l = logconfig["loggers"].setdefault("BitBake.%s" % domainarg, {})
565        l["level"] = logging.DEBUG - dlevel + 1
566        l.setdefault("handlers", []).extend(["BitBake.verbconsole"])
567
568    conf = bb.msg.setLoggingConfig(logconfig, logconfigfile)
569
570    if sys.stdin.isatty() and sys.stdout.isatty():
571        log_exec_tty = True
572    else:
573        log_exec_tty = False
574
575    helper = uihelper.BBUIHelper()
576
577    # Look for the specially designated handlers which need to be passed to the
578    # terminal handler
579    console_handlers = [h for h in conf.config['handlers'].values() if getattr(h, 'is_console', False)]
580
581    bb.utils.set_process_name("KnottyUI")
582
583    if params.options.remote_server and params.options.kill_server:
584        server.terminateServer()
585        return
586
587    llevel, debug_domains = bb.msg.constructLogOptions()
588    server.runCommand(["setEventMask", server.getEventHandle(), llevel, debug_domains, _evt_list])
589
590    # The logging_tree module is *extremely* helpful in debugging logging
591    # domains. Uncomment here to dump the logging tree when bitbake starts
592    #import logging_tree
593    #logging_tree.printout()
594
595    universe = False
596    if not params.observe_only:
597        params.updateFromServer(server)
598        cmdline = params.parseActions()
599        if not cmdline:
600            print("Nothing to do.  Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.")
601            return 1
602        if 'msg' in cmdline and cmdline['msg']:
603            logger.error(cmdline['msg'])
604            return 1
605        if cmdline['action'][0] == "buildTargets" and "universe" in cmdline['action'][1]:
606            universe = True
607
608        ret, error = server.runCommand(cmdline['action'])
609        if error:
610            logger.error("Command '%s' failed: %s" % (cmdline, error))
611            return 1
612        elif not ret:
613            logger.error("Command '%s' failed: returned %s" % (cmdline, ret))
614            return 1
615
616
617    parseprogress = None
618    cacheprogress = None
619    main.shutdown = 0
620    interrupted = False
621    return_value = 0
622    errors = 0
623    warnings = 0
624    taskfailures = []
625
626    printintervaldelta = 10 * 60 # 10 minutes
627    printinterval = printintervaldelta
628    lastprint = time.time()
629
630    termfilter = tf(main, helper, console_handlers, params.options.quiet)
631    atexit.register(termfilter.finish)
632
633    while True:
634        try:
635            if (lastprint + printinterval) <= time.time():
636                termfilter.keepAlive(printinterval)
637                printinterval += printintervaldelta
638            event = eventHandler.waitEvent(0)
639            if event is None:
640                if main.shutdown > 1:
641                    break
642                if not parseprogress:
643                    termfilter.updateFooter()
644                event = eventHandler.waitEvent(0.25)
645                if event is None:
646                    continue
647            helper.eventHandler(event)
648            if isinstance(event, bb.runqueue.runQueueExitWait):
649                if not main.shutdown:
650                    main.shutdown = 1
651                continue
652            if isinstance(event, bb.event.LogExecTTY):
653                if log_exec_tty:
654                    tries = event.retries
655                    while tries:
656                        print("Trying to run: %s" % event.prog)
657                        if os.system(event.prog) == 0:
658                            break
659                        time.sleep(event.sleep_delay)
660                        tries -= 1
661                    if tries:
662                        continue
663                logger.warning(event.msg)
664                continue
665
666            if isinstance(event, logging.LogRecord):
667                lastprint = time.time()
668                printinterval = printintervaldelta
669                if event.levelno >= bb.msg.BBLogFormatter.ERRORONCE:
670                    errors = errors + 1
671                    return_value = 1
672                elif event.levelno == bb.msg.BBLogFormatter.WARNING:
673                    warnings = warnings + 1
674
675                if event.taskpid != 0:
676                    # For "normal" logging conditions, don't show note logs from tasks
677                    # but do show them if the user has changed the default log level to
678                    # include verbose/debug messages
679                    if event.levelno <= bb.msg.BBLogFormatter.NOTE and (event.levelno < llevel or (event.levelno == bb.msg.BBLogFormatter.NOTE and llevel != bb.msg.BBLogFormatter.VERBOSE)):
680                        continue
681
682                    # Prefix task messages with recipe/task
683                    if event.taskpid in helper.pidmap and event.levelno not in [bb.msg.BBLogFormatter.PLAIN, bb.msg.BBLogFormatter.WARNONCE, bb.msg.BBLogFormatter.ERRORONCE]:
684                        taskinfo = helper.running_tasks[helper.pidmap[event.taskpid]]
685                        event.msg = taskinfo['title'] + ': ' + event.msg
686                if hasattr(event, 'fn') and event.levelno not in [bb.msg.BBLogFormatter.WARNONCE, bb.msg.BBLogFormatter.ERRORONCE]:
687                    event.msg = event.fn + ': ' + event.msg
688                logging.getLogger(event.name).handle(event)
689                continue
690
691            if isinstance(event, bb.build.TaskFailedSilent):
692                logger.warning("Logfile for failed setscene task is %s" % event.logfile)
693                continue
694            if isinstance(event, bb.build.TaskFailed):
695                return_value = 1
696                print_event_log(event, includelogs, loglines, termfilter)
697            if isinstance(event, bb.build.TaskBase):
698                logger.info(event._message)
699                continue
700            if isinstance(event, bb.event.ParseStarted):
701                if params.options.quiet > 1:
702                    continue
703                if event.total == 0:
704                    continue
705                termfilter.clearFooter()
706                parseprogress = new_progress("Parsing recipes", event.total).start()
707                continue
708            if isinstance(event, bb.event.ParseProgress):
709                if params.options.quiet > 1:
710                    continue
711                if parseprogress:
712                    parseprogress.update(event.current)
713                else:
714                    bb.warn("Got ParseProgress event for parsing that never started?")
715                continue
716            if isinstance(event, bb.event.ParseCompleted):
717                if params.options.quiet > 1:
718                    continue
719                if not parseprogress:
720                    continue
721                parseprogress.finish()
722                parseprogress = None
723                if params.options.quiet == 0:
724                    print(("Parsing of %d .bb files complete (%d cached, %d parsed). %d targets, %d skipped, %d masked, %d errors."
725                        % ( event.total, event.cached, event.parsed, event.virtuals, event.skipped, event.masked, event.errors)))
726                continue
727
728            if isinstance(event, bb.event.CacheLoadStarted):
729                if params.options.quiet > 1:
730                    continue
731                cacheprogress = new_progress("Loading cache", event.total).start()
732                continue
733            if isinstance(event, bb.event.CacheLoadProgress):
734                if params.options.quiet > 1:
735                    continue
736                cacheprogress.update(event.current)
737                continue
738            if isinstance(event, bb.event.CacheLoadCompleted):
739                if params.options.quiet > 1:
740                    continue
741                cacheprogress.finish()
742                if params.options.quiet == 0:
743                    print("Loaded %d entries from dependency cache." % event.num_entries)
744                continue
745
746            if isinstance(event, bb.command.CommandFailed):
747                return_value = event.exitcode
748                if event.error:
749                    errors = errors + 1
750                    logger.error(str(event))
751                main.shutdown = 2
752                continue
753            if isinstance(event, bb.command.CommandExit):
754                if not return_value:
755                    return_value = event.exitcode
756                main.shutdown = 2
757                continue
758            if isinstance(event, (bb.command.CommandCompleted, bb.cooker.CookerExit)):
759                main.shutdown = 2
760                continue
761            if isinstance(event, bb.event.MultipleProviders):
762                logger.info(str(event))
763                continue
764            if isinstance(event, bb.event.NoProvider):
765                # For universe builds, only show these as warnings, not errors
766                if not universe:
767                    return_value = 1
768                    errors = errors + 1
769                    logger.error(str(event))
770                else:
771                    logger.warning(str(event))
772                continue
773
774            if isinstance(event, bb.runqueue.sceneQueueTaskStarted):
775                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))
776                continue
777
778            if isinstance(event, bb.runqueue.runQueueTaskStarted):
779                if event.noexec:
780                    tasktype = 'noexec task'
781                else:
782                    tasktype = 'task'
783                logger.info("Running %s %d of %d (%s)",
784                            tasktype,
785                            event.stats.completed + event.stats.active +
786                                event.stats.failed + 1,
787                            event.stats.total, event.taskstring)
788                continue
789
790            if isinstance(event, bb.runqueue.runQueueTaskFailed):
791                return_value = 1
792                taskfailures.append(event.taskstring)
793                logger.error(str(event))
794                continue
795
796            if isinstance(event, bb.runqueue.sceneQueueTaskFailed):
797                logger.warning(str(event))
798                continue
799
800            if isinstance(event, bb.event.DepTreeGenerated):
801                continue
802
803            if isinstance(event, bb.event.ProcessStarted):
804                if params.options.quiet > 1:
805                    continue
806                termfilter.clearFooter()
807                parseprogress = new_progress(event.processname, event.total)
808                parseprogress.start(False)
809                continue
810            if isinstance(event, bb.event.ProcessProgress):
811                if params.options.quiet > 1:
812                    continue
813                if parseprogress:
814                    parseprogress.update(event.progress)
815                else:
816                    bb.warn("Got ProcessProgress event for someting that never started?")
817                continue
818            if isinstance(event, bb.event.ProcessFinished):
819                if params.options.quiet > 1:
820                    continue
821                if parseprogress:
822                    parseprogress.finish()
823                parseprogress = None
824                continue
825
826            # ignore
827            if isinstance(event, (bb.event.BuildBase,
828                                  bb.event.MetadataEvent,
829                                  bb.event.ConfigParsed,
830                                  bb.event.MultiConfigParsed,
831                                  bb.event.RecipeParsed,
832                                  bb.event.RecipePreFinalise,
833                                  bb.runqueue.runQueueEvent,
834                                  bb.event.OperationStarted,
835                                  bb.event.OperationCompleted,
836                                  bb.event.OperationProgress,
837                                  bb.event.DiskFull,
838                                  bb.event.HeartbeatEvent,
839                                  bb.build.TaskProgress)):
840                continue
841
842            logger.error("Unknown event: %s", event)
843
844        except EnvironmentError as ioerror:
845            termfilter.clearFooter()
846            # ignore interrupted io
847            if ioerror.args[0] == 4:
848                continue
849            sys.stderr.write(str(ioerror))
850            if not params.observe_only:
851                _, error = server.runCommand(["stateForceShutdown"])
852            main.shutdown = 2
853        except KeyboardInterrupt:
854            termfilter.clearFooter()
855            if params.observe_only:
856                print("\nKeyboard Interrupt, exiting observer...")
857                main.shutdown = 2
858
859            def state_force_shutdown():
860                print("\nSecond Keyboard Interrupt, stopping...\n")
861                _, error = server.runCommand(["stateForceShutdown"])
862                if error:
863                    logger.error("Unable to cleanly stop: %s" % error)
864
865            if not params.observe_only and main.shutdown == 1:
866                state_force_shutdown()
867
868            if not params.observe_only and main.shutdown == 0:
869                print("\nKeyboard Interrupt, closing down...\n")
870                interrupted = True
871                # Capture the second KeyboardInterrupt during stateShutdown is running
872                try:
873                    _, error = server.runCommand(["stateShutdown"])
874                    if error:
875                        logger.error("Unable to cleanly shutdown: %s" % error)
876                except KeyboardInterrupt:
877                    state_force_shutdown()
878
879            main.shutdown = main.shutdown + 1
880        except Exception as e:
881            import traceback
882            sys.stderr.write(traceback.format_exc())
883            if not params.observe_only:
884                _, error = server.runCommand(["stateForceShutdown"])
885            main.shutdown = 2
886            return_value = 1
887    try:
888        termfilter.clearFooter()
889        summary = ""
890        if taskfailures:
891            summary += pluralise("\nSummary: %s task failed:",
892                                 "\nSummary: %s tasks failed:", len(taskfailures))
893            for failure in taskfailures:
894                summary += "\n  %s" % failure
895        if warnings:
896            summary += pluralise("\nSummary: There was %s WARNING message.",
897                                 "\nSummary: There were %s WARNING messages.", warnings)
898        if return_value and errors:
899            summary += pluralise("\nSummary: There was %s ERROR message, returning a non-zero exit code.",
900                                 "\nSummary: There were %s ERROR messages, returning a non-zero exit code.", errors)
901        if summary and params.options.quiet == 0:
902            print(summary)
903
904        if interrupted:
905            print("Execution was interrupted, returning a non-zero exit code.")
906            if return_value == 0:
907                return_value = 1
908    except IOError as e:
909        import errno
910        if e.errno == errno.EPIPE:
911            pass
912
913    logging.shutdown()
914
915    return return_value
916