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