blob: 9c7e87dd1e0e1a7fc2190c1535f5efd1a23ef00b [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001#
2# BitBake ToasterUI Implementation
3# based on (No)TTY UI Implementation by Richard Purdie
4#
5# Handling output to TTYs or files (no TTY)
6#
7# Copyright (C) 2006-2012 Richard Purdie
8# Copyright (C) 2013 Intel Corporation
9#
10# This program is free software; you can redistribute it and/or modify
11# it under the terms of the GNU General Public License version 2 as
12# published by the Free Software Foundation.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License along
20# with this program; if not, write to the Free Software Foundation, Inc.,
21# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22
23from __future__ import division
24import sys
25try:
26 import bb
27except RuntimeError as exc:
28 sys.exit(str(exc))
29
30from bb.ui import uihelper
31from bb.ui.buildinfohelper import BuildInfoHelper
32
33import bb.msg
34import logging
35import os
36
37# pylint: disable=invalid-name
38# module properties for UI modules are read by bitbake and the contract should not be broken
39
40
41featureSet = [bb.cooker.CookerFeatures.HOB_EXTRA_CACHES, bb.cooker.CookerFeatures.SEND_DEPENDS_TREE, bb.cooker.CookerFeatures.BASEDATASTORE_TRACKING, bb.cooker.CookerFeatures.SEND_SANITYEVENTS]
42
43logger = logging.getLogger("ToasterLogger")
44interactive = sys.stdout.isatty()
45
46
47
48def _log_settings_from_server(server):
49 # Get values of variables which control our output
50 includelogs, error = server.runCommand(["getVariable", "BBINCLUDELOGS"])
51 if error:
52 logger.error("Unable to get the value of BBINCLUDELOGS variable: %s", error)
53 raise BaseException(error)
54 loglines, error = server.runCommand(["getVariable", "BBINCLUDELOGS_LINES"])
55 if error:
56 logger.error("Unable to get the value of BBINCLUDELOGS_LINES variable: %s", error)
57 raise BaseException(error)
58 consolelogfile, error = server.runCommand(["getVariable", "BB_CONSOLELOG"])
59 if error:
60 logger.error("Unable to get the value of BB_CONSOLELOG variable: %s", error)
61 raise BaseException(error)
62 return includelogs, loglines, consolelogfile
63
64
65def main(server, eventHandler, params ):
66 helper = uihelper.BBUIHelper()
67
68 console = logging.StreamHandler(sys.stdout)
69 format_str = "%(levelname)s: %(message)s"
70 formatter = bb.msg.BBLogFormatter(format_str)
71 bb.msg.addDefaultlogFilter(console)
72 console.setFormatter(formatter)
73 logger.addHandler(console)
74 logger.setLevel(logging.INFO)
75
76 _, _, consolelogfile = _log_settings_from_server(server)
77
78 # verify and warn
79 build_history_enabled = True
80 inheritlist, _ = server.runCommand(["getVariable", "INHERIT"])
81
82 if not "buildhistory" in inheritlist.split(" "):
83 logger.warn("buildhistory is not enabled. Please enable INHERIT += \"buildhistory\" to see image details.")
84 build_history_enabled = False
85
86 if not params.observe_only:
87 logger.error("ToasterUI can only work in observer mode")
88 return 1
89
90
91 main.shutdown = 0
92 interrupted = False
93 return_value = 0
94 errors = 0
95 warnings = 0
96 taskfailures = []
97 first = True
98
99 buildinfohelper = BuildInfoHelper(server, build_history_enabled)
100
101 if buildinfohelper.brbe is not None and consolelogfile:
102 # if we are under managed mode we have no other UI and we need to write our own file
103 bb.utils.mkdirhier(os.path.dirname(consolelogfile))
104 conlogformat = bb.msg.BBLogFormatter(format_str)
105 consolelog = logging.FileHandler(consolelogfile)
106 bb.msg.addDefaultlogFilter(consolelog)
107 consolelog.setFormatter(conlogformat)
108 logger.addHandler(consolelog)
109
110
111 while True:
112 try:
113 event = eventHandler.waitEvent(0.25)
114 if first:
115 first = False
116 logger.info("ToasterUI waiting for events")
117
118 if event is None:
119 if main.shutdown > 0:
120 break
121 continue
122
123 helper.eventHandler(event)
124
125 # pylint: disable=protected-access
126 # the code will look into the protected variables of the event; no easy way around this
127
128 if isinstance(event, bb.event.BuildStarted):
129 buildinfohelper.store_started_build(event)
130
131 if isinstance(event, (bb.build.TaskStarted, bb.build.TaskSucceeded, bb.build.TaskFailedSilent)):
132 buildinfohelper.update_and_store_task(event)
133 logger.warn("Logfile for task %s", event.logfile)
134 continue
135
136 if isinstance(event, bb.build.TaskBase):
137 logger.info(event._message)
138
139 if isinstance(event, bb.event.LogExecTTY):
140 logger.warn(event.msg)
141 continue
142
143 if isinstance(event, logging.LogRecord):
144 if event.levelno == -1:
145 event.levelno = formatter.ERROR
146
147 buildinfohelper.store_log_event(event)
148 if event.levelno >= formatter.ERROR:
149 errors = errors + 1
150 elif event.levelno == formatter.WARNING:
151 warnings = warnings + 1
152 # For "normal" logging conditions, don't show note logs from tasks
153 # but do show them if the user has changed the default log level to
154 # include verbose/debug messages
155 if event.taskpid != 0 and event.levelno <= formatter.NOTE:
156 continue
157
158 logger.handle(event)
159 continue
160
161 if isinstance(event, bb.build.TaskFailed):
162 buildinfohelper.update_and_store_task(event)
163 logfile = event.logfile
164 if logfile and os.path.exists(logfile):
165 bb.error("Logfile of failure stored in: %s" % logfile)
166 continue
167
168 # these events are unprocessed now, but may be used in the future to log
169 # timing and error informations from the parsing phase in Toaster
170 if isinstance(event, (bb.event.SanityCheckPassed, bb.event.SanityCheck)):
171 continue
172 if isinstance(event, bb.event.ParseStarted):
173 continue
174 if isinstance(event, bb.event.ParseProgress):
175 continue
176 if isinstance(event, bb.event.ParseCompleted):
177 continue
178 if isinstance(event, bb.event.CacheLoadStarted):
179 continue
180 if isinstance(event, bb.event.CacheLoadProgress):
181 continue
182 if isinstance(event, bb.event.CacheLoadCompleted):
183 continue
184 if isinstance(event, bb.event.MultipleProviders):
185 logger.info("multiple providers are available for %s%s (%s)", event._is_runtime and "runtime " or "",
186 event._item,
187 ", ".join(event._candidates))
188 logger.info("consider defining a PREFERRED_PROVIDER entry to match %s", event._item)
189 continue
190
191 if isinstance(event, bb.event.NoProvider):
192 errors = errors + 1
193 if event._runtime:
194 r = "R"
195 else:
196 r = ""
197
198 if event._dependees:
199 text = "Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)" % (r, event._item, ", ".join(event._dependees), r)
200 else:
201 text = "Nothing %sPROVIDES '%s'" % (r, event._item)
202
203 logger.error(text)
204 if event._reasons:
205 for reason in event._reasons:
206 logger.error("%s", reason)
207 text += reason
208 buildinfohelper.store_log_error(text)
209 continue
210
211 if isinstance(event, bb.event.ConfigParsed):
212 continue
213 if isinstance(event, bb.event.RecipeParsed):
214 continue
215
216 # end of saved events
217
218 if isinstance(event, (bb.runqueue.sceneQueueTaskStarted, bb.runqueue.runQueueTaskStarted, bb.runqueue.runQueueTaskSkipped)):
219 buildinfohelper.store_started_task(event)
220 continue
221
222 if isinstance(event, bb.runqueue.runQueueTaskCompleted):
223 buildinfohelper.update_and_store_task(event)
224 continue
225
226 if isinstance(event, bb.runqueue.runQueueTaskFailed):
227 buildinfohelper.update_and_store_task(event)
228 taskfailures.append(event.taskstring)
229 logger.error("Task %s (%s) failed with exit code '%s'",
230 event.taskid, event.taskstring, event.exitcode)
231 continue
232
233 if isinstance(event, (bb.runqueue.sceneQueueTaskCompleted, bb.runqueue.sceneQueueTaskFailed)):
234 buildinfohelper.update_and_store_task(event)
235 continue
236
237
238 if isinstance(event, (bb.event.TreeDataPreparationStarted, bb.event.TreeDataPreparationCompleted)):
239 continue
240
241 if isinstance(event, (bb.event.BuildCompleted, bb.command.CommandFailed)):
242
243 errorcode = 0
244 if isinstance(event, bb.command.CommandFailed):
245 errors += 1
246 errorcode = 1
247 logger.error("Command execution failed: %s", event.error)
248
249 # update the build info helper on BuildCompleted, not on CommandXXX
250 buildinfohelper.update_build_information(event, errors, warnings, taskfailures)
251 buildinfohelper.close(errorcode)
252 # mark the log output; controllers may kill the toasterUI after seeing this log
253 logger.info("ToasterUI build done 1, brbe: %s", buildinfohelper.brbe )
254
255 # we start a new build info
256 if buildinfohelper.brbe is not None:
257
258 logger.debug("ToasterUI under BuildEnvironment management - exiting after the build")
259 server.terminateServer()
260 else:
261 logger.debug("ToasterUI prepared for new build")
262 errors = 0
263 warnings = 0
264 taskfailures = []
265 buildinfohelper = BuildInfoHelper(server, build_history_enabled)
266
267 logger.info("ToasterUI build done 2")
268 continue
269
270 if isinstance(event, (bb.command.CommandCompleted,
271 bb.command.CommandFailed,
272 bb.command.CommandExit)):
273 errorcode = 0
274
275 continue
276
277 if isinstance(event, bb.event.MetadataEvent):
278 if event.type == "SinglePackageInfo":
279 buildinfohelper.store_build_package_information(event)
280 elif event.type == "LayerInfo":
281 buildinfohelper.store_layer_info(event)
282 elif event.type == "BuildStatsList":
283 buildinfohelper.store_tasks_stats(event)
284 elif event.type == "ImagePkgList":
285 buildinfohelper.store_target_package_data(event)
286 elif event.type == "MissedSstate":
287 buildinfohelper.store_missed_state_tasks(event)
288 elif event.type == "ImageFileSize":
289 buildinfohelper.update_target_image_file(event)
290 elif event.type == "ArtifactFileSize":
291 buildinfohelper.update_artifact_image_file(event)
292 elif event.type == "LicenseManifestPath":
293 buildinfohelper.store_license_manifest_path(event)
294 else:
295 logger.error("Unprocessed MetadataEvent %s ", str(event))
296 continue
297
298 if isinstance(event, bb.cooker.CookerExit):
299 # exit when the server exits
300 break
301
302 # ignore
303 if isinstance(event, (bb.event.BuildBase,
304 bb.event.StampUpdate,
305 bb.event.RecipePreFinalise,
306 bb.runqueue.runQueueEvent,
307 bb.runqueue.runQueueExitWait,
308 bb.event.OperationProgress,
309 bb.command.CommandFailed,
310 bb.command.CommandExit,
311 bb.command.CommandCompleted)):
312 continue
313
314 if isinstance(event, bb.event.DepTreeGenerated):
315 buildinfohelper.store_dependency_information(event)
316 continue
317
318 logger.error("Unknown event: %s", event)
319 return_value += 1
320
321 except EnvironmentError as ioerror:
322 # ignore interrupted io
323 if ioerror.args[0] == 4:
324 pass
325 except KeyboardInterrupt:
326 main.shutdown = 1
327 except Exception as e:
328 # print errors to log
329 import traceback
330 from pprint import pformat
331 exception_data = traceback.format_exc()
332 logger.error("%s\n%s" , e, exception_data)
333
334 _, _, tb = sys.exc_info()
335 if tb is not None:
336 curr = tb
337 while curr is not None:
338 logger.warn("Error data dump %s\n%s\n" , traceback.format_tb(curr,1), pformat(curr.tb_frame.f_locals))
339 curr = curr.tb_next
340
341 # save them to database, if possible; if it fails, we already logged to console.
342 try:
343 buildinfohelper.store_log_exception("%s\n%s" % (str(e), exception_data))
344 except Exception as ce:
345 logger.error("CRITICAL - Failed to to save toaster exception to the database: %s", str(ce))
346
347 # make sure we return with an error
348 return_value += 1
349
350 if interrupted:
351 if return_value == 0:
352 return_value += 1
353
354 logger.warn("Return value is %d", return_value)
355 return return_value