346 lines
13 KiB
Python
346 lines
13 KiB
Python
#
|
|
# BitBake ToasterUI Implementation
|
|
# based on (No)TTY UI Implementation by Richard Purdie
|
|
#
|
|
# Handling output to TTYs or files (no TTY)
|
|
#
|
|
# Copyright (C) 2006-2012 Richard Purdie
|
|
# Copyright (C) 2013 Intel Corporation
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License version 2 as
|
|
# published by the Free Software Foundation.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License along
|
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
|
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
from __future__ import division
|
|
try:
|
|
import bb
|
|
except RuntimeError as exc:
|
|
sys.exit(str(exc))
|
|
|
|
from bb.ui import uihelper
|
|
from bb.ui.buildinfohelper import BuildInfoHelper
|
|
|
|
import bb.msg
|
|
import copy
|
|
import fcntl
|
|
import logging
|
|
import os
|
|
import progressbar
|
|
import signal
|
|
import struct
|
|
import sys
|
|
import time
|
|
import xmlrpclib
|
|
|
|
featureSet = [bb.cooker.CookerFeatures.HOB_EXTRA_CACHES, bb.cooker.CookerFeatures.SEND_DEPENDS_TREE, bb.cooker.CookerFeatures.BASEDATASTORE_TRACKING]
|
|
|
|
logger = logging.getLogger("BitBake")
|
|
interactive = sys.stdout.isatty()
|
|
|
|
|
|
|
|
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)
|
|
consolelogfile, error = server.runCommand(["getVariable", "BB_CONSOLELOG"])
|
|
if error:
|
|
logger.error("Unable to get the value of BB_CONSOLELOG variable: %s" % error)
|
|
raise BaseException(error)
|
|
return includelogs, loglines, consolelogfile
|
|
|
|
|
|
def main(server, eventHandler, params ):
|
|
|
|
helper = uihelper.BBUIHelper()
|
|
|
|
console = logging.StreamHandler(sys.stdout)
|
|
format_str = "%(levelname)s: %(message)s"
|
|
format = bb.msg.BBLogFormatter(format_str)
|
|
bb.msg.addDefaultlogFilter(console)
|
|
console.setFormatter(format)
|
|
logger.addHandler(console)
|
|
|
|
includelogs, loglines, consolelogfile = _log_settings_from_server(server)
|
|
|
|
# verify and warn
|
|
build_history_enabled = True
|
|
inheritlist, error = server.runCommand(["getVariable", "INHERIT"])
|
|
|
|
if not "buildhistory" in inheritlist.split(" "):
|
|
logger.warn("buildhistory is not enabled. Please enable INHERIT += \"buildhistory\" to see image details.")
|
|
build_history_enabled = False
|
|
|
|
if not params.observe_only:
|
|
logger.error("ToasterUI can only work in observer mode")
|
|
return
|
|
|
|
|
|
main.shutdown = 0
|
|
interrupted = False
|
|
return_value = 0
|
|
errors = 0
|
|
warnings = 0
|
|
taskfailures = []
|
|
first = True
|
|
|
|
buildinfohelper = BuildInfoHelper(server, build_history_enabled)
|
|
|
|
if buildinfohelper.brbe is not None and consolelogfile:
|
|
# if we are under managed mode we have no other UI and we need to write our own file
|
|
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)
|
|
|
|
|
|
while True:
|
|
try:
|
|
event = eventHandler.waitEvent(0.25)
|
|
if first:
|
|
first = False
|
|
logger.info("ToasterUI waiting for events")
|
|
|
|
if event is None:
|
|
if main.shutdown > 0:
|
|
break
|
|
continue
|
|
|
|
helper.eventHandler(event)
|
|
|
|
if isinstance(event, bb.event.BuildStarted):
|
|
buildinfohelper.store_started_build(event)
|
|
|
|
if isinstance(event, (bb.build.TaskStarted, bb.build.TaskSucceeded, bb.build.TaskFailedSilent)):
|
|
buildinfohelper.update_and_store_task(event)
|
|
logger.warn("Logfile for task %s" % event.logfile)
|
|
continue
|
|
|
|
if isinstance(event, bb.build.TaskBase):
|
|
logger.info(event._message)
|
|
|
|
if isinstance(event, bb.event.LogExecTTY):
|
|
logger.warn(event.msg)
|
|
continue
|
|
|
|
if isinstance(event, logging.LogRecord):
|
|
buildinfohelper.store_log_event(event)
|
|
if event.levelno >= format.ERROR:
|
|
errors = errors + 1
|
|
return_value = 1
|
|
elif event.levelno == format.WARNING:
|
|
warnings = warnings + 1
|
|
# 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.taskpid != 0 and event.levelno <= format.NOTE:
|
|
continue
|
|
|
|
logger.handle(event)
|
|
continue
|
|
|
|
if isinstance(event, bb.build.TaskFailed):
|
|
buildinfohelper.update_and_store_task(event)
|
|
return_value = 1
|
|
logfile = event.logfile
|
|
if logfile and os.path.exists(logfile):
|
|
bb.error("Logfile of failure stored in: %s" % logfile)
|
|
continue
|
|
|
|
# these events are unprocessed now, but may be used in the future to log
|
|
# timing and error informations from the parsing phase in Toaster
|
|
if isinstance(event, (bb.event.SanityCheckPassed, bb.event.SanityCheck)):
|
|
continue
|
|
if isinstance(event, bb.event.ParseStarted):
|
|
continue
|
|
if isinstance(event, bb.event.ParseProgress):
|
|
continue
|
|
if isinstance(event, bb.event.ParseCompleted):
|
|
continue
|
|
if isinstance(event, bb.event.CacheLoadStarted):
|
|
continue
|
|
if isinstance(event, bb.event.CacheLoadProgress):
|
|
continue
|
|
if isinstance(event, bb.event.CacheLoadCompleted):
|
|
continue
|
|
if isinstance(event, bb.event.MultipleProviders):
|
|
logger.info("multiple providers are available for %s%s (%s)", event._is_runtime and "runtime " or "",
|
|
event._item,
|
|
", ".join(event._candidates))
|
|
logger.info("consider defining a PREFERRED_PROVIDER entry to match %s", event._item)
|
|
continue
|
|
|
|
if isinstance(event, bb.event.NoProvider):
|
|
return_value = 1
|
|
errors = errors + 1
|
|
if event._runtime:
|
|
r = "R"
|
|
else:
|
|
r = ""
|
|
|
|
if event._dependees:
|
|
text = "Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)" % (r, event._item, ", ".join(event._dependees), r)
|
|
else:
|
|
text = "Nothing %sPROVIDES '%s'" % (r, event._item)
|
|
|
|
logger.error(text)
|
|
if event._reasons:
|
|
for reason in event._reasons:
|
|
logger.error("%s", reason)
|
|
text += reason
|
|
buildinfohelper.store_log_error(text)
|
|
continue
|
|
|
|
if isinstance(event, bb.event.ConfigParsed):
|
|
continue
|
|
if isinstance(event, bb.event.RecipeParsed):
|
|
continue
|
|
|
|
# end of saved events
|
|
|
|
if isinstance(event, (bb.runqueue.sceneQueueTaskStarted, bb.runqueue.runQueueTaskStarted, bb.runqueue.runQueueTaskSkipped)):
|
|
buildinfohelper.store_started_task(event)
|
|
continue
|
|
|
|
if isinstance(event, bb.runqueue.runQueueTaskCompleted):
|
|
buildinfohelper.update_and_store_task(event)
|
|
continue
|
|
|
|
if isinstance(event, bb.runqueue.runQueueTaskFailed):
|
|
buildinfohelper.update_and_store_task(event)
|
|
taskfailures.append(event.taskstring)
|
|
logger.error("Task %s (%s) failed with exit code '%s'",
|
|
event.taskid, event.taskstring, event.exitcode)
|
|
continue
|
|
|
|
if isinstance(event, (bb.runqueue.sceneQueueTaskCompleted, bb.runqueue.sceneQueueTaskFailed)):
|
|
buildinfohelper.update_and_store_task(event)
|
|
continue
|
|
|
|
|
|
if isinstance(event, (bb.event.TreeDataPreparationStarted, bb.event.TreeDataPreparationCompleted)):
|
|
continue
|
|
|
|
if isinstance(event, (bb.event.BuildCompleted)):
|
|
continue
|
|
|
|
if isinstance(event, (bb.command.CommandCompleted,
|
|
bb.command.CommandFailed,
|
|
bb.command.CommandExit)):
|
|
errorcode = 0
|
|
if (isinstance(event, bb.command.CommandFailed)):
|
|
event.levelno = format.ERROR
|
|
event.msg = "Command Failed " + event.error
|
|
event.pathname = ""
|
|
event.lineno = 0
|
|
buildinfohelper.store_log_event(event)
|
|
errors += 1
|
|
errorcode = 1
|
|
logger.error("Command execution failed: %s", event.error)
|
|
|
|
buildinfohelper.update_build_information(event, errors, warnings, taskfailures)
|
|
buildinfohelper.close(errorcode)
|
|
# mark the log output; controllers may kill the toasterUI after seeing this log
|
|
logger.info("ToasterUI build done")
|
|
|
|
# we start a new build info
|
|
if buildinfohelper.brbe is not None:
|
|
|
|
logger.debug(1, "ToasterUI under BuildEnvironment management - exiting after the build")
|
|
server.terminateServer()
|
|
else:
|
|
logger.debug(1, "ToasterUI prepared for new build")
|
|
errors = 0
|
|
warnings = 0
|
|
taskfailures = []
|
|
buildinfohelper = BuildInfoHelper(server, build_history_enabled)
|
|
|
|
continue
|
|
|
|
if isinstance(event, bb.event.MetadataEvent):
|
|
if event.type == "SinglePackageInfo":
|
|
buildinfohelper.store_build_package_information(event)
|
|
elif event.type == "LayerInfo":
|
|
buildinfohelper.store_layer_info(event)
|
|
elif event.type == "BuildStatsList":
|
|
buildinfohelper.store_tasks_stats(event)
|
|
elif event.type == "ImagePkgList":
|
|
buildinfohelper.store_target_package_data(event)
|
|
elif event.type == "MissedSstate":
|
|
buildinfohelper.store_missed_state_tasks(event)
|
|
elif event.type == "ImageFileSize":
|
|
buildinfohelper.update_target_image_file(event)
|
|
elif event.type == "ArtifactFileSize":
|
|
buildinfohelper.update_artifact_image_file(event)
|
|
elif event.type == "LicenseManifestPath":
|
|
buildinfohelper.store_license_manifest_path(event)
|
|
else:
|
|
logger.error("Unprocessed MetadataEvent %s " % str(event))
|
|
continue
|
|
|
|
if isinstance(event, bb.cooker.CookerExit):
|
|
# exit when the server exits
|
|
break
|
|
|
|
# ignore
|
|
if isinstance(event, (bb.event.BuildBase,
|
|
bb.event.StampUpdate,
|
|
bb.event.RecipePreFinalise,
|
|
bb.runqueue.runQueueEvent,
|
|
bb.runqueue.runQueueExitWait,
|
|
bb.event.OperationProgress,
|
|
bb.command.CommandFailed,
|
|
bb.command.CommandExit,
|
|
bb.command.CommandCompleted)):
|
|
continue
|
|
|
|
if isinstance(event, bb.event.DepTreeGenerated):
|
|
buildinfohelper.store_dependency_information(event)
|
|
continue
|
|
|
|
logger.error("Unknown event: %s", event)
|
|
|
|
except EnvironmentError as ioerror:
|
|
# ignore interrupted io
|
|
if ioerror.args[0] == 4:
|
|
pass
|
|
except KeyboardInterrupt:
|
|
main.shutdown = 1
|
|
pass
|
|
except Exception as e:
|
|
# print errors to log
|
|
import traceback
|
|
exception_data = traceback.format_exc()
|
|
logger.error("%s\n%s" % (e, exception_data))
|
|
|
|
# save them to database, if possible; if it fails, we already logged to console.
|
|
try:
|
|
buildinfohelper.store_log_exception("%s\n%s" % (str(e), exception_data))
|
|
except Exception as ce:
|
|
logger.error("CRITICAL - Failed to to save toaster exception to the database: %s" % str(ce))
|
|
|
|
pass
|
|
|
|
if interrupted:
|
|
if return_value == 0:
|
|
return_value = 1
|
|
|
|
return return_value
|