knotty.py 29 KB

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