diff options
Diffstat (limited to 'poky/bitbake/lib/bb/ui')
-rw-r--r-- | poky/bitbake/lib/bb/ui/buildinfohelper.py | 8 | ||||
-rw-r--r-- | poky/bitbake/lib/bb/ui/knotty.py | 252 | ||||
-rw-r--r-- | poky/bitbake/lib/bb/ui/ncurses.py | 5 | ||||
-rw-r--r-- | poky/bitbake/lib/bb/ui/taskexp.py | 4 | ||||
-rw-r--r-- | poky/bitbake/lib/bb/ui/teamcity.py | 398 | ||||
-rw-r--r-- | poky/bitbake/lib/bb/ui/toasterui.py | 2 | ||||
-rw-r--r-- | poky/bitbake/lib/bb/ui/uievent.py | 2 | ||||
-rw-r--r-- | poky/bitbake/lib/bb/ui/uihelper.py | 39 |
8 files changed, 619 insertions, 91 deletions
diff --git a/poky/bitbake/lib/bb/ui/buildinfohelper.py b/poky/bitbake/lib/bb/ui/buildinfohelper.py index 5cbca97f3..82c62e332 100644 --- a/poky/bitbake/lib/bb/ui/buildinfohelper.py +++ b/poky/bitbake/lib/bb/ui/buildinfohelper.py @@ -935,7 +935,7 @@ class BuildInfoHelper(object): # only reset the build name if the one on the server is actually # a valid value for the build_name field - if build_name != None: + if build_name is not None: build_info['build_name'] = build_name changed = True @@ -1194,7 +1194,7 @@ class BuildInfoHelper(object): evdata = BuildInfoHelper._get_data_from_event(event) for t in self.internal_state['targets']: - if t.is_image == True: + if t.is_image: output_files = list(evdata.keys()) for output in output_files: if t.target in output and 'rootfs' in output and not output.endswith(".manifest"): @@ -1236,7 +1236,7 @@ class BuildInfoHelper(object): task_information['outcome'] = Task.OUTCOME_PREBUILT else: task_information['task_executed'] = True - if 'noexec' in vars(event) and event.noexec == True: + if 'noexec' in vars(event) and event.noexec: task_information['task_executed'] = False task_information['outcome'] = Task.OUTCOME_EMPTY task_information['script_type'] = Task.CODING_NA @@ -1776,7 +1776,7 @@ class BuildInfoHelper(object): image_file_extensions_unique = {} image_fstypes = self.server.runCommand( ['getVariable', 'IMAGE_FSTYPES'])[0] - if image_fstypes != None: + if image_fstypes is not None: image_types_str = image_fstypes.strip() image_file_extensions = re.sub(r' {2,}', ' ', image_types_str) image_file_extensions_unique = set(image_file_extensions.split(' ')) diff --git a/poky/bitbake/lib/bb/ui/knotty.py b/poky/bitbake/lib/bb/ui/knotty.py index 35736ade0..87e873d64 100644 --- a/poky/bitbake/lib/bb/ui/knotty.py +++ b/poky/bitbake/lib/bb/ui/knotty.py @@ -12,7 +12,6 @@ from __future__ import division import os import sys -import xmlrpc.client as xmlrpclib import logging import progressbar import signal @@ -35,15 +34,15 @@ class BBProgress(progressbar.ProgressBar): self.msg = msg self.extrapos = extrapos if not widgets: - widgets = [progressbar.Percentage(), ' ', progressbar.Bar(), ' ', - progressbar.ETA()] - self.extrapos = 4 + 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) + 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) @@ -110,12 +109,11 @@ def pluralise(singular, plural, qty): class InteractConsoleLogFilter(logging.Filter): - def __init__(self, tf, format): + def __init__(self, tf): self.tf = tf - self.format = format def filter(self, record): - if record.levelno == self.format.NOTE and (record.msg.startswith("Running") or record.msg.startswith("recipe ")): + if record.levelno == bb.msg.BBLogFormatter.NOTE and (record.msg.startswith("Running") or record.msg.startswith("recipe ")): return False self.tf.clearFooter() return True @@ -151,7 +149,7 @@ class TerminalFilter(object): cr = (25, 80) return cr - def __init__(self, main, helper, console, errconsole, format, quiet): + def __init__(self, main, helper, handlers, quiet): self.main = main self.helper = helper self.cuu = None @@ -181,7 +179,11 @@ class TerminalFilter(object): termios.tcsetattr(fd, termios.TCSADRAIN, new) curses.setupterm() if curses.tigetnum("colors") > 2: - format.enable_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") @@ -197,10 +199,9 @@ class TerminalFilter(object): self.interactive = False bb.note("Unable to use interactive mode for this terminal, using fallback") return - if console: - console.addFilter(InteractConsoleLogFilter(self, format)) - if errconsole: - errconsole.addFilter(InteractConsoleLogFilter(self, format)) + + for h in handlers: + h.addFilter(InteractConsoleLogFilter(self)) self.main_progress = None @@ -255,19 +256,19 @@ class TerminalFilter(object): start_time = activetasks[t].get("starttime", None) if not pbar or pbar.bouncing != (progress < 0): if progress < 0: - pbar = BBProgress("0: %s (pid %s) " % (activetasks[t]["title"], t), 100, widgets=[progressbar.BouncingSlider(), ''], extrapos=2, resize_handler=self.sigwinch_handle) + pbar = BBProgress("0: %s (pid %s)" % (activetasks[t]["title"], activetasks[t]["pid"]), 100, widgets=[' ', progressbar.BouncingSlider(), ''], extrapos=3, resize_handler=self.sigwinch_handle) pbar.bouncing = True else: - pbar = BBProgress("0: %s (pid %s) " % (activetasks[t]["title"], t), 100, widgets=[progressbar.Percentage(), ' ', progressbar.Bar(), ''], extrapos=4, resize_handler=self.sigwinch_handle) + pbar = BBProgress("0: %s (pid %s)" % (activetasks[t]["title"], activetasks[t]["pid"]), 100, widgets=[' ', progressbar.Percentage(), ' ', progressbar.Bar(), ''], extrapos=5, resize_handler=self.sigwinch_handle) pbar.bouncing = False activetasks[t]["progressbar"] = pbar tasks.append((pbar, progress, rate, start_time)) else: start_time = activetasks[t].get("starttime", None) if start_time: - tasks.append("%s - %s (pid %s)" % (activetasks[t]["title"], self.elapsed(currenttime - start_time), t)) + tasks.append("%s - %s (pid %s)" % (activetasks[t]["title"], self.elapsed(currenttime - start_time), activetasks[t]["pid"])) else: - tasks.append("%s (pid %s)" % (activetasks[t]["title"], t)) + tasks.append("%s (pid %s)" % (activetasks[t]["title"], activetasks[t]["pid"])) if self.main.shutdown: content = "Waiting for %s running tasks to finish:" % len(activetasks) @@ -363,7 +364,11 @@ def _log_settings_from_server(server, observe_only): if error: logger.error("Unable to get the value of BB_CONSOLELOG variable: %s" % error) raise BaseException(error) - return includelogs, loglines, consolelogfile + 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", @@ -380,7 +385,148 @@ def main(server, eventHandler, params, tf = TerminalFilter): if not params.observe_only: params.updateToServer(server, os.environ.copy()) - includelogs, loglines, consolelogfile = _log_settings_from_server(server, params.observe_only) + includelogs, loglines, consolelogfile, logconfigfile = _log_settings_from_server(server, params.observe_only) + + loglevel, _ = bb.msg.constructLogOptions() + + 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 + + conf = bb.msg.setLoggingConfig(logconfig, logconfigfile) if sys.stdin.isatty() and sys.stdout.isatty(): log_exec_tty = True @@ -389,23 +535,9 @@ def main(server, eventHandler, params, tf = TerminalFilter): helper = uihelper.BBUIHelper() - console = logging.StreamHandler(sys.stdout) - errconsole = logging.StreamHandler(sys.stderr) - format_str = "%(levelname)s: %(message)s" - format = bb.msg.BBLogFormatter(format_str) - if params.options.quiet == 0: - forcelevel = None - elif params.options.quiet > 2: - forcelevel = bb.msg.BBLogFormatter.ERROR - else: - forcelevel = bb.msg.BBLogFormatter.WARNING - bb.msg.addDefaultlogFilter(console, bb.msg.BBLogFilterStdOut, forcelevel) - bb.msg.addDefaultlogFilter(errconsole, bb.msg.BBLogFilterStdErr) - console.setFormatter(format) - errconsole.setFormatter(format) - if not bb.msg.has_console_handler(logger): - logger.addHandler(console) - logger.addHandler(errconsole) + # 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") @@ -413,24 +545,14 @@ def main(server, eventHandler, params, tf = TerminalFilter): server.terminateServer() return - consolelog = None - if consolelogfile and not params.options.show_environment and not params.options.show_versions: - bb.utils.mkdirhier(os.path.dirname(consolelogfile)) - conlogformat = bb.msg.BBLogFormatter(format_str) - consolelog = logging.FileHandler(consolelogfile) - bb.msg.addDefaultlogFilter(consolelog) - consolelog.setFormatter(conlogformat) - logger.addHandler(consolelog) - 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 - llevel, debug_domains = bb.msg.constructLogOptions() server.runCommand(["setEventMask", server.getEventHandle(), llevel, debug_domains, _evt_list]) + # 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: params.updateFromServer(server) @@ -448,7 +570,7 @@ def main(server, eventHandler, params, tf = TerminalFilter): if error: logger.error("Command '%s' failed: %s" % (cmdline, error)) return 1 - elif ret != True: + elif not ret: logger.error("Command '%s' failed: returned %s" % (cmdline, ret)) return 1 @@ -465,7 +587,7 @@ def main(server, eventHandler, params, tf = TerminalFilter): printinterval = 5000 lastprint = time.time() - termfilter = tf(main, helper, console, errconsole, format, params.options.quiet) + termfilter = tf(main, helper, console_handlers, params.options.quiet) atexit.register(termfilter.finish) while True: @@ -477,7 +599,8 @@ def main(server, eventHandler, params, tf = TerminalFilter): if event is None: if main.shutdown > 1: break - termfilter.updateFooter() + if not parseprogress: + termfilter.updateFooter() event = eventHandler.waitEvent(0.25) if event is None: continue @@ -503,26 +626,26 @@ def main(server, eventHandler, params, tf = TerminalFilter): if isinstance(event, logging.LogRecord): lastprint = time.time() printinterval = 5000 - if event.levelno >= format.ERROR: + if event.levelno >= bb.msg.BBLogFormatter.ERROR: errors = errors + 1 return_value = 1 - elif event.levelno == format.WARNING: + 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 <= format.NOTE and (event.levelno < llevel or (event.levelno == format.NOTE and llevel != format.VERBOSE)): + 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.running_tasks and event.levelno != format.PLAIN: - taskinfo = helper.running_tasks[event.taskpid] + if event.taskpid in helper.pidmap and event.levelno != bb.msg.BBLogFormatter.PLAIN: + taskinfo = helper.running_tasks[helper.pidmap[event.taskpid]] event.msg = taskinfo['title'] + ': ' + event.msg if hasattr(event, 'fn'): event.msg = event.fn + ': ' + event.msg - logger.handle(event) + logging.getLogger(event.name).handle(event) continue if isinstance(event, bb.build.TaskFailedSilent): @@ -539,6 +662,7 @@ def main(server, eventHandler, params, tf = TerminalFilter): continue if event.total == 0: continue + termfilter.clearFooter() parseprogress = new_progress("Parsing recipes", event.total).start() continue if isinstance(event, bb.event.ParseProgress): @@ -589,6 +713,7 @@ def main(server, eventHandler, params, tf = TerminalFilter): if isinstance(event, bb.command.CommandExit): if not return_value: return_value = event.exitcode + main.shutdown = 2 continue if isinstance(event, (bb.command.CommandCompleted, bb.cooker.CookerExit)): main.shutdown = 2 @@ -638,6 +763,7 @@ def main(server, eventHandler, params, tf = TerminalFilter): 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 @@ -745,8 +871,6 @@ def main(server, eventHandler, params, tf = TerminalFilter): if e.errno == errno.EPIPE: pass - if consolelog: - logger.removeHandler(consolelog) - consolelog.close() + logging.shutdown() return return_value diff --git a/poky/bitbake/lib/bb/ui/ncurses.py b/poky/bitbake/lib/bb/ui/ncurses.py index c422732b2..da4fbeabb 100644 --- a/poky/bitbake/lib/bb/ui/ncurses.py +++ b/poky/bitbake/lib/bb/ui/ncurses.py @@ -37,7 +37,7 @@ import logging -import os, sys, itertools, time, subprocess +import os, sys, itertools, time try: import curses @@ -46,7 +46,6 @@ except ImportError: import bb import xmlrpc.client -from bb import ui from bb.ui import uihelper parsespin = itertools.cycle( r'|/-\\' ) @@ -239,7 +238,7 @@ class NCursesUI: if error: print("Error running command '%s': %s" % (cmdline, error)) return - elif ret != True: + elif not ret: print("Couldn't get default commandlind! %s" % ret) return except xmlrpc.client.Fault as x: diff --git a/poky/bitbake/lib/bb/ui/taskexp.py b/poky/bitbake/lib/bb/ui/taskexp.py index 50a943cd0..8fff24423 100644 --- a/poky/bitbake/lib/bb/ui/taskexp.py +++ b/poky/bitbake/lib/bb/ui/taskexp.py @@ -11,10 +11,8 @@ import sys import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk, Gdk, GObject -from multiprocessing import Queue import threading from xmlrpc import client -import time import bb import bb.event @@ -202,7 +200,7 @@ def main(server, eventHandler, params): if error: print("Error running command '%s': %s" % (cmdline, error)) return 1 - elif ret != True: + elif not ret: print("Error running command '%s': returned %s" % (cmdline, ret)) return 1 except client.Fault as x: diff --git a/poky/bitbake/lib/bb/ui/teamcity.py b/poky/bitbake/lib/bb/ui/teamcity.py new file mode 100644 index 000000000..1854292fa --- /dev/null +++ b/poky/bitbake/lib/bb/ui/teamcity.py @@ -0,0 +1,398 @@ +# +# TeamCity UI Implementation +# +# Implements a TeamCity frontend for the BitBake utility, via service messages. +# See https://www.jetbrains.com/help/teamcity/build-script-interaction-with-teamcity.html +# +# Based on ncurses.py and knotty.py, variously by Michael Lauer and Richard Purdie +# +# Copyright (C) 2006 Michael 'Mickey' Lauer +# Copyright (C) 2006-2012 Richard Purdie +# Copyright (C) 2018-2020 Agilent Technologies, Inc. +# +# SPDX-License-Identifier: GPL-2.0-only +# +# Author: Chris Laplante <chris.laplante@agilent.com> + +from __future__ import division + +import datetime +import logging +import math +import os +import re +import sys +import xmlrpc.client +from collections import deque + +import bb +import bb.build +import bb.command +import bb.cooker +import bb.event +import bb.exceptions +import bb.runqueue +from bb.ui import uihelper + +logger = logging.getLogger("BitBake") + + +class TeamCityUI: + def __init__(self): + self._block_stack = [] + self._last_progress_state = None + + @classmethod + def escape_service_value(cls, value): + """ + Escape a value for inclusion in a service message. TeamCity uses the vertical pipe character for escaping. + See: https://confluence.jetbrains.com/display/TCD10/Build+Script+Interaction+with+TeamCity#BuildScriptInteractionwithTeamCity-Escapedvalues + """ + return re.sub(r"(['|\[\]])", r"|\1", value).replace("\n", "|n").replace("\r", "|r") + + @classmethod + def emit_service_message(cls, message_type, **kwargs): + print(cls.format_service_message(message_type, **kwargs), flush=True) + + @classmethod + def format_service_message(cls, message_type, **kwargs): + payload = " ".join(["{0}='{1}'".format(k, cls.escape_service_value(v)) for k, v in kwargs.items()]) + return "##teamcity[{0} {1}]".format(message_type, payload) + + @classmethod + def emit_simple_service_message(cls, message_type, message): + print(cls.format_simple_service_message(message_type, message), flush=True) + + @classmethod + def format_simple_service_message(cls, message_type, message): + return "##teamcity[{0} '{1}']".format(message_type, cls.escape_service_value(message)) + + @classmethod + def format_build_message(cls, text, status): + return cls.format_service_message("message", text=text, status=status) + + def block_start(self, name): + self._block_stack.append(name) + self.emit_service_message("blockOpened", name=name) + + def block_end(self): + if self._block_stack: + name = self._block_stack.pop() + self.emit_service_message("blockClosed", name=name) + + def progress(self, message, percent, extra=None): + now = datetime.datetime.now() + percent = "{0: >3.0f}".format(percent) + + report = False + if not self._last_progress_state \ + or (self._last_progress_state[0] == message + and self._last_progress_state[1] != percent + and (now - self._last_progress_state[2]).microseconds >= 5000) \ + or self._last_progress_state[0] != message: + report = True + self._last_progress_state = (message, percent, now) + + if report or percent in [0, 100]: + self.emit_simple_service_message("progressMessage", "{0}: {1}%{2}".format(message, percent, extra or "")) + + +class TeamcityLogFormatter(logging.Formatter): + def format(self, record): + details = "" + if hasattr(record, 'bb_exc_formatted'): + details = ''.join(record.bb_exc_formatted) + elif hasattr(record, 'bb_exc_info'): + etype, value, tb = record.bb_exc_info + formatted = bb.exceptions.format_exception(etype, value, tb, limit=5) + details = ''.join(formatted) + + if record.levelno in [bb.msg.BBLogFormatter.ERROR, bb.msg.BBLogFormatter.CRITICAL]: + # ERROR gets a separate errorDetails field + msg = TeamCityUI.format_service_message("message", text=record.getMessage(), status="ERROR", + errorDetails=details) + else: + payload = record.getMessage() + if details: + payload += "\n" + details + if record.levelno == bb.msg.BBLogFormatter.PLAIN: + msg = payload + elif record.levelno == bb.msg.BBLogFormatter.WARNING: + msg = TeamCityUI.format_service_message("message", text=payload, status="WARNING") + else: + msg = TeamCityUI.format_service_message("message", text=payload, status="NORMAL") + + return msg + + +_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 _log_settings_from_server(server): + # 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) + return includelogs, loglines + + +def main(server, eventHandler, params): + params.updateToServer(server, os.environ.copy()) + + includelogs, loglines = _log_settings_from_server(server) + + ui = TeamCityUI() + + helper = uihelper.BBUIHelper() + + console = logging.StreamHandler(sys.stdout) + errconsole = logging.StreamHandler(sys.stderr) + format = TeamcityLogFormatter() + if params.options.quiet == 0: + forcelevel = None + elif params.options.quiet > 2: + forcelevel = bb.msg.BBLogFormatter.ERROR + else: + forcelevel = bb.msg.BBLogFormatter.WARNING + bb.msg.addDefaultlogFilter(console, bb.msg.BBLogFilterStdOut, forcelevel) + bb.msg.addDefaultlogFilter(errconsole, bb.msg.BBLogFilterStdErr) + console.setFormatter(format) + errconsole.setFormatter(format) + if not bb.msg.has_console_handler(logger): + logger.addHandler(console) + logger.addHandler(errconsole) + + if params.options.remote_server and params.options.kill_server: + server.terminateServer() + return + + if params.observe_only: + logger.error("Observe-only mode not supported in this UI") + return 1 + + llevel, debug_domains = bb.msg.constructLogOptions() + server.runCommand(["setEventMask", server.getEventHandle(), llevel, debug_domains, _evt_list]) + + try: + params.updateFromServer(server) + cmdline = params.parseActions() + if not cmdline: + logger.error("No task given") + return 1 + if 'msg' in cmdline and cmdline['msg']: + logger.error(cmdline['msg']) + return 1 + cmdline = cmdline['action'] + ret, error = server.runCommand(cmdline) + if error: + logger.error("{0}: {1}".format(cmdline, error)) + return 1 + elif not ret: + logger.error("Couldn't get default commandline: {0}".format(re)) + return 1 + except xmlrpc.client.Fault as x: + logger.error("XMLRPC Fault getting commandline: {0}".format(x)) + return 1 + + active_process_total = None + is_tasks_running = False + + while True: + try: + event = eventHandler.waitEvent(0.25) + if not event: + continue + + helper.eventHandler(event) + + if isinstance(event, bb.build.TaskBase): + logger.info(event._message) + if isinstance(event, logging.LogRecord): + # Don't report sstate failures as errors, since Yocto will just run the tasks for real + if event.msg == "No suitable staging package found" or (event.msg.startswith( + "Fetcher failure: Unable to find file") and "downloadfilename" in event.msg and "sstate" in event.msg): + event.levelno = bb.msg.BBLogFormatter.WARNING + 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.running_tasks and event.levelno != bb.msg.BBLogFormatter.PLAIN: + taskinfo = helper.running_tasks[event.taskpid] + event.msg = taskinfo['title'] + ': ' + event.msg + if hasattr(event, 'fn'): + event.msg = event.fn + ': ' + event.msg + logger.handle(event) + if isinstance(event, bb.build.TaskFailedSilent): + logger.warning("Logfile for failed setscene task is %s" % event.logfile) + continue + if isinstance(event, bb.build.TaskFailed): + rt = "{0}-{1}:{2}".format(event.pn, event.pv.replace("AUTOINC", "0"), event.task) + + logfile = event.logfile + if not logfile or not os.path.exists(logfile): + TeamCityUI.emit_service_message("buildProblem", description="{0}\nUnknown failure (no log file available)".format(rt)) + if not event.task.endswith("_setscene"): + server.runCommand(["stateForceShutdown"]) + continue + + details = deque(maxlen=loglines) + error_lines = [] + if includelogs and not event.errprinted: + with open(logfile, "r") as f: + while True: + line = f.readline() + if not line: + break + line = line.rstrip() + details.append(' | %s' % line) + # TODO: a less stupid check for errors + if (event.task == "do_compile") and ("error:" in line): + error_lines.append(line) + + if error_lines: + TeamCityUI.emit_service_message("compilationStarted", compiler=rt) + for line in error_lines: + TeamCityUI.emit_service_message("message", text=line, status="ERROR") + TeamCityUI.emit_service_message("compilationFinished", compiler=rt) + else: + TeamCityUI.emit_service_message("buildProblem", description=rt) + + err = "Logfile of failure stored in: %s" % logfile + if details: + ui.block_start("{0} task log".format(rt)) + # TeamCity seems to choke on service messages longer than about 63800 characters, so if error + # details is longer than, say, 60000, batch it up into several messages. + first_message = True + while details: + detail_len = 0 + batch = deque() + while details and detail_len < 60000: + # TODO: This code doesn't bother to handle lines that themselves are extremely long. + line = details.popleft() + batch.append(line) + detail_len += len(line) + + if first_message: + batch.appendleft("Log data follows:") + first_message = False + TeamCityUI.emit_service_message("message", text=err, status="ERROR", + errorDetails="\n".join(batch)) + else: + TeamCityUI.emit_service_message("message", text="[continued]", status="ERROR", + errorDetails="\n".join(batch)) + ui.block_end() + else: + TeamCityUI.emit_service_message("message", text=err, status="ERROR", errorDetails="") + + if not event.task.endswith("_setscene"): + server.runCommand(["stateForceShutdown"]) + + if isinstance(event, bb.event.ProcessStarted): + if event.processname in ["Initialising tasks", "Checking sstate mirror object availability"]: + active_process_total = event.total + ui.block_start(event.processname) + if isinstance(event, bb.event.ProcessFinished): + if event.processname in ["Initialising tasks", "Checking sstate mirror object availability"]: + ui.progress(event.processname, 100) + ui.block_end() + if isinstance(event, bb.event.ProcessProgress): + if event.processname in ["Initialising tasks", + "Checking sstate mirror object availability"] and active_process_total != 0: + ui.progress(event.processname, event.progress * 100 / active_process_total) + if isinstance(event, bb.event.CacheLoadStarted): + ui.block_start("Loading cache") + if isinstance(event, bb.event.CacheLoadProgress): + if event.total != 0: + ui.progress("Loading cache", math.floor(event.current * 100 / event.total)) + if isinstance(event, bb.event.CacheLoadCompleted): + ui.progress("Loading cache", 100) + ui.block_end() + if isinstance(event, bb.event.ParseStarted): + ui.block_start("Parsing recipes and checking upstream revisions") + if isinstance(event, bb.event.ParseProgress): + if event.total != 0: + ui.progress("Parsing recipes", math.floor(event.current * 100 / event.total)) + if isinstance(event, bb.event.ParseCompleted): + ui.progress("Parsing recipes", 100) + ui.block_end() + if isinstance(event, bb.command.CommandCompleted): + return + if isinstance(event, bb.command.CommandFailed): + logger.error(str(event)) + return 1 + if isinstance(event, bb.event.MultipleProviders): + logger.warning(str(event)) + continue + if isinstance(event, bb.event.NoProvider): + logger.error(str(event)) + continue + if isinstance(event, bb.command.CommandExit): + return + if isinstance(event, bb.cooker.CookerExit): + return + if isinstance(event, bb.runqueue.sceneQueueTaskStarted): + if not is_tasks_running: + is_tasks_running = True + ui.block_start("Running tasks") + if event.stats.total != 0: + ui.progress("Running setscene tasks", ( + event.stats.completed + event.stats.active + event.stats.failed + 1) * 100 / event.stats.total) + if isinstance(event, bb.runqueue.runQueueTaskStarted): + if not is_tasks_running: + is_tasks_running = True + ui.block_start("Running tasks") + if event.stats.total != 0: + pseudo_total = event.stats.total - event.stats.skipped + pseudo_complete = event.stats.completed + event.stats.active - event.stats.skipped + event.stats.failed + 1 + # TODO: sometimes this gives over 100% + ui.progress("Running runqueue tasks", (pseudo_complete) * 100 / pseudo_total, + " ({0}/{1})".format(pseudo_complete, pseudo_total)) + if isinstance(event, bb.runqueue.sceneQueueTaskFailed): + logger.warning(str(event)) + continue + if isinstance(event, bb.runqueue.runQueueTaskFailed): + logger.error(str(event)) + return 1 + if isinstance(event, bb.event.LogExecTTY): + pass + except EnvironmentError as ioerror: + # ignore interrupted io + if ioerror.args[0] == 4: + pass + except Exception as ex: + logger.error(str(ex)) + + # except KeyboardInterrupt: + # if shutdown == 2: + # mw.appendText("Third Keyboard Interrupt, exit.\n") + # exitflag = True + # if shutdown == 1: + # mw.appendText("Second Keyboard Interrupt, stopping...\n") + # _, error = server.runCommand(["stateForceShutdown"]) + # if error: + # print("Unable to cleanly stop: %s" % error) + # if shutdown == 0: + # mw.appendText("Keyboard Interrupt, closing down...\n") + # _, error = server.runCommand(["stateShutdown"]) + # if error: + # print("Unable to cleanly shutdown: %s" % error) + # shutdown = shutdown + 1 + # pass diff --git a/poky/bitbake/lib/bb/ui/toasterui.py b/poky/bitbake/lib/bb/ui/toasterui.py index 51892c9a0..9260f5d9d 100644 --- a/poky/bitbake/lib/bb/ui/toasterui.py +++ b/poky/bitbake/lib/bb/ui/toasterui.py @@ -176,7 +176,7 @@ def main(server, eventHandler, params): if error: logger.error("Command '%s' failed: %s" % (cmdline, error)) return 1 - elif ret != True: + elif not ret: logger.error("Command '%s' failed: returned %s" % (cmdline, ret)) return 1 diff --git a/poky/bitbake/lib/bb/ui/uievent.py b/poky/bitbake/lib/bb/ui/uievent.py index fedb05064..13d0d4a04 100644 --- a/poky/bitbake/lib/bb/ui/uievent.py +++ b/poky/bitbake/lib/bb/ui/uievent.py @@ -46,7 +46,7 @@ class BBUIEventQueue: self.EventHandle = ret error = "" - if self.EventHandle != None: + if self.EventHandle is not None: break errmsg = "Could not register UI event handler. Error: %s, host %s, "\ diff --git a/poky/bitbake/lib/bb/ui/uihelper.py b/poky/bitbake/lib/bb/ui/uihelper.py index c8dd7df08..48d808ae2 100644 --- a/poky/bitbake/lib/bb/ui/uihelper.py +++ b/poky/bitbake/lib/bb/ui/uihelper.py @@ -15,39 +15,48 @@ class BBUIHelper: # Running PIDs preserves the order tasks were executed in self.running_pids = [] self.failed_tasks = [] + self.pidmap = {} self.tasknumber_current = 0 self.tasknumber_total = 0 def eventHandler(self, event): + # PIDs are a bad idea as they can be reused before we process all UI events. + # We maintain a 'fuzzy' match for TaskProgress since there is no other way to match + def removetid(pid, tid): + self.running_pids.remove(tid) + del self.running_tasks[tid] + if self.pidmap[pid] == tid: + del self.pidmap[pid] + self.needUpdate = True + if isinstance(event, bb.build.TaskStarted): + tid = event._fn + ":" + event._task if event._mc != "default": - self.running_tasks[event.pid] = { 'title' : "mc:%s:%s %s" % (event._mc, event._package, event._task), 'starttime' : time.time() } + self.running_tasks[tid] = { 'title' : "mc:%s:%s %s" % (event._mc, event._package, event._task), 'starttime' : time.time(), 'pid' : event.pid } else: - self.running_tasks[event.pid] = { 'title' : "%s %s" % (event._package, event._task), 'starttime' : time.time() } - self.running_pids.append(event.pid) + self.running_tasks[tid] = { 'title' : "%s %s" % (event._package, event._task), 'starttime' : time.time(), 'pid' : event.pid } + self.running_pids.append(tid) + self.pidmap[event.pid] = tid self.needUpdate = True elif isinstance(event, bb.build.TaskSucceeded): - del self.running_tasks[event.pid] - self.running_pids.remove(event.pid) - self.needUpdate = True + tid = event._fn + ":" + event._task + removetid(event.pid, tid) elif isinstance(event, bb.build.TaskFailedSilent): - del self.running_tasks[event.pid] - self.running_pids.remove(event.pid) + tid = event._fn + ":" + event._task + removetid(event.pid, tid) # Don't add to the failed tasks list since this is e.g. a setscene task failure - self.needUpdate = True elif isinstance(event, bb.build.TaskFailed): - del self.running_tasks[event.pid] - self.running_pids.remove(event.pid) + tid = event._fn + ":" + event._task + removetid(event.pid, tid) self.failed_tasks.append( { 'title' : "%s %s" % (event._package, event._task)}) - self.needUpdate = True elif isinstance(event, bb.runqueue.runQueueTaskStarted): self.tasknumber_current = event.stats.completed + event.stats.active + event.stats.failed + 1 self.tasknumber_total = event.stats.total self.needUpdate = True elif isinstance(event, bb.build.TaskProgress): - if event.pid > 0: - self.running_tasks[event.pid]['progress'] = event.progress - self.running_tasks[event.pid]['rate'] = event.rate + if event.pid > 0 and event.pid in self.pidmap: + self.running_tasks[self.pidmap[event.pid]]['progress'] = event.progress + self.running_tasks[self.pidmap[event.pid]]['rate'] = event.rate self.needUpdate = True else: return False |