summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/bb/ui/knotty.py
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/bb/ui/knotty.py')
-rw-r--r--bitbake/lib/bb/ui/knotty.py193
1 files changed, 137 insertions, 56 deletions
diff --git a/bitbake/lib/bb/ui/knotty.py b/bitbake/lib/bb/ui/knotty.py
index 0efa614dfc..f86999bb09 100644
--- a/bitbake/lib/bb/ui/knotty.py
+++ b/bitbake/lib/bb/ui/knotty.py
@@ -21,10 +21,11 @@ import fcntl
21import struct 21import struct
22import copy 22import copy
23import atexit 23import atexit
24from itertools import groupby
24 25
25from bb.ui import uihelper 26from bb.ui import uihelper
26 27
27featureSet = [bb.cooker.CookerFeatures.SEND_SANITYEVENTS] 28featureSet = [bb.cooker.CookerFeatures.SEND_SANITYEVENTS, bb.cooker.CookerFeatures.BASEDATASTORE_TRACKING]
28 29
29logger = logging.getLogger("BitBake") 30logger = logging.getLogger("BitBake")
30interactive = sys.stdout.isatty() 31interactive = sys.stdout.isatty()
@@ -178,7 +179,7 @@ class TerminalFilter(object):
178 new[3] = new[3] & ~termios.ECHO 179 new[3] = new[3] & ~termios.ECHO
179 termios.tcsetattr(fd, termios.TCSADRAIN, new) 180 termios.tcsetattr(fd, termios.TCSADRAIN, new)
180 curses.setupterm() 181 curses.setupterm()
181 if curses.tigetnum("colors") > 2: 182 if curses.tigetnum("colors") > 2 and os.environ.get('NO_COLOR', '') == '':
182 for h in handlers: 183 for h in handlers:
183 try: 184 try:
184 h.formatter.enable_color() 185 h.formatter.enable_color()
@@ -227,7 +228,9 @@ class TerminalFilter(object):
227 228
228 def keepAlive(self, t): 229 def keepAlive(self, t):
229 if not self.cuu: 230 if not self.cuu:
230 print("Bitbake still alive (%ds)" % t) 231 print("Bitbake still alive (no events for %ds). Active tasks:" % t)
232 for t in self.helper.running_tasks:
233 print(t)
231 sys.stdout.flush() 234 sys.stdout.flush()
232 235
233 def updateFooter(self): 236 def updateFooter(self):
@@ -249,58 +252,68 @@ class TerminalFilter(object):
249 return 252 return
250 tasks = [] 253 tasks = []
251 for t in runningpids: 254 for t in runningpids:
255 start_time = activetasks[t].get("starttime", None)
256 if start_time:
257 msg = "%s - %s (pid %s)" % (activetasks[t]["title"], self.elapsed(currenttime - start_time), activetasks[t]["pid"])
258 else:
259 msg = "%s (pid %s)" % (activetasks[t]["title"], activetasks[t]["pid"])
252 progress = activetasks[t].get("progress", None) 260 progress = activetasks[t].get("progress", None)
253 if progress is not None: 261 if progress is not None:
254 pbar = activetasks[t].get("progressbar", None) 262 pbar = activetasks[t].get("progressbar", None)
255 rate = activetasks[t].get("rate", None) 263 rate = activetasks[t].get("rate", None)
256 start_time = activetasks[t].get("starttime", None)
257 if not pbar or pbar.bouncing != (progress < 0): 264 if not pbar or pbar.bouncing != (progress < 0):
258 if progress < 0: 265 if progress < 0:
259 pbar = BBProgress("0: %s (pid %s)" % (activetasks[t]["title"], activetasks[t]["pid"]), 100, widgets=[' ', progressbar.BouncingSlider(), ''], extrapos=3, resize_handler=self.sigwinch_handle) 266 pbar = BBProgress("0: %s" % msg, 100, widgets=[' ', progressbar.BouncingSlider(), ''], extrapos=3, resize_handler=self.sigwinch_handle)
260 pbar.bouncing = True 267 pbar.bouncing = True
261 else: 268 else:
262 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) 269 pbar = BBProgress("0: %s" % msg, 100, widgets=[' ', progressbar.Percentage(), ' ', progressbar.Bar(), ''], extrapos=5, resize_handler=self.sigwinch_handle)
263 pbar.bouncing = False 270 pbar.bouncing = False
264 activetasks[t]["progressbar"] = pbar 271 activetasks[t]["progressbar"] = pbar
265 tasks.append((pbar, progress, rate, start_time)) 272 tasks.append((pbar, msg, progress, rate, start_time))
266 else: 273 else:
267 start_time = activetasks[t].get("starttime", None) 274 tasks.append(msg)
268 if start_time:
269 tasks.append("%s - %s (pid %s)" % (activetasks[t]["title"], self.elapsed(currenttime - start_time), activetasks[t]["pid"]))
270 else:
271 tasks.append("%s (pid %s)" % (activetasks[t]["title"], activetasks[t]["pid"]))
272 275
273 if self.main.shutdown: 276 if self.main.shutdown:
274 content = "Waiting for %s running tasks to finish:" % len(activetasks) 277 content = pluralise("Waiting for %s running task to finish",
278 "Waiting for %s running tasks to finish", len(activetasks))
279 if not self.quiet:
280 content += ':'
275 print(content) 281 print(content)
276 else: 282 else:
283 scene_tasks = "%s of %s" % (self.helper.setscene_current, self.helper.setscene_total)
284 cur_tasks = "%s of %s" % (self.helper.tasknumber_current, self.helper.tasknumber_total)
285
286 content = ''
287 if not self.quiet:
288 msg = "Setscene tasks: %s" % scene_tasks
289 content += msg + "\n"
290 print(msg)
291
277 if self.quiet: 292 if self.quiet:
278 content = "Running tasks (%s of %s)" % (self.helper.tasknumber_current, self.helper.tasknumber_total) 293 msg = "Running tasks (%s, %s)" % (scene_tasks, cur_tasks)
279 elif not len(activetasks): 294 elif not len(activetasks):
280 content = "No currently running tasks (%s of %s)" % (self.helper.tasknumber_current, self.helper.tasknumber_total) 295 msg = "No currently running tasks (%s)" % cur_tasks
281 else: 296 else:
282 content = "Currently %2s running tasks (%s of %s)" % (len(activetasks), self.helper.tasknumber_current, self.helper.tasknumber_total) 297 msg = "Currently %2s running tasks (%s)" % (len(activetasks), cur_tasks)
283 maxtask = self.helper.tasknumber_total 298 maxtask = self.helper.tasknumber_total
284 if not self.main_progress or self.main_progress.maxval != maxtask: 299 if not self.main_progress or self.main_progress.maxval != maxtask:
285 widgets = [' ', progressbar.Percentage(), ' ', progressbar.Bar()] 300 widgets = [' ', progressbar.Percentage(), ' ', progressbar.Bar()]
286 self.main_progress = BBProgress("Running tasks", maxtask, widgets=widgets, resize_handler=self.sigwinch_handle) 301 self.main_progress = BBProgress("Running tasks", maxtask, widgets=widgets, resize_handler=self.sigwinch_handle)
287 self.main_progress.start(False) 302 self.main_progress.start(False)
288 self.main_progress.setmessage(content) 303 self.main_progress.setmessage(msg)
289 progress = self.helper.tasknumber_current - 1 304 progress = max(0, self.helper.tasknumber_current - 1)
290 if progress < 0: 305 content += self.main_progress.update(progress)
291 progress = 0
292 content = self.main_progress.update(progress)
293 print('') 306 print('')
294 lines = 1 + int(len(content) / (self.columns + 1)) 307 lines = self.getlines(content)
295 if self.quiet == 0: 308 if not self.quiet:
296 for tasknum, task in enumerate(tasks[:(self.rows - 2)]): 309 for tasknum, task in enumerate(tasks[:(self.rows - 1 - lines)]):
297 if isinstance(task, tuple): 310 if isinstance(task, tuple):
298 pbar, progress, rate, start_time = task 311 pbar, msg, progress, rate, start_time = task
299 if not pbar.start_time: 312 if not pbar.start_time:
300 pbar.start(False) 313 pbar.start(False)
301 if start_time: 314 if start_time:
302 pbar.start_time = start_time 315 pbar.start_time = start_time
303 pbar.setmessage('%s:%s' % (tasknum, pbar.msg.split(':', 1)[1])) 316 pbar.setmessage('%s: %s' % (tasknum, msg))
304 pbar.setextra(rate) 317 pbar.setextra(rate)
305 if progress > -1: 318 if progress > -1:
306 content = pbar.update(progress) 319 content = pbar.update(progress)
@@ -310,11 +323,17 @@ class TerminalFilter(object):
310 else: 323 else:
311 content = "%s: %s" % (tasknum, task) 324 content = "%s: %s" % (tasknum, task)
312 print(content) 325 print(content)
313 lines = lines + 1 + int(len(content) / (self.columns + 1)) 326 lines = lines + self.getlines(content)
314 self.footer_present = lines 327 self.footer_present = lines
315 self.lastpids = runningpids[:] 328 self.lastpids = runningpids[:]
316 self.lastcount = self.helper.tasknumber_current 329 self.lastcount = self.helper.tasknumber_current
317 330
331 def getlines(self, content):
332 lines = 0
333 for line in content.split("\n"):
334 lines = lines + 1 + int(len(line) / (self.columns + 1))
335 return lines
336
318 def finish(self): 337 def finish(self):
319 if self.stdinbackup: 338 if self.stdinbackup:
320 fd = sys.stdin.fileno() 339 fd = sys.stdin.fileno()
@@ -401,6 +420,11 @@ def main(server, eventHandler, params, tf = TerminalFilter):
401 except bb.BBHandledException: 420 except bb.BBHandledException:
402 drain_events_errorhandling(eventHandler) 421 drain_events_errorhandling(eventHandler)
403 return 1 422 return 1
423 except Exception as e:
424 # bitbake-server comms failure
425 early_logger = bb.msg.logger_create('bitbake', sys.stdout)
426 early_logger.fatal("Attempting to set server environment: %s", e)
427 return 1
404 428
405 if params.options.quiet == 0: 429 if params.options.quiet == 0:
406 console_loglevel = loglevel 430 console_loglevel = loglevel
@@ -539,6 +563,13 @@ def main(server, eventHandler, params, tf = TerminalFilter):
539 except OSError: 563 except OSError:
540 pass 564 pass
541 565
566 # Add the logging domains specified by the user on the command line
567 for (domainarg, iterator) in groupby(params.debug_domains):
568 dlevel = len(tuple(iterator))
569 l = logconfig["loggers"].setdefault("BitBake.%s" % domainarg, {})
570 l["level"] = logging.DEBUG - dlevel + 1
571 l.setdefault("handlers", []).extend(["BitBake.verbconsole"])
572
542 conf = bb.msg.setLoggingConfig(logconfig, logconfigfile) 573 conf = bb.msg.setLoggingConfig(logconfig, logconfigfile)
543 574
544 if sys.stdin.isatty() and sys.stdout.isatty(): 575 if sys.stdin.isatty() and sys.stdout.isatty():
@@ -559,7 +590,12 @@ def main(server, eventHandler, params, tf = TerminalFilter):
559 return 590 return
560 591
561 llevel, debug_domains = bb.msg.constructLogOptions() 592 llevel, debug_domains = bb.msg.constructLogOptions()
562 server.runCommand(["setEventMask", server.getEventHandle(), llevel, debug_domains, _evt_list]) 593 try:
594 server.runCommand(["setEventMask", server.getEventHandle(), llevel, debug_domains, _evt_list])
595 except (BrokenPipeError, EOFError) as e:
596 # bitbake-server comms failure
597 logger.fatal("Attempting to set event mask: %s", e)
598 return 1
563 599
564 # The logging_tree module is *extremely* helpful in debugging logging 600 # The logging_tree module is *extremely* helpful in debugging logging
565 # domains. Uncomment here to dump the logging tree when bitbake starts 601 # domains. Uncomment here to dump the logging tree when bitbake starts
@@ -568,7 +604,11 @@ def main(server, eventHandler, params, tf = TerminalFilter):
568 604
569 universe = False 605 universe = False
570 if not params.observe_only: 606 if not params.observe_only:
571 params.updateFromServer(server) 607 try:
608 params.updateFromServer(server)
609 except Exception as e:
610 logger.fatal("Fetching command line: %s", e)
611 return 1
572 cmdline = params.parseActions() 612 cmdline = params.parseActions()
573 if not cmdline: 613 if not cmdline:
574 print("Nothing to do. Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.") 614 print("Nothing to do. Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.")
@@ -579,7 +619,12 @@ def main(server, eventHandler, params, tf = TerminalFilter):
579 if cmdline['action'][0] == "buildTargets" and "universe" in cmdline['action'][1]: 619 if cmdline['action'][0] == "buildTargets" and "universe" in cmdline['action'][1]:
580 universe = True 620 universe = True
581 621
582 ret, error = server.runCommand(cmdline['action']) 622 try:
623 ret, error = server.runCommand(cmdline['action'])
624 except (BrokenPipeError, EOFError) as e:
625 # bitbake-server comms failure
626 logger.fatal("Command '{}' failed: %s".format(cmdline), e)
627 return 1
583 if error: 628 if error:
584 logger.error("Command '%s' failed: %s" % (cmdline, error)) 629 logger.error("Command '%s' failed: %s" % (cmdline, error))
585 return 1 630 return 1
@@ -597,26 +642,40 @@ def main(server, eventHandler, params, tf = TerminalFilter):
597 warnings = 0 642 warnings = 0
598 taskfailures = [] 643 taskfailures = []
599 644
600 printinterval = 5000 645 printintervaldelta = 10 * 60 # 10 minutes
601 lastprint = time.time() 646 printinterval = printintervaldelta
647 pinginterval = 1 * 60 # 1 minute
648 lastevent = lastprint = time.time()
602 649
603 termfilter = tf(main, helper, console_handlers, params.options.quiet) 650 termfilter = tf(main, helper, console_handlers, params.options.quiet)
604 atexit.register(termfilter.finish) 651 atexit.register(termfilter.finish)
605 652
606 while True: 653 # shutdown levels
654 # 0 - normal operation
655 # 1 - no new task execution, let current running tasks finish
656 # 2 - interrupting currently executing tasks
657 # 3 - we're done, exit
658 while main.shutdown < 3:
607 try: 659 try:
608 if (lastprint + printinterval) <= time.time(): 660 if (lastprint + printinterval) <= time.time():
609 termfilter.keepAlive(printinterval) 661 termfilter.keepAlive(printinterval)
610 printinterval += 5000 662 printinterval += printintervaldelta
611 event = eventHandler.waitEvent(0) 663 event = eventHandler.waitEvent(0)
612 if event is None: 664 if event is None:
613 if main.shutdown > 1: 665 if (lastevent + pinginterval) <= time.time():
614 break 666 ret, error = server.runCommand(["ping"])
667 if error or not ret:
668 termfilter.clearFooter()
669 print("No reply after pinging server (%s, %s), exiting." % (str(error), str(ret)))
670 return_value = 3
671 main.shutdown = 3
672 lastevent = time.time()
615 if not parseprogress: 673 if not parseprogress:
616 termfilter.updateFooter() 674 termfilter.updateFooter()
617 event = eventHandler.waitEvent(0.25) 675 event = eventHandler.waitEvent(0.25)
618 if event is None: 676 if event is None:
619 continue 677 continue
678 lastevent = time.time()
620 helper.eventHandler(event) 679 helper.eventHandler(event)
621 if isinstance(event, bb.runqueue.runQueueExitWait): 680 if isinstance(event, bb.runqueue.runQueueExitWait):
622 if not main.shutdown: 681 if not main.shutdown:
@@ -638,8 +697,8 @@ def main(server, eventHandler, params, tf = TerminalFilter):
638 697
639 if isinstance(event, logging.LogRecord): 698 if isinstance(event, logging.LogRecord):
640 lastprint = time.time() 699 lastprint = time.time()
641 printinterval = 5000 700 printinterval = printintervaldelta
642 if event.levelno >= bb.msg.BBLogFormatter.ERROR: 701 if event.levelno >= bb.msg.BBLogFormatter.ERRORONCE:
643 errors = errors + 1 702 errors = errors + 1
644 return_value = 1 703 return_value = 1
645 elif event.levelno == bb.msg.BBLogFormatter.WARNING: 704 elif event.levelno == bb.msg.BBLogFormatter.WARNING:
@@ -653,10 +712,10 @@ def main(server, eventHandler, params, tf = TerminalFilter):
653 continue 712 continue
654 713
655 # Prefix task messages with recipe/task 714 # Prefix task messages with recipe/task
656 if event.taskpid in helper.pidmap and event.levelno != bb.msg.BBLogFormatter.PLAIN: 715 if event.taskpid in helper.pidmap and event.levelno not in [bb.msg.BBLogFormatter.PLAIN, bb.msg.BBLogFormatter.WARNONCE, bb.msg.BBLogFormatter.ERRORONCE]:
657 taskinfo = helper.running_tasks[helper.pidmap[event.taskpid]] 716 taskinfo = helper.running_tasks[helper.pidmap[event.taskpid]]
658 event.msg = taskinfo['title'] + ': ' + event.msg 717 event.msg = taskinfo['title'] + ': ' + event.msg
659 if hasattr(event, 'fn'): 718 if hasattr(event, 'fn') and event.levelno not in [bb.msg.BBLogFormatter.WARNONCE, bb.msg.BBLogFormatter.ERRORONCE]:
660 event.msg = event.fn + ': ' + event.msg 719 event.msg = event.fn + ': ' + event.msg
661 logging.getLogger(event.name).handle(event) 720 logging.getLogger(event.name).handle(event)
662 continue 721 continue
@@ -721,15 +780,15 @@ def main(server, eventHandler, params, tf = TerminalFilter):
721 if event.error: 780 if event.error:
722 errors = errors + 1 781 errors = errors + 1
723 logger.error(str(event)) 782 logger.error(str(event))
724 main.shutdown = 2 783 main.shutdown = 3
725 continue 784 continue
726 if isinstance(event, bb.command.CommandExit): 785 if isinstance(event, bb.command.CommandExit):
727 if not return_value: 786 if not return_value:
728 return_value = event.exitcode 787 return_value = event.exitcode
729 main.shutdown = 2 788 main.shutdown = 3
730 continue 789 continue
731 if isinstance(event, (bb.command.CommandCompleted, bb.cooker.CookerExit)): 790 if isinstance(event, (bb.command.CommandCompleted, bb.cooker.CookerExit)):
732 main.shutdown = 2 791 main.shutdown = 3
733 continue 792 continue
734 if isinstance(event, bb.event.MultipleProviders): 793 if isinstance(event, bb.event.MultipleProviders):
735 logger.info(str(event)) 794 logger.info(str(event))
@@ -745,7 +804,7 @@ def main(server, eventHandler, params, tf = TerminalFilter):
745 continue 804 continue
746 805
747 if isinstance(event, bb.runqueue.sceneQueueTaskStarted): 806 if isinstance(event, bb.runqueue.sceneQueueTaskStarted):
748 logger.info("Running setscene task %d of %d (%s)" % (event.stats.completed + event.stats.active + event.stats.failed + 1, event.stats.total, event.taskstring)) 807 logger.info("Running setscene task %d of %d (%s)" % (event.stats.setscene_covered + event.stats.setscene_active + event.stats.setscene_notcovered + 1, event.stats.setscene_total, event.taskstring))
749 continue 808 continue
750 809
751 if isinstance(event, bb.runqueue.runQueueTaskStarted): 810 if isinstance(event, bb.runqueue.runQueueTaskStarted):
@@ -814,15 +873,26 @@ def main(server, eventHandler, params, tf = TerminalFilter):
814 873
815 logger.error("Unknown event: %s", event) 874 logger.error("Unknown event: %s", event)
816 875
876 except (BrokenPipeError, EOFError) as e:
877 # bitbake-server comms failure, don't attempt further comms and exit
878 logger.fatal("Executing event: %s", e)
879 return_value = 1
880 errors = errors + 1
881 main.shutdown = 3
817 except EnvironmentError as ioerror: 882 except EnvironmentError as ioerror:
818 termfilter.clearFooter() 883 termfilter.clearFooter()
819 # ignore interrupted io 884 # ignore interrupted io
820 if ioerror.args[0] == 4: 885 if ioerror.args[0] == 4:
821 continue 886 continue
822 sys.stderr.write(str(ioerror)) 887 sys.stderr.write(str(ioerror))
823 if not params.observe_only:
824 _, error = server.runCommand(["stateForceShutdown"])
825 main.shutdown = 2 888 main.shutdown = 2
889 if not params.observe_only:
890 try:
891 _, error = server.runCommand(["stateForceShutdown"])
892 except (BrokenPipeError, EOFError) as e:
893 # bitbake-server comms failure, don't attempt further comms and exit
894 logger.fatal("Unable to force shutdown: %s", e)
895 main.shutdown = 3
826 except KeyboardInterrupt: 896 except KeyboardInterrupt:
827 termfilter.clearFooter() 897 termfilter.clearFooter()
828 if params.observe_only: 898 if params.observe_only:
@@ -831,9 +901,13 @@ def main(server, eventHandler, params, tf = TerminalFilter):
831 901
832 def state_force_shutdown(): 902 def state_force_shutdown():
833 print("\nSecond Keyboard Interrupt, stopping...\n") 903 print("\nSecond Keyboard Interrupt, stopping...\n")
834 _, error = server.runCommand(["stateForceShutdown"]) 904 try:
835 if error: 905 _, error = server.runCommand(["stateForceShutdown"])
836 logger.error("Unable to cleanly stop: %s" % error) 906 if error:
907 logger.error("Unable to cleanly stop: %s" % error)
908 except (BrokenPipeError, EOFError) as e:
909 # bitbake-server comms failure
910 logger.fatal("Unable to cleanly stop: %s", e)
837 911
838 if not params.observe_only and main.shutdown == 1: 912 if not params.observe_only and main.shutdown == 1:
839 state_force_shutdown() 913 state_force_shutdown()
@@ -846,17 +920,24 @@ def main(server, eventHandler, params, tf = TerminalFilter):
846 _, error = server.runCommand(["stateShutdown"]) 920 _, error = server.runCommand(["stateShutdown"])
847 if error: 921 if error:
848 logger.error("Unable to cleanly shutdown: %s" % error) 922 logger.error("Unable to cleanly shutdown: %s" % error)
923 except (BrokenPipeError, EOFError) as e:
924 # bitbake-server comms failure
925 logger.fatal("Unable to cleanly shutdown: %s", e)
849 except KeyboardInterrupt: 926 except KeyboardInterrupt:
850 state_force_shutdown() 927 state_force_shutdown()
851 928
852 main.shutdown = main.shutdown + 1 929 main.shutdown = main.shutdown + 1
853 pass
854 except Exception as e: 930 except Exception as e:
855 import traceback 931 import traceback
856 sys.stderr.write(traceback.format_exc()) 932 sys.stderr.write(traceback.format_exc())
857 if not params.observe_only:
858 _, error = server.runCommand(["stateForceShutdown"])
859 main.shutdown = 2 933 main.shutdown = 2
934 if not params.observe_only:
935 try:
936 _, error = server.runCommand(["stateForceShutdown"])
937 except (BrokenPipeError, EOFError) as e:
938 # bitbake-server comms failure, don't attempt further comms and exit
939 logger.fatal("Unable to force shutdown: %s", e)
940 main.shudown = 3
860 return_value = 1 941 return_value = 1
861 try: 942 try:
862 termfilter.clearFooter() 943 termfilter.clearFooter()
@@ -867,11 +948,11 @@ def main(server, eventHandler, params, tf = TerminalFilter):
867 for failure in taskfailures: 948 for failure in taskfailures:
868 summary += "\n %s" % failure 949 summary += "\n %s" % failure
869 if warnings: 950 if warnings:
870 summary += pluralise("\nSummary: There was %s WARNING message shown.", 951 summary += pluralise("\nSummary: There was %s WARNING message.",
871 "\nSummary: There were %s WARNING messages shown.", warnings) 952 "\nSummary: There were %s WARNING messages.", warnings)
872 if return_value and errors: 953 if return_value and errors:
873 summary += pluralise("\nSummary: There was %s ERROR message shown, returning a non-zero exit code.", 954 summary += pluralise("\nSummary: There was %s ERROR message, returning a non-zero exit code.",
874 "\nSummary: There were %s ERROR messages shown, returning a non-zero exit code.", errors) 955 "\nSummary: There were %s ERROR messages, returning a non-zero exit code.", errors)
875 if summary and params.options.quiet == 0: 956 if summary and params.options.quiet == 0:
876 print(summary) 957 print(summary)
877 958