knotty.py 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889
  1. #
  2. # BitBake (No)TTY UI Implementation
  3. #
  4. # Handling output to TTYs or files (no TTY)
  5. #
  6. # Copyright (C) 2006-2012 Richard Purdie
  7. #
  8. # SPDX-License-Identifier: GPL-2.0-only
  9. #
  10. from __future__ import division
  11. import os
  12. import sys
  13. import logging
  14. import progressbar
  15. import signal
  16. import bb.msg
  17. import time
  18. import fcntl
  19. import struct
  20. import copy
  21. import atexit
  22. from bb.ui import uihelper
  23. featureSet = [bb.cooker.CookerFeatures.SEND_SANITYEVENTS]
  24. logger = logging.getLogger("BitBake")
  25. interactive = sys.stdout.isatty()
  26. class BBProgress(progressbar.ProgressBar):
  27. def __init__(self, msg, maxval, widgets=None, extrapos=-1, resize_handler=None):
  28. self.msg = msg
  29. self.extrapos = extrapos
  30. if not widgets:
  31. widgets = [': ', progressbar.Percentage(), ' ', progressbar.Bar(),
  32. ' ', progressbar.ETA()]
  33. self.extrapos = 5
  34. if resize_handler:
  35. self._resize_default = resize_handler
  36. else:
  37. self._resize_default = signal.getsignal(signal.SIGWINCH)
  38. progressbar.ProgressBar.__init__(self, maxval, [self.msg] + widgets, fd=sys.stdout)
  39. def _handle_resize(self, signum=None, frame=None):
  40. progressbar.ProgressBar._handle_resize(self, signum, frame)
  41. if self._resize_default:
  42. self._resize_default(signum, frame)
  43. def finish(self):
  44. progressbar.ProgressBar.finish(self)
  45. if self._resize_default:
  46. signal.signal(signal.SIGWINCH, self._resize_default)
  47. def setmessage(self, msg):
  48. self.msg = msg
  49. self.widgets[0] = msg
  50. def setextra(self, extra):
  51. if self.extrapos > -1:
  52. if extra:
  53. extrastr = str(extra)
  54. if extrastr[0] != ' ':
  55. extrastr = ' ' + extrastr
  56. else:
  57. extrastr = ''
  58. self.widgets[self.extrapos] = extrastr
  59. def _need_update(self):
  60. # We always want the bar to print when update() is called
  61. return True
  62. class NonInteractiveProgress(object):
  63. fobj = sys.stdout
  64. def __init__(self, msg, maxval):
  65. self.msg = msg
  66. self.maxval = maxval
  67. self.finished = False
  68. def start(self, update=True):
  69. self.fobj.write("%s..." % self.msg)
  70. self.fobj.flush()
  71. return self
  72. def update(self, value):
  73. pass
  74. def finish(self):
  75. if self.finished:
  76. return
  77. self.fobj.write("done.\n")
  78. self.fobj.flush()
  79. self.finished = True
  80. def new_progress(msg, maxval):
  81. if interactive:
  82. return BBProgress(msg, maxval)
  83. else:
  84. return NonInteractiveProgress(msg, maxval)
  85. def pluralise(singular, plural, qty):
  86. if(qty == 1):
  87. return singular % qty
  88. else:
  89. return plural % qty
  90. class InteractConsoleLogFilter(logging.Filter):
  91. def __init__(self, tf):
  92. self.tf = tf
  93. def filter(self, record):
  94. if record.levelno == bb.msg.BBLogFormatter.NOTE and (record.msg.startswith("Running") or record.msg.startswith("recipe ")):
  95. return False
  96. self.tf.clearFooter()
  97. return True
  98. class TerminalFilter(object):
  99. rows = 25
  100. columns = 80
  101. def sigwinch_handle(self, signum, frame):
  102. self.rows, self.columns = self.getTerminalColumns()
  103. if self._sigwinch_default:
  104. self._sigwinch_default(signum, frame)
  105. def getTerminalColumns(self):
  106. def ioctl_GWINSZ(fd):
  107. try:
  108. cr = struct.unpack('hh', fcntl.ioctl(fd, self.termios.TIOCGWINSZ, '1234'))
  109. except:
  110. return None
  111. return cr
  112. cr = ioctl_GWINSZ(sys.stdout.fileno())
  113. if not cr:
  114. try:
  115. fd = os.open(os.ctermid(), os.O_RDONLY)
  116. cr = ioctl_GWINSZ(fd)
  117. os.close(fd)
  118. except:
  119. pass
  120. if not cr:
  121. try:
  122. cr = (os.environ['LINES'], os.environ['COLUMNS'])
  123. except:
  124. cr = (25, 80)
  125. return cr
  126. def __init__(self, main, helper, handlers, quiet):
  127. self.main = main
  128. self.helper = helper
  129. self.cuu = None
  130. self.stdinbackup = None
  131. self.interactive = sys.stdout.isatty()
  132. self.footer_present = False
  133. self.lastpids = []
  134. self.lasttime = None
  135. self.quiet = quiet
  136. if not self.interactive:
  137. return
  138. try:
  139. import curses
  140. except ImportError:
  141. sys.exit("FATAL: The knotty ui could not load the required curses python module.")
  142. import termios
  143. self.curses = curses
  144. self.termios = termios
  145. try:
  146. fd = sys.stdin.fileno()
  147. self.stdinbackup = termios.tcgetattr(fd)
  148. new = copy.deepcopy(self.stdinbackup)
  149. new[3] = new[3] & ~termios.ECHO
  150. termios.tcsetattr(fd, termios.TCSADRAIN, new)
  151. curses.setupterm()
  152. if curses.tigetnum("colors") > 2:
  153. for h in handlers:
  154. try:
  155. h.formatter.enable_color()
  156. except AttributeError:
  157. pass
  158. self.ed = curses.tigetstr("ed")
  159. if self.ed:
  160. self.cuu = curses.tigetstr("cuu")
  161. try:
  162. self._sigwinch_default = signal.getsignal(signal.SIGWINCH)
  163. signal.signal(signal.SIGWINCH, self.sigwinch_handle)
  164. except:
  165. pass
  166. self.rows, self.columns = self.getTerminalColumns()
  167. except:
  168. self.cuu = None
  169. if not self.cuu:
  170. self.interactive = False
  171. bb.note("Unable to use interactive mode for this terminal, using fallback")
  172. return
  173. for h in handlers:
  174. h.addFilter(InteractConsoleLogFilter(self))
  175. self.main_progress = None
  176. def clearFooter(self):
  177. if self.footer_present:
  178. lines = self.footer_present
  179. sys.stdout.buffer.write(self.curses.tparm(self.cuu, lines))
  180. sys.stdout.buffer.write(self.curses.tparm(self.ed))
  181. sys.stdout.flush()
  182. self.footer_present = False
  183. def elapsed(self, sec):
  184. hrs = int(sec / 3600.0)
  185. sec -= hrs * 3600
  186. min = int(sec / 60.0)
  187. sec -= min * 60
  188. if hrs > 0:
  189. return "%dh%dm%ds" % (hrs, min, sec)
  190. elif min > 0:
  191. return "%dm%ds" % (min, sec)
  192. else:
  193. return "%ds" % (sec)
  194. def keepAlive(self, t):
  195. if not self.cuu:
  196. print("Bitbake still alive (%ds)" % t)
  197. sys.stdout.flush()
  198. def updateFooter(self):
  199. if not self.cuu:
  200. return
  201. activetasks = self.helper.running_tasks
  202. failedtasks = self.helper.failed_tasks
  203. runningpids = self.helper.running_pids
  204. currenttime = time.time()
  205. if not self.lasttime or (currenttime - self.lasttime > 5):
  206. self.helper.needUpdate = True
  207. self.lasttime = currenttime
  208. if self.footer_present and not self.helper.needUpdate:
  209. return
  210. self.helper.needUpdate = False
  211. if self.footer_present:
  212. self.clearFooter()
  213. if (not self.helper.tasknumber_total or self.helper.tasknumber_current == self.helper.tasknumber_total) and not len(activetasks):
  214. return
  215. tasks = []
  216. for t in runningpids:
  217. progress = activetasks[t].get("progress", None)
  218. if progress is not None:
  219. pbar = activetasks[t].get("progressbar", None)
  220. rate = activetasks[t].get("rate", None)
  221. start_time = activetasks[t].get("starttime", None)
  222. if not pbar or pbar.bouncing != (progress < 0):
  223. if progress < 0:
  224. pbar = BBProgress("0: %s (pid %s)" % (activetasks[t]["title"], activetasks[t]["pid"]), 100, widgets=[' ', progressbar.BouncingSlider(), ''], extrapos=3, resize_handler=self.sigwinch_handle)
  225. pbar.bouncing = True
  226. else:
  227. 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)
  228. pbar.bouncing = False
  229. activetasks[t]["progressbar"] = pbar
  230. tasks.append((pbar, progress, rate, start_time))
  231. else:
  232. start_time = activetasks[t].get("starttime", None)
  233. if start_time:
  234. tasks.append("%s - %s (pid %s)" % (activetasks[t]["title"], self.elapsed(currenttime - start_time), activetasks[t]["pid"]))
  235. else:
  236. tasks.append("%s (pid %s)" % (activetasks[t]["title"], activetasks[t]["pid"]))
  237. if self.main.shutdown:
  238. content = "Waiting for %s running tasks to finish:" % len(activetasks)
  239. print(content)
  240. else:
  241. if self.quiet:
  242. content = "Running tasks (%s of %s)" % (self.helper.tasknumber_current, self.helper.tasknumber_total)
  243. elif not len(activetasks):
  244. content = "No currently running tasks (%s of %s)" % (self.helper.tasknumber_current, self.helper.tasknumber_total)
  245. else:
  246. content = "Currently %2s running tasks (%s of %s)" % (len(activetasks), self.helper.tasknumber_current, self.helper.tasknumber_total)
  247. maxtask = self.helper.tasknumber_total
  248. if not self.main_progress or self.main_progress.maxval != maxtask:
  249. widgets = [' ', progressbar.Percentage(), ' ', progressbar.Bar()]
  250. self.main_progress = BBProgress("Running tasks", maxtask, widgets=widgets, resize_handler=self.sigwinch_handle)
  251. self.main_progress.start(False)
  252. self.main_progress.setmessage(content)
  253. progress = self.helper.tasknumber_current - 1
  254. if progress < 0:
  255. progress = 0
  256. content = self.main_progress.update(progress)
  257. print('')
  258. lines = 1 + int(len(content) / (self.columns + 1))
  259. if self.quiet == 0:
  260. for tasknum, task in enumerate(tasks[:(self.rows - 2)]):
  261. if isinstance(task, tuple):
  262. pbar, progress, rate, start_time = task
  263. if not pbar.start_time:
  264. pbar.start(False)
  265. if start_time:
  266. pbar.start_time = start_time
  267. pbar.setmessage('%s:%s' % (tasknum, pbar.msg.split(':', 1)[1]))
  268. pbar.setextra(rate)
  269. if progress > -1:
  270. content = pbar.update(progress)
  271. else:
  272. content = pbar.update(1)
  273. print('')
  274. else:
  275. content = "%s: %s" % (tasknum, task)
  276. print(content)
  277. lines = lines + 1 + int(len(content) / (self.columns + 1))
  278. self.footer_present = lines
  279. self.lastpids = runningpids[:]
  280. self.lastcount = self.helper.tasknumber_current
  281. def finish(self):
  282. if self.stdinbackup:
  283. fd = sys.stdin.fileno()
  284. self.termios.tcsetattr(fd, self.termios.TCSADRAIN, self.stdinbackup)
  285. def print_event_log(event, includelogs, loglines, termfilter):
  286. # FIXME refactor this out further
  287. logfile = event.logfile
  288. if logfile and os.path.exists(logfile):
  289. termfilter.clearFooter()
  290. bb.error("Logfile of failure stored in: %s" % logfile)
  291. if includelogs and not event.errprinted:
  292. print("Log data follows:")
  293. f = open(logfile, "r")
  294. lines = []
  295. while True:
  296. l = f.readline()
  297. if l == '':
  298. break
  299. l = l.rstrip()
  300. if loglines:
  301. lines.append(' | %s' % l)
  302. if len(lines) > int(loglines):
  303. lines.pop(0)
  304. else:
  305. print('| %s' % l)
  306. f.close()
  307. if lines:
  308. for line in lines:
  309. print(line)
  310. def _log_settings_from_server(server, observe_only):
  311. # Get values of variables which control our output
  312. includelogs, error = server.runCommand(["getVariable", "BBINCLUDELOGS"])
  313. if error:
  314. logger.error("Unable to get the value of BBINCLUDELOGS variable: %s" % error)
  315. raise BaseException(error)
  316. loglines, error = server.runCommand(["getVariable", "BBINCLUDELOGS_LINES"])
  317. if error:
  318. logger.error("Unable to get the value of BBINCLUDELOGS_LINES variable: %s" % error)
  319. raise BaseException(error)
  320. if observe_only:
  321. cmd = 'getVariable'
  322. else:
  323. cmd = 'getSetVariable'
  324. consolelogfile, error = server.runCommand([cmd, "BB_CONSOLELOG"])
  325. if error:
  326. logger.error("Unable to get the value of BB_CONSOLELOG variable: %s" % error)
  327. raise BaseException(error)
  328. logconfigfile, error = server.runCommand([cmd, "BB_LOGCONFIG"])
  329. if error:
  330. logger.error("Unable to get the value of BB_LOGCONFIG variable: %s" % error)
  331. raise BaseException(error)
  332. return includelogs, loglines, consolelogfile, logconfigfile
  333. _evt_list = [ "bb.runqueue.runQueueExitWait", "bb.event.LogExecTTY", "logging.LogRecord",
  334. "bb.build.TaskFailed", "bb.build.TaskBase", "bb.event.ParseStarted",
  335. "bb.event.ParseProgress", "bb.event.ParseCompleted", "bb.event.CacheLoadStarted",
  336. "bb.event.CacheLoadProgress", "bb.event.CacheLoadCompleted", "bb.command.CommandFailed",
  337. "bb.command.CommandExit", "bb.command.CommandCompleted", "bb.cooker.CookerExit",
  338. "bb.event.MultipleProviders", "bb.event.NoProvider", "bb.runqueue.sceneQueueTaskStarted",
  339. "bb.runqueue.runQueueTaskStarted", "bb.runqueue.runQueueTaskFailed", "bb.runqueue.sceneQueueTaskFailed",
  340. "bb.event.BuildBase", "bb.build.TaskStarted", "bb.build.TaskSucceeded", "bb.build.TaskFailedSilent",
  341. "bb.build.TaskProgress", "bb.event.ProcessStarted", "bb.event.ProcessProgress", "bb.event.ProcessFinished"]
  342. def drain_events_errorhandling(eventHandler):
  343. # We don't have logging setup, we do need to show any events we see before exiting
  344. event = True
  345. logger = bb.msg.logger_create('bitbake', sys.stdout)
  346. while event:
  347. event = eventHandler.waitEvent(0)
  348. if isinstance(event, logging.LogRecord):
  349. logger.handle(event)
  350. def main(server, eventHandler, params, tf = TerminalFilter):
  351. try:
  352. if not params.observe_only:
  353. params.updateToServer(server, os.environ.copy())
  354. includelogs, loglines, consolelogfile, logconfigfile = _log_settings_from_server(server, params.observe_only)
  355. loglevel, _ = bb.msg.constructLogOptions()
  356. except bb.BBHandledException:
  357. drain_events_errorhandling(eventHandler)
  358. return 1
  359. if params.options.quiet == 0:
  360. console_loglevel = loglevel
  361. elif params.options.quiet > 2:
  362. console_loglevel = bb.msg.BBLogFormatter.ERROR
  363. else:
  364. console_loglevel = bb.msg.BBLogFormatter.WARNING
  365. logconfig = {
  366. "version": 1,
  367. "handlers": {
  368. "BitBake.console": {
  369. "class": "logging.StreamHandler",
  370. "formatter": "BitBake.consoleFormatter",
  371. "level": console_loglevel,
  372. "stream": "ext://sys.stdout",
  373. "filters": ["BitBake.stdoutFilter"],
  374. ".": {
  375. "is_console": True,
  376. },
  377. },
  378. "BitBake.errconsole": {
  379. "class": "logging.StreamHandler",
  380. "formatter": "BitBake.consoleFormatter",
  381. "level": loglevel,
  382. "stream": "ext://sys.stderr",
  383. "filters": ["BitBake.stderrFilter"],
  384. ".": {
  385. "is_console": True,
  386. },
  387. },
  388. # This handler can be used if specific loggers should print on
  389. # the console at a lower severity than the default. It will
  390. # display any messages sent to it that are lower than then
  391. # BitBake.console logging level (so as to prevent duplication of
  392. # messages). Nothing is attached to this handler by default
  393. "BitBake.verbconsole": {
  394. "class": "logging.StreamHandler",
  395. "formatter": "BitBake.consoleFormatter",
  396. "level": 1,
  397. "stream": "ext://sys.stdout",
  398. "filters": ["BitBake.verbconsoleFilter"],
  399. ".": {
  400. "is_console": True,
  401. },
  402. },
  403. },
  404. "formatters": {
  405. # This format instance will get color output enabled by the
  406. # terminal
  407. "BitBake.consoleFormatter" : {
  408. "()": "bb.msg.BBLogFormatter",
  409. "format": "%(levelname)s: %(message)s"
  410. },
  411. # The file log requires a separate instance so that it doesn't get
  412. # color enabled
  413. "BitBake.logfileFormatter": {
  414. "()": "bb.msg.BBLogFormatter",
  415. "format": "%(levelname)s: %(message)s"
  416. }
  417. },
  418. "filters": {
  419. "BitBake.stdoutFilter": {
  420. "()": "bb.msg.LogFilterLTLevel",
  421. "level": "ERROR"
  422. },
  423. "BitBake.stderrFilter": {
  424. "()": "bb.msg.LogFilterGEQLevel",
  425. "level": "ERROR"
  426. },
  427. "BitBake.verbconsoleFilter": {
  428. "()": "bb.msg.LogFilterLTLevel",
  429. "level": console_loglevel
  430. },
  431. },
  432. "loggers": {
  433. "BitBake": {
  434. "level": loglevel,
  435. "handlers": ["BitBake.console", "BitBake.errconsole"],
  436. }
  437. },
  438. "disable_existing_loggers": False
  439. }
  440. # Enable the console log file if enabled
  441. if consolelogfile and not params.options.show_environment and not params.options.show_versions:
  442. logconfig = bb.msg.mergeLoggingConfig(logconfig, {
  443. "version": 1,
  444. "handlers" : {
  445. "BitBake.consolelog": {
  446. "class": "logging.FileHandler",
  447. "formatter": "BitBake.logfileFormatter",
  448. "level": loglevel,
  449. "filename": consolelogfile,
  450. },
  451. # Just like verbconsole, anything sent here will go to the
  452. # log file, unless it would go to BitBake.consolelog
  453. "BitBake.verbconsolelog" : {
  454. "class": "logging.FileHandler",
  455. "formatter": "BitBake.logfileFormatter",
  456. "level": 1,
  457. "filename": consolelogfile,
  458. "filters": ["BitBake.verbconsolelogFilter"],
  459. },
  460. },
  461. "filters": {
  462. "BitBake.verbconsolelogFilter": {
  463. "()": "bb.msg.LogFilterLTLevel",
  464. "level": loglevel,
  465. },
  466. },
  467. "loggers": {
  468. "BitBake": {
  469. "handlers": ["BitBake.consolelog"],
  470. },
  471. # Other interesting things that we want to keep an eye on
  472. # in the log files in case someone has an issue, but not
  473. # necessarily show to the user on the console
  474. "BitBake.SigGen.HashEquiv": {
  475. "level": "VERBOSE",
  476. "handlers": ["BitBake.verbconsolelog"],
  477. },
  478. "BitBake.RunQueue.HashEquiv": {
  479. "level": "VERBOSE",
  480. "handlers": ["BitBake.verbconsolelog"],
  481. }
  482. }
  483. })
  484. bb.utils.mkdirhier(os.path.dirname(consolelogfile))
  485. loglink = os.path.join(os.path.dirname(consolelogfile), 'console-latest.log')
  486. bb.utils.remove(loglink)
  487. try:
  488. os.symlink(os.path.basename(consolelogfile), loglink)
  489. except OSError:
  490. pass
  491. conf = bb.msg.setLoggingConfig(logconfig, logconfigfile)
  492. if sys.stdin.isatty() and sys.stdout.isatty():
  493. log_exec_tty = True
  494. else:
  495. log_exec_tty = False
  496. helper = uihelper.BBUIHelper()
  497. # Look for the specially designated handlers which need to be passed to the
  498. # terminal handler
  499. console_handlers = [h for h in conf.config['handlers'].values() if getattr(h, 'is_console', False)]
  500. bb.utils.set_process_name("KnottyUI")
  501. if params.options.remote_server and params.options.kill_server:
  502. server.terminateServer()
  503. return
  504. llevel, debug_domains = bb.msg.constructLogOptions()
  505. server.runCommand(["setEventMask", server.getEventHandle(), llevel, debug_domains, _evt_list])
  506. # The logging_tree module is *extremely* helpful in debugging logging
  507. # domains. Uncomment here to dump the logging tree when bitbake starts
  508. #import logging_tree
  509. #logging_tree.printout()
  510. universe = False
  511. if not params.observe_only:
  512. params.updateFromServer(server)
  513. cmdline = params.parseActions()
  514. if not cmdline:
  515. print("Nothing to do. Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.")
  516. return 1
  517. if 'msg' in cmdline and cmdline['msg']:
  518. logger.error(cmdline['msg'])
  519. return 1
  520. if cmdline['action'][0] == "buildTargets" and "universe" in cmdline['action'][1]:
  521. universe = True
  522. ret, error = server.runCommand(cmdline['action'])
  523. if error:
  524. logger.error("Command '%s' failed: %s" % (cmdline, error))
  525. return 1
  526. elif not ret:
  527. logger.error("Command '%s' failed: returned %s" % (cmdline, ret))
  528. return 1
  529. parseprogress = None
  530. cacheprogress = None
  531. main.shutdown = 0
  532. interrupted = False
  533. return_value = 0
  534. errors = 0
  535. warnings = 0
  536. taskfailures = []
  537. printinterval = 5000
  538. lastprint = time.time()
  539. termfilter = tf(main, helper, console_handlers, params.options.quiet)
  540. atexit.register(termfilter.finish)
  541. while True:
  542. try:
  543. if (lastprint + printinterval) <= time.time():
  544. termfilter.keepAlive(printinterval)
  545. printinterval += 5000
  546. event = eventHandler.waitEvent(0)
  547. if event is None:
  548. if main.shutdown > 1:
  549. break
  550. if not parseprogress:
  551. termfilter.updateFooter()
  552. event = eventHandler.waitEvent(0.25)
  553. if event is None:
  554. continue
  555. helper.eventHandler(event)
  556. if isinstance(event, bb.runqueue.runQueueExitWait):
  557. if not main.shutdown:
  558. main.shutdown = 1
  559. continue
  560. if isinstance(event, bb.event.LogExecTTY):
  561. if log_exec_tty:
  562. tries = event.retries
  563. while tries:
  564. print("Trying to run: %s" % event.prog)
  565. if os.system(event.prog) == 0:
  566. break
  567. time.sleep(event.sleep_delay)
  568. tries -= 1
  569. if tries:
  570. continue
  571. logger.warning(event.msg)
  572. continue
  573. if isinstance(event, logging.LogRecord):
  574. lastprint = time.time()
  575. printinterval = 5000
  576. if event.levelno >= bb.msg.BBLogFormatter.ERROR:
  577. errors = errors + 1
  578. return_value = 1
  579. elif event.levelno == bb.msg.BBLogFormatter.WARNING:
  580. warnings = warnings + 1
  581. if event.taskpid != 0:
  582. # For "normal" logging conditions, don't show note logs from tasks
  583. # but do show them if the user has changed the default log level to
  584. # include verbose/debug messages
  585. if event.levelno <= bb.msg.BBLogFormatter.NOTE and (event.levelno < llevel or (event.levelno == bb.msg.BBLogFormatter.NOTE and llevel != bb.msg.BBLogFormatter.VERBOSE)):
  586. continue
  587. # Prefix task messages with recipe/task
  588. if event.taskpid in helper.pidmap and event.levelno != bb.msg.BBLogFormatter.PLAIN:
  589. taskinfo = helper.running_tasks[helper.pidmap[event.taskpid]]
  590. event.msg = taskinfo['title'] + ': ' + event.msg
  591. if hasattr(event, 'fn'):
  592. event.msg = event.fn + ': ' + event.msg
  593. logging.getLogger(event.name).handle(event)
  594. continue
  595. if isinstance(event, bb.build.TaskFailedSilent):
  596. logger.warning("Logfile for failed setscene task is %s" % event.logfile)
  597. continue
  598. if isinstance(event, bb.build.TaskFailed):
  599. return_value = 1
  600. print_event_log(event, includelogs, loglines, termfilter)
  601. if isinstance(event, bb.build.TaskBase):
  602. logger.info(event._message)
  603. continue
  604. if isinstance(event, bb.event.ParseStarted):
  605. if params.options.quiet > 1:
  606. continue
  607. if event.total == 0:
  608. continue
  609. termfilter.clearFooter()
  610. parseprogress = new_progress("Parsing recipes", event.total).start()
  611. continue
  612. if isinstance(event, bb.event.ParseProgress):
  613. if params.options.quiet > 1:
  614. continue
  615. if parseprogress:
  616. parseprogress.update(event.current)
  617. else:
  618. bb.warn("Got ParseProgress event for parsing that never started?")
  619. continue
  620. if isinstance(event, bb.event.ParseCompleted):
  621. if params.options.quiet > 1:
  622. continue
  623. if not parseprogress:
  624. continue
  625. parseprogress.finish()
  626. parseprogress = None
  627. if params.options.quiet == 0:
  628. print(("Parsing of %d .bb files complete (%d cached, %d parsed). %d targets, %d skipped, %d masked, %d errors."
  629. % ( event.total, event.cached, event.parsed, event.virtuals, event.skipped, event.masked, event.errors)))
  630. continue
  631. if isinstance(event, bb.event.CacheLoadStarted):
  632. if params.options.quiet > 1:
  633. continue
  634. cacheprogress = new_progress("Loading cache", event.total).start()
  635. continue
  636. if isinstance(event, bb.event.CacheLoadProgress):
  637. if params.options.quiet > 1:
  638. continue
  639. cacheprogress.update(event.current)
  640. continue
  641. if isinstance(event, bb.event.CacheLoadCompleted):
  642. if params.options.quiet > 1:
  643. continue
  644. cacheprogress.finish()
  645. if params.options.quiet == 0:
  646. print("Loaded %d entries from dependency cache." % event.num_entries)
  647. continue
  648. if isinstance(event, bb.command.CommandFailed):
  649. return_value = event.exitcode
  650. if event.error:
  651. errors = errors + 1
  652. logger.error(str(event))
  653. main.shutdown = 2
  654. continue
  655. if isinstance(event, bb.command.CommandExit):
  656. if not return_value:
  657. return_value = event.exitcode
  658. main.shutdown = 2
  659. continue
  660. if isinstance(event, (bb.command.CommandCompleted, bb.cooker.CookerExit)):
  661. main.shutdown = 2
  662. continue
  663. if isinstance(event, bb.event.MultipleProviders):
  664. logger.info(str(event))
  665. continue
  666. if isinstance(event, bb.event.NoProvider):
  667. # For universe builds, only show these as warnings, not errors
  668. if not universe:
  669. return_value = 1
  670. errors = errors + 1
  671. logger.error(str(event))
  672. else:
  673. logger.warning(str(event))
  674. continue
  675. if isinstance(event, bb.runqueue.sceneQueueTaskStarted):
  676. logger.info("Running setscene task %d of %d (%s)" % (event.stats.completed + event.stats.active + event.stats.failed + 1, event.stats.total, event.taskstring))
  677. continue
  678. if isinstance(event, bb.runqueue.runQueueTaskStarted):
  679. if event.noexec:
  680. tasktype = 'noexec task'
  681. else:
  682. tasktype = 'task'
  683. logger.info("Running %s %d of %d (%s)",
  684. tasktype,
  685. event.stats.completed + event.stats.active +
  686. event.stats.failed + 1,
  687. event.stats.total, event.taskstring)
  688. continue
  689. if isinstance(event, bb.runqueue.runQueueTaskFailed):
  690. return_value = 1
  691. taskfailures.append(event.taskstring)
  692. logger.error(str(event))
  693. continue
  694. if isinstance(event, bb.runqueue.sceneQueueTaskFailed):
  695. logger.warning(str(event))
  696. continue
  697. if isinstance(event, bb.event.DepTreeGenerated):
  698. continue
  699. if isinstance(event, bb.event.ProcessStarted):
  700. if params.options.quiet > 1:
  701. continue
  702. termfilter.clearFooter()
  703. parseprogress = new_progress(event.processname, event.total)
  704. parseprogress.start(False)
  705. continue
  706. if isinstance(event, bb.event.ProcessProgress):
  707. if params.options.quiet > 1:
  708. continue
  709. if parseprogress:
  710. parseprogress.update(event.progress)
  711. else:
  712. bb.warn("Got ProcessProgress event for someting that never started?")
  713. continue
  714. if isinstance(event, bb.event.ProcessFinished):
  715. if params.options.quiet > 1:
  716. continue
  717. if parseprogress:
  718. parseprogress.finish()
  719. parseprogress = None
  720. continue
  721. # ignore
  722. if isinstance(event, (bb.event.BuildBase,
  723. bb.event.MetadataEvent,
  724. bb.event.ConfigParsed,
  725. bb.event.MultiConfigParsed,
  726. bb.event.RecipeParsed,
  727. bb.event.RecipePreFinalise,
  728. bb.runqueue.runQueueEvent,
  729. bb.event.OperationStarted,
  730. bb.event.OperationCompleted,
  731. bb.event.OperationProgress,
  732. bb.event.DiskFull,
  733. bb.event.HeartbeatEvent,
  734. bb.build.TaskProgress)):
  735. continue
  736. logger.error("Unknown event: %s", event)
  737. except EnvironmentError as ioerror:
  738. termfilter.clearFooter()
  739. # ignore interrupted io
  740. if ioerror.args[0] == 4:
  741. continue
  742. sys.stderr.write(str(ioerror))
  743. if not params.observe_only:
  744. _, error = server.runCommand(["stateForceShutdown"])
  745. main.shutdown = 2
  746. except KeyboardInterrupt:
  747. termfilter.clearFooter()
  748. if params.observe_only:
  749. print("\nKeyboard Interrupt, exiting observer...")
  750. main.shutdown = 2
  751. def state_force_shutdown():
  752. print("\nSecond Keyboard Interrupt, stopping...\n")
  753. _, error = server.runCommand(["stateForceShutdown"])
  754. if error:
  755. logger.error("Unable to cleanly stop: %s" % error)
  756. if not params.observe_only and main.shutdown == 1:
  757. state_force_shutdown()
  758. if not params.observe_only and main.shutdown == 0:
  759. print("\nKeyboard Interrupt, closing down...\n")
  760. interrupted = True
  761. # Capture the second KeyboardInterrupt during stateShutdown is running
  762. try:
  763. _, error = server.runCommand(["stateShutdown"])
  764. if error:
  765. logger.error("Unable to cleanly shutdown: %s" % error)
  766. except KeyboardInterrupt:
  767. state_force_shutdown()
  768. main.shutdown = main.shutdown + 1
  769. pass
  770. except Exception as e:
  771. import traceback
  772. sys.stderr.write(traceback.format_exc())
  773. if not params.observe_only:
  774. _, error = server.runCommand(["stateForceShutdown"])
  775. main.shutdown = 2
  776. return_value = 1
  777. try:
  778. termfilter.clearFooter()
  779. summary = ""
  780. if taskfailures:
  781. summary += pluralise("\nSummary: %s task failed:",
  782. "\nSummary: %s tasks failed:", len(taskfailures))
  783. for failure in taskfailures:
  784. summary += "\n %s" % failure
  785. if warnings:
  786. summary += pluralise("\nSummary: There was %s WARNING message shown.",
  787. "\nSummary: There were %s WARNING messages shown.", warnings)
  788. if return_value and errors:
  789. summary += pluralise("\nSummary: There was %s ERROR message shown, returning a non-zero exit code.",
  790. "\nSummary: There were %s ERROR messages shown, returning a non-zero exit code.", errors)
  791. if summary and params.options.quiet == 0:
  792. print(summary)
  793. if interrupted:
  794. print("Execution was interrupted, returning a non-zero exit code.")
  795. if return_value == 0:
  796. return_value = 1
  797. except IOError as e:
  798. import errno
  799. if e.errno == errno.EPIPE:
  800. pass
  801. logging.shutdown()
  802. return return_value