blob: 6bf4c1f03001b63cd2be2e2f80beb285b371dd5e [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
Patrick Williamsf1e5d692016-03-30 15:21:19 -050024import time
Patrick Williamsc124f4f2015-09-15 14:41:29 -050025import sys
26try:
27 import bb
28except RuntimeError as exc:
29 sys.exit(str(exc))
30
31from bb.ui import uihelper
32from bb.ui.buildinfohelper import BuildInfoHelper
33
34import bb.msg
35import logging
36import os
37
38# pylint: disable=invalid-name
39# module properties for UI modules are read by bitbake and the contract should not be broken
40
41
42featureSet = [bb.cooker.CookerFeatures.HOB_EXTRA_CACHES, bb.cooker.CookerFeatures.SEND_DEPENDS_TREE, bb.cooker.CookerFeatures.BASEDATASTORE_TRACKING, bb.cooker.CookerFeatures.SEND_SANITYEVENTS]
43
44logger = logging.getLogger("ToasterLogger")
45interactive = sys.stdout.isatty()
46
Patrick Williamsc124f4f2015-09-15 14:41:29 -050047def _log_settings_from_server(server):
48 # Get values of variables which control our output
49 includelogs, error = server.runCommand(["getVariable", "BBINCLUDELOGS"])
50 if error:
51 logger.error("Unable to get the value of BBINCLUDELOGS variable: %s", error)
52 raise BaseException(error)
53 loglines, error = server.runCommand(["getVariable", "BBINCLUDELOGS_LINES"])
54 if error:
55 logger.error("Unable to get the value of BBINCLUDELOGS_LINES variable: %s", error)
56 raise BaseException(error)
57 consolelogfile, error = server.runCommand(["getVariable", "BB_CONSOLELOG"])
58 if error:
59 logger.error("Unable to get the value of BB_CONSOLELOG variable: %s", error)
60 raise BaseException(error)
Patrick Williamsf1e5d692016-03-30 15:21:19 -050061 return consolelogfile
Patrick Williamsc124f4f2015-09-15 14:41:29 -050062
Patrick Williamsf1e5d692016-03-30 15:21:19 -050063# create a log file for a single build and direct the logger at it;
64# log file name is timestamped to the millisecond (depending
65# on system clock accuracy) to ensure it doesn't overlap with
66# other log file names
67#
68# returns (log file, path to log file) for a build
69def _open_build_log(log_dir):
70 format_str = "%(levelname)s: %(message)s"
Patrick Williamsc124f4f2015-09-15 14:41:29 -050071
Patrick Williamsf1e5d692016-03-30 15:21:19 -050072 now = time.time()
73 now_ms = int((now - int(now)) * 1000)
74 time_str = time.strftime('build_%Y%m%d_%H%M%S', time.localtime(now))
75 log_file_name = time_str + ('.%d.log' % now_ms)
76 build_log_file_path = os.path.join(log_dir, log_file_name)
77
78 build_log = logging.FileHandler(build_log_file_path)
79
80 logformat = bb.msg.BBLogFormatter(format_str)
81 build_log.setFormatter(logformat)
82
83 bb.msg.addDefaultlogFilter(build_log)
84 logger.addHandler(build_log)
85
86 return (build_log, build_log_file_path)
87
88# stop logging to the build log if it exists
89def _close_build_log(build_log):
90 if build_log:
91 build_log.flush()
92 build_log.close()
93 logger.removeHandler(build_log)
94
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050095_evt_list = [
96 "bb.build.TaskBase",
97 "bb.build.TaskFailed",
98 "bb.build.TaskFailedSilent",
99 "bb.build.TaskStarted",
100 "bb.build.TaskSucceeded",
101 "bb.command.CommandCompleted",
102 "bb.command.CommandExit",
103 "bb.command.CommandFailed",
104 "bb.cooker.CookerExit",
105 "bb.event.BuildCompleted",
106 "bb.event.BuildStarted",
107 "bb.event.CacheLoadCompleted",
108 "bb.event.CacheLoadProgress",
109 "bb.event.CacheLoadStarted",
110 "bb.event.ConfigParsed",
111 "bb.event.DepTreeGenerated",
112 "bb.event.LogExecTTY",
113 "bb.event.MetadataEvent",
114 "bb.event.MultipleProviders",
115 "bb.event.NoProvider",
116 "bb.event.ParseCompleted",
117 "bb.event.ParseProgress",
118 "bb.event.RecipeParsed",
119 "bb.event.SanityCheck",
120 "bb.event.SanityCheckPassed",
121 "bb.event.TreeDataPreparationCompleted",
122 "bb.event.TreeDataPreparationStarted",
123 "bb.runqueue.runQueueTaskCompleted",
124 "bb.runqueue.runQueueTaskFailed",
125 "bb.runqueue.runQueueTaskSkipped",
126 "bb.runqueue.runQueueTaskStarted",
127 "bb.runqueue.sceneQueueTaskCompleted",
128 "bb.runqueue.sceneQueueTaskFailed",
129 "bb.runqueue.sceneQueueTaskStarted",
130 "logging.LogRecord"]
131
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500132def main(server, eventHandler, params):
133 # set to a logging.FileHandler instance when a build starts;
134 # see _open_build_log()
135 build_log = None
136
137 # set to the log path when a build starts
138 build_log_file_path = None
139
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500140 helper = uihelper.BBUIHelper()
141
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500142 # TODO don't use log output to determine when bitbake has started
143 #
144 # WARNING: this log handler cannot be removed, as localhostbecontroller
145 # relies on output in the toaster_ui.log file to determine whether
146 # the bitbake server has started, which only happens if
147 # this logger is setup here (see the TODO in the loop below)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500148 console = logging.StreamHandler(sys.stdout)
149 format_str = "%(levelname)s: %(message)s"
150 formatter = bb.msg.BBLogFormatter(format_str)
151 bb.msg.addDefaultlogFilter(console)
152 console.setFormatter(formatter)
153 logger.addHandler(console)
154 logger.setLevel(logging.INFO)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500155 llevel, debug_domains = bb.msg.constructLogOptions()
156 result, error = server.runCommand(["setEventMask", server.getEventHandle(), llevel, debug_domains, _evt_list])
157 if not result or error:
158 logger.error("can't set event mask: %s", error)
159 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500160
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500161 # verify and warn
162 build_history_enabled = True
163 inheritlist, _ = server.runCommand(["getVariable", "INHERIT"])
164
165 if not "buildhistory" in inheritlist.split(" "):
166 logger.warn("buildhistory is not enabled. Please enable INHERIT += \"buildhistory\" to see image details.")
167 build_history_enabled = False
168
169 if not params.observe_only:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500170 params.updateFromServer(server)
171 params.updateToServer(server, os.environ.copy())
172 cmdline = params.parseActions()
173 if not cmdline:
174 print("Nothing to do. Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.")
175 return 1
176 if 'msg' in cmdline and cmdline['msg']:
177 logger.error(cmdline['msg'])
178 return 1
179
180 ret, error = server.runCommand(cmdline['action'])
181 if error:
182 logger.error("Command '%s' failed: %s" % (cmdline, error))
183 return 1
184 elif ret != True:
185 logger.error("Command '%s' failed: returned %s" % (cmdline, ret))
186 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500187
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500188 # set to 1 when toasterui needs to shut down
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500189 main.shutdown = 0
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500190
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500191 interrupted = False
192 return_value = 0
193 errors = 0
194 warnings = 0
195 taskfailures = []
196 first = True
197
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500198 buildinfohelper = BuildInfoHelper(server, build_history_enabled,
199 os.getenv('TOASTER_BRBE'))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500200
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500201 # write our own log files into bitbake's log directory;
202 # we're only interested in the path to the parent directory of
203 # this file, as we're writing our own logs into the same directory
204 consolelogfile = _log_settings_from_server(server)
205 log_dir = os.path.dirname(consolelogfile)
206 bb.utils.mkdirhier(log_dir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500207
208 while True:
209 try:
210 event = eventHandler.waitEvent(0.25)
211 if first:
212 first = False
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500213
214 # TODO don't use log output to determine when bitbake has started
215 #
216 # this is the line localhostbecontroller needs to
217 # see in toaster_ui.log which it uses to decide whether
218 # the bitbake server has started...
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500219 logger.info("ToasterUI waiting for events")
220
221 if event is None:
222 if main.shutdown > 0:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500223 # if shutting down, close any open build log first
224 _close_build_log(build_log)
225
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500226 break
227 continue
228
229 helper.eventHandler(event)
230
231 # pylint: disable=protected-access
232 # the code will look into the protected variables of the event; no easy way around this
233
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500234 # we treat ParseStarted as the first event of toaster-triggered
235 # builds; that way we get the Build Configuration included in the log
236 # and any errors that occur before BuildStarted is fired
237 if isinstance(event, bb.event.ParseStarted):
238 if not (build_log and build_log_file_path):
239 build_log, build_log_file_path = _open_build_log(log_dir)
240 continue
241
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500242 if isinstance(event, bb.event.BuildStarted):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500243 if not (build_log and build_log_file_path):
244 build_log, build_log_file_path = _open_build_log(log_dir)
245
246 buildinfohelper.store_started_build(event, build_log_file_path)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500247 continue
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500248
249 if isinstance(event, (bb.build.TaskStarted, bb.build.TaskSucceeded, bb.build.TaskFailedSilent)):
250 buildinfohelper.update_and_store_task(event)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500251 logger.info("Logfile for task %s", event.logfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500252 continue
253
254 if isinstance(event, bb.build.TaskBase):
255 logger.info(event._message)
256
257 if isinstance(event, bb.event.LogExecTTY):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500258 logger.info(event.msg)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500259 continue
260
261 if isinstance(event, logging.LogRecord):
262 if event.levelno == -1:
263 event.levelno = formatter.ERROR
264
265 buildinfohelper.store_log_event(event)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500266
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500267 if event.levelno >= formatter.ERROR:
268 errors = errors + 1
269 elif event.levelno == formatter.WARNING:
270 warnings = warnings + 1
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500271
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500272 # For "normal" logging conditions, don't show note logs from tasks
273 # but do show them if the user has changed the default log level to
274 # include verbose/debug messages
275 if event.taskpid != 0 and event.levelno <= formatter.NOTE:
276 continue
277
278 logger.handle(event)
279 continue
280
281 if isinstance(event, bb.build.TaskFailed):
282 buildinfohelper.update_and_store_task(event)
283 logfile = event.logfile
284 if logfile and os.path.exists(logfile):
285 bb.error("Logfile of failure stored in: %s" % logfile)
286 continue
287
288 # these events are unprocessed now, but may be used in the future to log
289 # timing and error informations from the parsing phase in Toaster
290 if isinstance(event, (bb.event.SanityCheckPassed, bb.event.SanityCheck)):
291 continue
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500292 if isinstance(event, bb.event.ParseProgress):
293 continue
294 if isinstance(event, bb.event.ParseCompleted):
295 continue
296 if isinstance(event, bb.event.CacheLoadStarted):
297 continue
298 if isinstance(event, bb.event.CacheLoadProgress):
299 continue
300 if isinstance(event, bb.event.CacheLoadCompleted):
301 continue
302 if isinstance(event, bb.event.MultipleProviders):
303 logger.info("multiple providers are available for %s%s (%s)", event._is_runtime and "runtime " or "",
304 event._item,
305 ", ".join(event._candidates))
306 logger.info("consider defining a PREFERRED_PROVIDER entry to match %s", event._item)
307 continue
308
309 if isinstance(event, bb.event.NoProvider):
310 errors = errors + 1
311 if event._runtime:
312 r = "R"
313 else:
314 r = ""
315
316 if event._dependees:
317 text = "Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)" % (r, event._item, ", ".join(event._dependees), r)
318 else:
319 text = "Nothing %sPROVIDES '%s'" % (r, event._item)
320
321 logger.error(text)
322 if event._reasons:
323 for reason in event._reasons:
324 logger.error("%s", reason)
325 text += reason
326 buildinfohelper.store_log_error(text)
327 continue
328
329 if isinstance(event, bb.event.ConfigParsed):
330 continue
331 if isinstance(event, bb.event.RecipeParsed):
332 continue
333
334 # end of saved events
335
336 if isinstance(event, (bb.runqueue.sceneQueueTaskStarted, bb.runqueue.runQueueTaskStarted, bb.runqueue.runQueueTaskSkipped)):
337 buildinfohelper.store_started_task(event)
338 continue
339
340 if isinstance(event, bb.runqueue.runQueueTaskCompleted):
341 buildinfohelper.update_and_store_task(event)
342 continue
343
344 if isinstance(event, bb.runqueue.runQueueTaskFailed):
345 buildinfohelper.update_and_store_task(event)
346 taskfailures.append(event.taskstring)
347 logger.error("Task %s (%s) failed with exit code '%s'",
348 event.taskid, event.taskstring, event.exitcode)
349 continue
350
351 if isinstance(event, (bb.runqueue.sceneQueueTaskCompleted, bb.runqueue.sceneQueueTaskFailed)):
352 buildinfohelper.update_and_store_task(event)
353 continue
354
355
356 if isinstance(event, (bb.event.TreeDataPreparationStarted, bb.event.TreeDataPreparationCompleted)):
357 continue
358
359 if isinstance(event, (bb.event.BuildCompleted, bb.command.CommandFailed)):
360
361 errorcode = 0
362 if isinstance(event, bb.command.CommandFailed):
363 errors += 1
364 errorcode = 1
365 logger.error("Command execution failed: %s", event.error)
366
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500367 # turn off logging to the current build log
368 _close_build_log(build_log)
369
370 # reset ready for next BuildStarted
371 build_log = None
372
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500373 # update the build info helper on BuildCompleted, not on CommandXXX
374 buildinfohelper.update_build_information(event, errors, warnings, taskfailures)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500375
376 brbe = buildinfohelper.brbe
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500377 buildinfohelper.close(errorcode)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500378
379 # we start a new build info
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500380 if params.observe_only:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500381 logger.debug("ToasterUI prepared for new build")
382 errors = 0
383 warnings = 0
384 taskfailures = []
385 buildinfohelper = BuildInfoHelper(server, build_history_enabled)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500386 else:
387 main.shutdown = 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500388
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500389 logger.info("ToasterUI build done, brbe: %s", brbe)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500390 continue
391
392 if isinstance(event, (bb.command.CommandCompleted,
393 bb.command.CommandFailed,
394 bb.command.CommandExit)):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500395 if params.observe_only:
396 errorcode = 0
397 else:
398 main.shutdown = 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500399
400 continue
401
402 if isinstance(event, bb.event.MetadataEvent):
403 if event.type == "SinglePackageInfo":
404 buildinfohelper.store_build_package_information(event)
405 elif event.type == "LayerInfo":
406 buildinfohelper.store_layer_info(event)
407 elif event.type == "BuildStatsList":
408 buildinfohelper.store_tasks_stats(event)
409 elif event.type == "ImagePkgList":
410 buildinfohelper.store_target_package_data(event)
411 elif event.type == "MissedSstate":
412 buildinfohelper.store_missed_state_tasks(event)
413 elif event.type == "ImageFileSize":
414 buildinfohelper.update_target_image_file(event)
415 elif event.type == "ArtifactFileSize":
416 buildinfohelper.update_artifact_image_file(event)
417 elif event.type == "LicenseManifestPath":
418 buildinfohelper.store_license_manifest_path(event)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500419 elif event.type == "SetBRBE":
420 buildinfohelper.brbe = buildinfohelper._get_data_from_event(event)
421 elif event.type == "OSErrorException":
422 logger.error(event)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500423 else:
424 logger.error("Unprocessed MetadataEvent %s ", str(event))
425 continue
426
427 if isinstance(event, bb.cooker.CookerExit):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500428 # shutdown when bitbake server shuts down
429 main.shutdown = 1
430 continue
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500431
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500432 if isinstance(event, bb.event.DepTreeGenerated):
433 buildinfohelper.store_dependency_information(event)
434 continue
435
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500436 logger.warn("Unknown event: %s", event)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500437 return_value += 1
438
439 except EnvironmentError as ioerror:
440 # ignore interrupted io
441 if ioerror.args[0] == 4:
442 pass
443 except KeyboardInterrupt:
444 main.shutdown = 1
445 except Exception as e:
446 # print errors to log
447 import traceback
448 from pprint import pformat
449 exception_data = traceback.format_exc()
450 logger.error("%s\n%s" , e, exception_data)
451
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500452 # save them to database, if possible; if it fails, we already logged to console.
453 try:
454 buildinfohelper.store_log_exception("%s\n%s" % (str(e), exception_data))
455 except Exception as ce:
456 logger.error("CRITICAL - Failed to to save toaster exception to the database: %s", str(ce))
457
458 # make sure we return with an error
459 return_value += 1
460
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500461 if interrupted and return_value == 0:
462 return_value += 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500463
464 logger.warn("Return value is %d", return_value)
465 return return_value