1*4882a593Smuzhiyun# 2*4882a593Smuzhiyun# TeamCity UI Implementation 3*4882a593Smuzhiyun# 4*4882a593Smuzhiyun# Implements a TeamCity frontend for the BitBake utility, via service messages. 5*4882a593Smuzhiyun# See https://www.jetbrains.com/help/teamcity/build-script-interaction-with-teamcity.html 6*4882a593Smuzhiyun# 7*4882a593Smuzhiyun# Based on ncurses.py and knotty.py, variously by Michael Lauer and Richard Purdie 8*4882a593Smuzhiyun# 9*4882a593Smuzhiyun# Copyright (C) 2006 Michael 'Mickey' Lauer 10*4882a593Smuzhiyun# Copyright (C) 2006-2012 Richard Purdie 11*4882a593Smuzhiyun# Copyright (C) 2018-2020 Agilent Technologies, Inc. 12*4882a593Smuzhiyun# 13*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 14*4882a593Smuzhiyun# 15*4882a593Smuzhiyun# Author: Chris Laplante <chris.laplante@agilent.com> 16*4882a593Smuzhiyun 17*4882a593Smuzhiyunfrom __future__ import division 18*4882a593Smuzhiyun 19*4882a593Smuzhiyunimport datetime 20*4882a593Smuzhiyunimport logging 21*4882a593Smuzhiyunimport math 22*4882a593Smuzhiyunimport os 23*4882a593Smuzhiyunimport re 24*4882a593Smuzhiyunimport sys 25*4882a593Smuzhiyunimport xmlrpc.client 26*4882a593Smuzhiyunfrom collections import deque 27*4882a593Smuzhiyun 28*4882a593Smuzhiyunimport bb 29*4882a593Smuzhiyunimport bb.build 30*4882a593Smuzhiyunimport bb.command 31*4882a593Smuzhiyunimport bb.cooker 32*4882a593Smuzhiyunimport bb.event 33*4882a593Smuzhiyunimport bb.exceptions 34*4882a593Smuzhiyunimport bb.runqueue 35*4882a593Smuzhiyunfrom bb.ui import uihelper 36*4882a593Smuzhiyun 37*4882a593Smuzhiyunlogger = logging.getLogger("BitBake") 38*4882a593Smuzhiyun 39*4882a593Smuzhiyun 40*4882a593Smuzhiyunclass TeamCityUI: 41*4882a593Smuzhiyun def __init__(self): 42*4882a593Smuzhiyun self._block_stack = [] 43*4882a593Smuzhiyun self._last_progress_state = None 44*4882a593Smuzhiyun 45*4882a593Smuzhiyun @classmethod 46*4882a593Smuzhiyun def escape_service_value(cls, value): 47*4882a593Smuzhiyun """ 48*4882a593Smuzhiyun Escape a value for inclusion in a service message. TeamCity uses the vertical pipe character for escaping. 49*4882a593Smuzhiyun See: https://confluence.jetbrains.com/display/TCD10/Build+Script+Interaction+with+TeamCity#BuildScriptInteractionwithTeamCity-Escapedvalues 50*4882a593Smuzhiyun """ 51*4882a593Smuzhiyun return re.sub(r"(['|\[\]])", r"|\1", value).replace("\n", "|n").replace("\r", "|r") 52*4882a593Smuzhiyun 53*4882a593Smuzhiyun @classmethod 54*4882a593Smuzhiyun def emit_service_message(cls, message_type, **kwargs): 55*4882a593Smuzhiyun print(cls.format_service_message(message_type, **kwargs), flush=True) 56*4882a593Smuzhiyun 57*4882a593Smuzhiyun @classmethod 58*4882a593Smuzhiyun def format_service_message(cls, message_type, **kwargs): 59*4882a593Smuzhiyun payload = " ".join(["{0}='{1}'".format(k, cls.escape_service_value(v)) for k, v in kwargs.items()]) 60*4882a593Smuzhiyun return "##teamcity[{0} {1}]".format(message_type, payload) 61*4882a593Smuzhiyun 62*4882a593Smuzhiyun @classmethod 63*4882a593Smuzhiyun def emit_simple_service_message(cls, message_type, message): 64*4882a593Smuzhiyun print(cls.format_simple_service_message(message_type, message), flush=True) 65*4882a593Smuzhiyun 66*4882a593Smuzhiyun @classmethod 67*4882a593Smuzhiyun def format_simple_service_message(cls, message_type, message): 68*4882a593Smuzhiyun return "##teamcity[{0} '{1}']".format(message_type, cls.escape_service_value(message)) 69*4882a593Smuzhiyun 70*4882a593Smuzhiyun @classmethod 71*4882a593Smuzhiyun def format_build_message(cls, text, status): 72*4882a593Smuzhiyun return cls.format_service_message("message", text=text, status=status) 73*4882a593Smuzhiyun 74*4882a593Smuzhiyun def block_start(self, name): 75*4882a593Smuzhiyun self._block_stack.append(name) 76*4882a593Smuzhiyun self.emit_service_message("blockOpened", name=name) 77*4882a593Smuzhiyun 78*4882a593Smuzhiyun def block_end(self): 79*4882a593Smuzhiyun if self._block_stack: 80*4882a593Smuzhiyun name = self._block_stack.pop() 81*4882a593Smuzhiyun self.emit_service_message("blockClosed", name=name) 82*4882a593Smuzhiyun 83*4882a593Smuzhiyun def progress(self, message, percent, extra=None): 84*4882a593Smuzhiyun now = datetime.datetime.now() 85*4882a593Smuzhiyun percent = "{0: >3.0f}".format(percent) 86*4882a593Smuzhiyun 87*4882a593Smuzhiyun report = False 88*4882a593Smuzhiyun if not self._last_progress_state \ 89*4882a593Smuzhiyun or (self._last_progress_state[0] == message 90*4882a593Smuzhiyun and self._last_progress_state[1] != percent 91*4882a593Smuzhiyun and (now - self._last_progress_state[2]).microseconds >= 5000) \ 92*4882a593Smuzhiyun or self._last_progress_state[0] != message: 93*4882a593Smuzhiyun report = True 94*4882a593Smuzhiyun self._last_progress_state = (message, percent, now) 95*4882a593Smuzhiyun 96*4882a593Smuzhiyun if report or percent in [0, 100]: 97*4882a593Smuzhiyun self.emit_simple_service_message("progressMessage", "{0}: {1}%{2}".format(message, percent, extra or "")) 98*4882a593Smuzhiyun 99*4882a593Smuzhiyun 100*4882a593Smuzhiyunclass TeamcityLogFormatter(logging.Formatter): 101*4882a593Smuzhiyun def format(self, record): 102*4882a593Smuzhiyun details = "" 103*4882a593Smuzhiyun if hasattr(record, 'bb_exc_formatted'): 104*4882a593Smuzhiyun details = ''.join(record.bb_exc_formatted) 105*4882a593Smuzhiyun elif hasattr(record, 'bb_exc_info'): 106*4882a593Smuzhiyun etype, value, tb = record.bb_exc_info 107*4882a593Smuzhiyun formatted = bb.exceptions.format_exception(etype, value, tb, limit=5) 108*4882a593Smuzhiyun details = ''.join(formatted) 109*4882a593Smuzhiyun 110*4882a593Smuzhiyun if record.levelno in [bb.msg.BBLogFormatter.ERROR, bb.msg.BBLogFormatter.CRITICAL]: 111*4882a593Smuzhiyun # ERROR gets a separate errorDetails field 112*4882a593Smuzhiyun msg = TeamCityUI.format_service_message("message", text=record.getMessage(), status="ERROR", 113*4882a593Smuzhiyun errorDetails=details) 114*4882a593Smuzhiyun else: 115*4882a593Smuzhiyun payload = record.getMessage() 116*4882a593Smuzhiyun if details: 117*4882a593Smuzhiyun payload += "\n" + details 118*4882a593Smuzhiyun if record.levelno == bb.msg.BBLogFormatter.PLAIN: 119*4882a593Smuzhiyun msg = payload 120*4882a593Smuzhiyun elif record.levelno == bb.msg.BBLogFormatter.WARNING: 121*4882a593Smuzhiyun msg = TeamCityUI.format_service_message("message", text=payload, status="WARNING") 122*4882a593Smuzhiyun else: 123*4882a593Smuzhiyun msg = TeamCityUI.format_service_message("message", text=payload, status="NORMAL") 124*4882a593Smuzhiyun 125*4882a593Smuzhiyun return msg 126*4882a593Smuzhiyun 127*4882a593Smuzhiyun 128*4882a593Smuzhiyun_evt_list = ["bb.runqueue.runQueueExitWait", "bb.event.LogExecTTY", "logging.LogRecord", 129*4882a593Smuzhiyun "bb.build.TaskFailed", "bb.build.TaskBase", "bb.event.ParseStarted", 130*4882a593Smuzhiyun "bb.event.ParseProgress", "bb.event.ParseCompleted", "bb.event.CacheLoadStarted", 131*4882a593Smuzhiyun "bb.event.CacheLoadProgress", "bb.event.CacheLoadCompleted", "bb.command.CommandFailed", 132*4882a593Smuzhiyun "bb.command.CommandExit", "bb.command.CommandCompleted", "bb.cooker.CookerExit", 133*4882a593Smuzhiyun "bb.event.MultipleProviders", "bb.event.NoProvider", "bb.runqueue.sceneQueueTaskStarted", 134*4882a593Smuzhiyun "bb.runqueue.runQueueTaskStarted", "bb.runqueue.runQueueTaskFailed", "bb.runqueue.sceneQueueTaskFailed", 135*4882a593Smuzhiyun "bb.event.BuildBase", "bb.build.TaskStarted", "bb.build.TaskSucceeded", "bb.build.TaskFailedSilent", 136*4882a593Smuzhiyun "bb.build.TaskProgress", "bb.event.ProcessStarted", "bb.event.ProcessProgress", "bb.event.ProcessFinished"] 137*4882a593Smuzhiyun 138*4882a593Smuzhiyun 139*4882a593Smuzhiyundef _log_settings_from_server(server): 140*4882a593Smuzhiyun # Get values of variables which control our output 141*4882a593Smuzhiyun includelogs, error = server.runCommand(["getVariable", "BBINCLUDELOGS"]) 142*4882a593Smuzhiyun if error: 143*4882a593Smuzhiyun logger.error("Unable to get the value of BBINCLUDELOGS variable: %s" % error) 144*4882a593Smuzhiyun raise BaseException(error) 145*4882a593Smuzhiyun loglines, error = server.runCommand(["getVariable", "BBINCLUDELOGS_LINES"]) 146*4882a593Smuzhiyun if error: 147*4882a593Smuzhiyun logger.error("Unable to get the value of BBINCLUDELOGS_LINES variable: %s" % error) 148*4882a593Smuzhiyun raise BaseException(error) 149*4882a593Smuzhiyun return includelogs, loglines 150*4882a593Smuzhiyun 151*4882a593Smuzhiyun 152*4882a593Smuzhiyundef main(server, eventHandler, params): 153*4882a593Smuzhiyun params.updateToServer(server, os.environ.copy()) 154*4882a593Smuzhiyun 155*4882a593Smuzhiyun includelogs, loglines = _log_settings_from_server(server) 156*4882a593Smuzhiyun 157*4882a593Smuzhiyun ui = TeamCityUI() 158*4882a593Smuzhiyun 159*4882a593Smuzhiyun helper = uihelper.BBUIHelper() 160*4882a593Smuzhiyun 161*4882a593Smuzhiyun console = logging.StreamHandler(sys.stdout) 162*4882a593Smuzhiyun errconsole = logging.StreamHandler(sys.stderr) 163*4882a593Smuzhiyun format = TeamcityLogFormatter() 164*4882a593Smuzhiyun if params.options.quiet == 0: 165*4882a593Smuzhiyun forcelevel = None 166*4882a593Smuzhiyun elif params.options.quiet > 2: 167*4882a593Smuzhiyun forcelevel = bb.msg.BBLogFormatter.ERROR 168*4882a593Smuzhiyun else: 169*4882a593Smuzhiyun forcelevel = bb.msg.BBLogFormatter.WARNING 170*4882a593Smuzhiyun console.setFormatter(format) 171*4882a593Smuzhiyun errconsole.setFormatter(format) 172*4882a593Smuzhiyun if not bb.msg.has_console_handler(logger): 173*4882a593Smuzhiyun logger.addHandler(console) 174*4882a593Smuzhiyun logger.addHandler(errconsole) 175*4882a593Smuzhiyun 176*4882a593Smuzhiyun if params.options.remote_server and params.options.kill_server: 177*4882a593Smuzhiyun server.terminateServer() 178*4882a593Smuzhiyun return 179*4882a593Smuzhiyun 180*4882a593Smuzhiyun if params.observe_only: 181*4882a593Smuzhiyun logger.error("Observe-only mode not supported in this UI") 182*4882a593Smuzhiyun return 1 183*4882a593Smuzhiyun 184*4882a593Smuzhiyun llevel, debug_domains = bb.msg.constructLogOptions() 185*4882a593Smuzhiyun server.runCommand(["setEventMask", server.getEventHandle(), llevel, debug_domains, _evt_list]) 186*4882a593Smuzhiyun 187*4882a593Smuzhiyun try: 188*4882a593Smuzhiyun params.updateFromServer(server) 189*4882a593Smuzhiyun cmdline = params.parseActions() 190*4882a593Smuzhiyun if not cmdline: 191*4882a593Smuzhiyun logger.error("No task given") 192*4882a593Smuzhiyun return 1 193*4882a593Smuzhiyun if 'msg' in cmdline and cmdline['msg']: 194*4882a593Smuzhiyun logger.error(cmdline['msg']) 195*4882a593Smuzhiyun return 1 196*4882a593Smuzhiyun cmdline = cmdline['action'] 197*4882a593Smuzhiyun ret, error = server.runCommand(cmdline) 198*4882a593Smuzhiyun if error: 199*4882a593Smuzhiyun logger.error("{0}: {1}".format(cmdline, error)) 200*4882a593Smuzhiyun return 1 201*4882a593Smuzhiyun elif not ret: 202*4882a593Smuzhiyun logger.error("Couldn't get default commandline: {0}".format(re)) 203*4882a593Smuzhiyun return 1 204*4882a593Smuzhiyun except xmlrpc.client.Fault as x: 205*4882a593Smuzhiyun logger.error("XMLRPC Fault getting commandline: {0}".format(x)) 206*4882a593Smuzhiyun return 1 207*4882a593Smuzhiyun 208*4882a593Smuzhiyun active_process_total = None 209*4882a593Smuzhiyun is_tasks_running = False 210*4882a593Smuzhiyun 211*4882a593Smuzhiyun while True: 212*4882a593Smuzhiyun try: 213*4882a593Smuzhiyun event = eventHandler.waitEvent(0.25) 214*4882a593Smuzhiyun if not event: 215*4882a593Smuzhiyun continue 216*4882a593Smuzhiyun 217*4882a593Smuzhiyun helper.eventHandler(event) 218*4882a593Smuzhiyun 219*4882a593Smuzhiyun if isinstance(event, bb.build.TaskBase): 220*4882a593Smuzhiyun logger.info(event._message) 221*4882a593Smuzhiyun if isinstance(event, logging.LogRecord): 222*4882a593Smuzhiyun # Don't report sstate failures as errors, since Yocto will just run the tasks for real 223*4882a593Smuzhiyun if event.msg == "No suitable staging package found" or (event.msg.startswith( 224*4882a593Smuzhiyun "Fetcher failure: Unable to find file") and "downloadfilename" in event.msg and "sstate" in event.msg): 225*4882a593Smuzhiyun event.levelno = bb.msg.BBLogFormatter.WARNING 226*4882a593Smuzhiyun if event.taskpid != 0: 227*4882a593Smuzhiyun # For "normal" logging conditions, don't show note logs from tasks 228*4882a593Smuzhiyun # but do show them if the user has changed the default log level to 229*4882a593Smuzhiyun # include verbose/debug messages 230*4882a593Smuzhiyun if event.levelno <= bb.msg.BBLogFormatter.NOTE and (event.levelno < llevel or ( 231*4882a593Smuzhiyun event.levelno == bb.msg.BBLogFormatter.NOTE and llevel != bb.msg.BBLogFormatter.VERBOSE)): 232*4882a593Smuzhiyun continue 233*4882a593Smuzhiyun 234*4882a593Smuzhiyun # Prefix task messages with recipe/task 235*4882a593Smuzhiyun if event.taskpid in helper.running_tasks and event.levelno != bb.msg.BBLogFormatter.PLAIN: 236*4882a593Smuzhiyun taskinfo = helper.running_tasks[event.taskpid] 237*4882a593Smuzhiyun event.msg = taskinfo['title'] + ': ' + event.msg 238*4882a593Smuzhiyun if hasattr(event, 'fn'): 239*4882a593Smuzhiyun event.msg = event.fn + ': ' + event.msg 240*4882a593Smuzhiyun logger.handle(event) 241*4882a593Smuzhiyun if isinstance(event, bb.build.TaskFailedSilent): 242*4882a593Smuzhiyun logger.warning("Logfile for failed setscene task is %s" % event.logfile) 243*4882a593Smuzhiyun continue 244*4882a593Smuzhiyun if isinstance(event, bb.build.TaskFailed): 245*4882a593Smuzhiyun rt = "{0}-{1}:{2}".format(event.pn, event.pv.replace("AUTOINC", "0"), event.task) 246*4882a593Smuzhiyun 247*4882a593Smuzhiyun logfile = event.logfile 248*4882a593Smuzhiyun if not logfile or not os.path.exists(logfile): 249*4882a593Smuzhiyun TeamCityUI.emit_service_message("buildProblem", description="{0}\nUnknown failure (no log file available)".format(rt)) 250*4882a593Smuzhiyun if not event.task.endswith("_setscene"): 251*4882a593Smuzhiyun server.runCommand(["stateForceShutdown"]) 252*4882a593Smuzhiyun continue 253*4882a593Smuzhiyun 254*4882a593Smuzhiyun details = deque(maxlen=loglines) 255*4882a593Smuzhiyun error_lines = [] 256*4882a593Smuzhiyun if includelogs and not event.errprinted: 257*4882a593Smuzhiyun with open(logfile, "r") as f: 258*4882a593Smuzhiyun while True: 259*4882a593Smuzhiyun line = f.readline() 260*4882a593Smuzhiyun if not line: 261*4882a593Smuzhiyun break 262*4882a593Smuzhiyun line = line.rstrip() 263*4882a593Smuzhiyun details.append(' | %s' % line) 264*4882a593Smuzhiyun # TODO: a less stupid check for errors 265*4882a593Smuzhiyun if (event.task == "do_compile") and ("error:" in line): 266*4882a593Smuzhiyun error_lines.append(line) 267*4882a593Smuzhiyun 268*4882a593Smuzhiyun if error_lines: 269*4882a593Smuzhiyun TeamCityUI.emit_service_message("compilationStarted", compiler=rt) 270*4882a593Smuzhiyun for line in error_lines: 271*4882a593Smuzhiyun TeamCityUI.emit_service_message("message", text=line, status="ERROR") 272*4882a593Smuzhiyun TeamCityUI.emit_service_message("compilationFinished", compiler=rt) 273*4882a593Smuzhiyun else: 274*4882a593Smuzhiyun TeamCityUI.emit_service_message("buildProblem", description=rt) 275*4882a593Smuzhiyun 276*4882a593Smuzhiyun err = "Logfile of failure stored in: %s" % logfile 277*4882a593Smuzhiyun if details: 278*4882a593Smuzhiyun ui.block_start("{0} task log".format(rt)) 279*4882a593Smuzhiyun # TeamCity seems to choke on service messages longer than about 63800 characters, so if error 280*4882a593Smuzhiyun # details is longer than, say, 60000, batch it up into several messages. 281*4882a593Smuzhiyun first_message = True 282*4882a593Smuzhiyun while details: 283*4882a593Smuzhiyun detail_len = 0 284*4882a593Smuzhiyun batch = deque() 285*4882a593Smuzhiyun while details and detail_len < 60000: 286*4882a593Smuzhiyun # TODO: This code doesn't bother to handle lines that themselves are extremely long. 287*4882a593Smuzhiyun line = details.popleft() 288*4882a593Smuzhiyun batch.append(line) 289*4882a593Smuzhiyun detail_len += len(line) 290*4882a593Smuzhiyun 291*4882a593Smuzhiyun if first_message: 292*4882a593Smuzhiyun batch.appendleft("Log data follows:") 293*4882a593Smuzhiyun first_message = False 294*4882a593Smuzhiyun TeamCityUI.emit_service_message("message", text=err, status="ERROR", 295*4882a593Smuzhiyun errorDetails="\n".join(batch)) 296*4882a593Smuzhiyun else: 297*4882a593Smuzhiyun TeamCityUI.emit_service_message("message", text="[continued]", status="ERROR", 298*4882a593Smuzhiyun errorDetails="\n".join(batch)) 299*4882a593Smuzhiyun ui.block_end() 300*4882a593Smuzhiyun else: 301*4882a593Smuzhiyun TeamCityUI.emit_service_message("message", text=err, status="ERROR", errorDetails="") 302*4882a593Smuzhiyun 303*4882a593Smuzhiyun if not event.task.endswith("_setscene"): 304*4882a593Smuzhiyun server.runCommand(["stateForceShutdown"]) 305*4882a593Smuzhiyun 306*4882a593Smuzhiyun if isinstance(event, bb.event.ProcessStarted): 307*4882a593Smuzhiyun if event.processname in ["Initialising tasks", "Checking sstate mirror object availability"]: 308*4882a593Smuzhiyun active_process_total = event.total 309*4882a593Smuzhiyun ui.block_start(event.processname) 310*4882a593Smuzhiyun if isinstance(event, bb.event.ProcessFinished): 311*4882a593Smuzhiyun if event.processname in ["Initialising tasks", "Checking sstate mirror object availability"]: 312*4882a593Smuzhiyun ui.progress(event.processname, 100) 313*4882a593Smuzhiyun ui.block_end() 314*4882a593Smuzhiyun if isinstance(event, bb.event.ProcessProgress): 315*4882a593Smuzhiyun if event.processname in ["Initialising tasks", 316*4882a593Smuzhiyun "Checking sstate mirror object availability"] and active_process_total != 0: 317*4882a593Smuzhiyun ui.progress(event.processname, event.progress * 100 / active_process_total) 318*4882a593Smuzhiyun if isinstance(event, bb.event.CacheLoadStarted): 319*4882a593Smuzhiyun ui.block_start("Loading cache") 320*4882a593Smuzhiyun if isinstance(event, bb.event.CacheLoadProgress): 321*4882a593Smuzhiyun if event.total != 0: 322*4882a593Smuzhiyun ui.progress("Loading cache", math.floor(event.current * 100 / event.total)) 323*4882a593Smuzhiyun if isinstance(event, bb.event.CacheLoadCompleted): 324*4882a593Smuzhiyun ui.progress("Loading cache", 100) 325*4882a593Smuzhiyun ui.block_end() 326*4882a593Smuzhiyun if isinstance(event, bb.event.ParseStarted): 327*4882a593Smuzhiyun ui.block_start("Parsing recipes and checking upstream revisions") 328*4882a593Smuzhiyun if isinstance(event, bb.event.ParseProgress): 329*4882a593Smuzhiyun if event.total != 0: 330*4882a593Smuzhiyun ui.progress("Parsing recipes", math.floor(event.current * 100 / event.total)) 331*4882a593Smuzhiyun if isinstance(event, bb.event.ParseCompleted): 332*4882a593Smuzhiyun ui.progress("Parsing recipes", 100) 333*4882a593Smuzhiyun ui.block_end() 334*4882a593Smuzhiyun if isinstance(event, bb.command.CommandCompleted): 335*4882a593Smuzhiyun return 336*4882a593Smuzhiyun if isinstance(event, bb.command.CommandFailed): 337*4882a593Smuzhiyun logger.error(str(event)) 338*4882a593Smuzhiyun return 1 339*4882a593Smuzhiyun if isinstance(event, bb.event.MultipleProviders): 340*4882a593Smuzhiyun logger.warning(str(event)) 341*4882a593Smuzhiyun continue 342*4882a593Smuzhiyun if isinstance(event, bb.event.NoProvider): 343*4882a593Smuzhiyun logger.error(str(event)) 344*4882a593Smuzhiyun continue 345*4882a593Smuzhiyun if isinstance(event, bb.command.CommandExit): 346*4882a593Smuzhiyun return 347*4882a593Smuzhiyun if isinstance(event, bb.cooker.CookerExit): 348*4882a593Smuzhiyun return 349*4882a593Smuzhiyun if isinstance(event, bb.runqueue.sceneQueueTaskStarted): 350*4882a593Smuzhiyun if not is_tasks_running: 351*4882a593Smuzhiyun is_tasks_running = True 352*4882a593Smuzhiyun ui.block_start("Running tasks") 353*4882a593Smuzhiyun if event.stats.total != 0: 354*4882a593Smuzhiyun ui.progress("Running setscene tasks", ( 355*4882a593Smuzhiyun event.stats.completed + event.stats.active + event.stats.failed + 1) * 100 / event.stats.total) 356*4882a593Smuzhiyun if isinstance(event, bb.runqueue.runQueueTaskStarted): 357*4882a593Smuzhiyun if not is_tasks_running: 358*4882a593Smuzhiyun is_tasks_running = True 359*4882a593Smuzhiyun ui.block_start("Running tasks") 360*4882a593Smuzhiyun if event.stats.total != 0: 361*4882a593Smuzhiyun pseudo_total = event.stats.total - event.stats.skipped 362*4882a593Smuzhiyun pseudo_complete = event.stats.completed + event.stats.active - event.stats.skipped + event.stats.failed + 1 363*4882a593Smuzhiyun # TODO: sometimes this gives over 100% 364*4882a593Smuzhiyun ui.progress("Running runqueue tasks", (pseudo_complete) * 100 / pseudo_total, 365*4882a593Smuzhiyun " ({0}/{1})".format(pseudo_complete, pseudo_total)) 366*4882a593Smuzhiyun if isinstance(event, bb.runqueue.sceneQueueTaskFailed): 367*4882a593Smuzhiyun logger.warning(str(event)) 368*4882a593Smuzhiyun continue 369*4882a593Smuzhiyun if isinstance(event, bb.runqueue.runQueueTaskFailed): 370*4882a593Smuzhiyun logger.error(str(event)) 371*4882a593Smuzhiyun return 1 372*4882a593Smuzhiyun if isinstance(event, bb.event.LogExecTTY): 373*4882a593Smuzhiyun pass 374*4882a593Smuzhiyun except EnvironmentError as ioerror: 375*4882a593Smuzhiyun # ignore interrupted io 376*4882a593Smuzhiyun if ioerror.args[0] == 4: 377*4882a593Smuzhiyun pass 378*4882a593Smuzhiyun except Exception as ex: 379*4882a593Smuzhiyun logger.error(str(ex)) 380*4882a593Smuzhiyun 381*4882a593Smuzhiyun # except KeyboardInterrupt: 382*4882a593Smuzhiyun # if shutdown == 2: 383*4882a593Smuzhiyun # mw.appendText("Third Keyboard Interrupt, exit.\n") 384*4882a593Smuzhiyun # exitflag = True 385*4882a593Smuzhiyun # if shutdown == 1: 386*4882a593Smuzhiyun # mw.appendText("Second Keyboard Interrupt, stopping...\n") 387*4882a593Smuzhiyun # _, error = server.runCommand(["stateForceShutdown"]) 388*4882a593Smuzhiyun # if error: 389*4882a593Smuzhiyun # print("Unable to cleanly stop: %s" % error) 390*4882a593Smuzhiyun # if shutdown == 0: 391*4882a593Smuzhiyun # mw.appendText("Keyboard Interrupt, closing down...\n") 392*4882a593Smuzhiyun # _, error = server.runCommand(["stateShutdown"]) 393*4882a593Smuzhiyun # if error: 394*4882a593Smuzhiyun # print("Unable to cleanly shutdown: %s" % error) 395*4882a593Smuzhiyun # shutdown = shutdown + 1 396*4882a593Smuzhiyun # pass 397