summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bitbake/lib/bb/build.py34
-rw-r--r--bitbake/lib/bb/progress.py86
-rw-r--r--bitbake/lib/bb/ui/knotty.py74
-rw-r--r--bitbake/lib/bb/ui/uihelper.py7
-rw-r--r--bitbake/lib/progressbar/progressbar.py16
-rw-r--r--bitbake/lib/progressbar/widgets.py36
6 files changed, 239 insertions, 14 deletions
diff --git a/bitbake/lib/bb/build.py b/bitbake/lib/bb/build.py
index 2ebe67306f..4fb2a77cfd 100644
--- a/bitbake/lib/bb/build.py
+++ b/bitbake/lib/bb/build.py
@@ -35,6 +35,7 @@ import stat
35import bb 35import bb
36import bb.msg 36import bb.msg
37import bb.process 37import bb.process
38import bb.progress
38from bb import data, event, utils 39from bb import data, event, utils
39 40
40bblogger = logging.getLogger('BitBake') 41bblogger = logging.getLogger('BitBake')
@@ -137,6 +138,25 @@ class TaskInvalid(TaskBase):
137 super(TaskInvalid, self).__init__(task, None, metadata) 138 super(TaskInvalid, self).__init__(task, None, metadata)
138 self._message = "No such task '%s'" % task 139 self._message = "No such task '%s'" % task
139 140
141class TaskProgress(event.Event):
142 """
143 Task made some progress that could be reported to the user, usually in
144 the form of a progress bar or similar.
145 NOTE: this class does not inherit from TaskBase since it doesn't need
146 to - it's fired within the task context itself, so we don't have any of
147 the context information that you do in the case of the other events.
148 The event PID can be used to determine which task it came from.
149 The progress value is normally 0-100, but can also be negative
150 indicating that progress has been made but we aren't able to determine
151 how much.
152 The rate is optional, this is simply an extra string to display to the
153 user if specified.
154 """
155 def __init__(self, progress, rate=None):
156 self.progress = progress
157 self.rate = rate
158 event.Event.__init__(self)
159
140 160
141class LogTee(object): 161class LogTee(object):
142 def __init__(self, logger, outfile): 162 def __init__(self, logger, outfile):
@@ -340,6 +360,20 @@ exit $ret
340 else: 360 else:
341 logfile = sys.stdout 361 logfile = sys.stdout
342 362
363 progress = d.getVarFlag(func, 'progress', True)
364 if progress:
365 if progress == 'percent':
366 # Use default regex
367 logfile = bb.progress.BasicProgressHandler(d, outfile=logfile)
368 elif progress.startswith('percent:'):
369 # Use specified regex
370 logfile = bb.progress.BasicProgressHandler(d, regex=progress.split(':', 1)[1], outfile=logfile)
371 elif progress.startswith('outof:'):
372 # Use specified regex
373 logfile = bb.progress.OutOfProgressHandler(d, regex=progress.split(':', 1)[1], outfile=logfile)
374 else:
375 bb.warn('%s: invalid task progress varflag value "%s", ignoring' % (func, progress))
376
343 def readfifo(data): 377 def readfifo(data):
344 lines = data.split(b'\0') 378 lines = data.split(b'\0')
345 for line in lines: 379 for line in lines:
diff --git a/bitbake/lib/bb/progress.py b/bitbake/lib/bb/progress.py
new file mode 100644
index 0000000000..bab8e9465d
--- /dev/null
+++ b/bitbake/lib/bb/progress.py
@@ -0,0 +1,86 @@
1"""
2BitBake progress handling code
3"""
4
5# Copyright (C) 2016 Intel Corporation
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License version 2 as
9# published by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20import sys
21import re
22import time
23import bb.event
24import bb.build
25
26class ProgressHandler(object):
27 """
28 Base class that can pretend to be a file object well enough to be
29 used to build objects to intercept console output and determine the
30 progress of some operation.
31 """
32 def __init__(self, d, outfile=None):
33 self._progress = 0
34 self._data = d
35 self._lastevent = 0
36 if outfile:
37 self._outfile = outfile
38 else:
39 self._outfile = sys.stdout
40
41 def _fire_progress(self, taskprogress, rate=None):
42 """Internal function to fire the progress event"""
43 bb.event.fire(bb.build.TaskProgress(taskprogress, rate), self._data)
44
45 def write(self, string):
46 self._outfile.write(string)
47
48 def flush(self):
49 self._outfile.flush()
50
51 def update(self, progress, rate=None):
52 ts = time.time()
53 if progress > 100:
54 progress = 100
55 if progress != self._progress or self._lastevent + 1 < ts:
56 self._fire_progress(progress, rate)
57 self._lastevent = ts
58 self._progress = progress
59
60class BasicProgressHandler(ProgressHandler):
61 def __init__(self, d, regex=r'(\d+)%', outfile=None):
62 super(BasicProgressHandler, self).__init__(d, outfile)
63 self._regex = re.compile(regex)
64 # Send an initial progress event so the bar gets shown
65 self._fire_progress(0)
66
67 def write(self, string):
68 percs = self._regex.findall(string)
69 if percs:
70 progress = int(percs[-1])
71 self.update(progress)
72 super(BasicProgressHandler, self).write(string)
73
74class OutOfProgressHandler(ProgressHandler):
75 def __init__(self, d, regex, outfile=None):
76 super(OutOfProgressHandler, self).__init__(d, outfile)
77 self._regex = re.compile(regex)
78 # Send an initial progress event so the bar gets shown
79 self._fire_progress(0)
80
81 def write(self, string):
82 nums = self._regex.findall(string)
83 if nums:
84 progress = (float(nums[-1][0]) / float(nums[-1][1])) * 100
85 self.update(progress)
86 super(OutOfProgressHandler, self).write(string)
diff --git a/bitbake/lib/bb/ui/knotty.py b/bitbake/lib/bb/ui/knotty.py
index 6a6f6888e3..2513501500 100644
--- a/bitbake/lib/bb/ui/knotty.py
+++ b/bitbake/lib/bb/ui/knotty.py
@@ -40,10 +40,13 @@ logger = logging.getLogger("BitBake")
40interactive = sys.stdout.isatty() 40interactive = sys.stdout.isatty()
41 41
42class BBProgress(progressbar.ProgressBar): 42class BBProgress(progressbar.ProgressBar):
43 def __init__(self, msg, maxval): 43 def __init__(self, msg, maxval, widgets=None):
44 self.msg = msg 44 self.msg = msg
45 widgets = [progressbar.Percentage(), ' ', progressbar.Bar(), ' ', 45 self.extrapos = -1
46 progressbar.ETA()] 46 if not widgets:
47 widgets = [progressbar.Percentage(), ' ', progressbar.Bar(), ' ',
48 progressbar.ETA()]
49 self.extrapos = 4
47 50
48 try: 51 try:
49 self._resize_default = signal.getsignal(signal.SIGWINCH) 52 self._resize_default = signal.getsignal(signal.SIGWINCH)
@@ -55,11 +58,31 @@ class BBProgress(progressbar.ProgressBar):
55 progressbar.ProgressBar._handle_resize(self, signum, frame) 58 progressbar.ProgressBar._handle_resize(self, signum, frame)
56 if self._resize_default: 59 if self._resize_default:
57 self._resize_default(signum, frame) 60 self._resize_default(signum, frame)
61
58 def finish(self): 62 def finish(self):
59 progressbar.ProgressBar.finish(self) 63 progressbar.ProgressBar.finish(self)
60 if self._resize_default: 64 if self._resize_default:
61 signal.signal(signal.SIGWINCH, self._resize_default) 65 signal.signal(signal.SIGWINCH, self._resize_default)
62 66
67 def setmessage(self, msg):
68 self.msg = msg
69 self.widgets[0] = msg
70
71 def setextra(self, extra):
72 if extra:
73 extrastr = str(extra)
74 if extrastr[0] != ' ':
75 extrastr = ' ' + extrastr
76 if extrastr[-1] != ' ':
77 extrastr += ' '
78 else:
79 extrastr = ' '
80 self.widgets[self.extrapos] = extrastr
81
82 def _need_update(self):
83 # We always want the bar to print when update() is called
84 return True
85
63class NonInteractiveProgress(object): 86class NonInteractiveProgress(object):
64 fobj = sys.stdout 87 fobj = sys.stdout
65 88
@@ -195,15 +218,31 @@ class TerminalFilter(object):
195 activetasks = self.helper.running_tasks 218 activetasks = self.helper.running_tasks
196 failedtasks = self.helper.failed_tasks 219 failedtasks = self.helper.failed_tasks
197 runningpids = self.helper.running_pids 220 runningpids = self.helper.running_pids
198 if self.footer_present and (self.lastcount == self.helper.tasknumber_current) and (self.lastpids == runningpids): 221 if self.footer_present and not self.helper.needUpdate:
199 return 222 return
223 self.helper.needUpdate = False
200 if self.footer_present: 224 if self.footer_present:
201 self.clearFooter() 225 self.clearFooter()
202 if (not self.helper.tasknumber_total or self.helper.tasknumber_current == self.helper.tasknumber_total) and not len(activetasks): 226 if (not self.helper.tasknumber_total or self.helper.tasknumber_current == self.helper.tasknumber_total) and not len(activetasks):
203 return 227 return
204 tasks = [] 228 tasks = []
205 for t in runningpids: 229 for t in runningpids:
206 tasks.append("%s (pid %s)" % (activetasks[t]["title"], t)) 230 progress = activetasks[t].get("progress", None)
231 if progress is not None:
232 pbar = activetasks[t].get("progressbar", None)
233 rate = activetasks[t].get("rate", None)
234 start_time = activetasks[t].get("starttime", None)
235 if not pbar or pbar.bouncing != (progress < 0):
236 if progress < 0:
237 pbar = BBProgress("0: %s (pid %s) " % (activetasks[t]["title"], t), 100, widgets=[progressbar.BouncingSlider()])
238 pbar.bouncing = True
239 else:
240 pbar = BBProgress("0: %s (pid %s) " % (activetasks[t]["title"], t), 100)
241 pbar.bouncing = False
242 activetasks[t]["progressbar"] = pbar
243 tasks.append((pbar, progress, rate, start_time))
244 else:
245 tasks.append("%s (pid %s)" % (activetasks[t]["title"], t))
207 246
208 if self.main.shutdown: 247 if self.main.shutdown:
209 content = "Waiting for %s running tasks to finish:" % len(activetasks) 248 content = "Waiting for %s running tasks to finish:" % len(activetasks)
@@ -214,8 +253,23 @@ class TerminalFilter(object):
214 print(content) 253 print(content)
215 lines = 1 + int(len(content) / (self.columns + 1)) 254 lines = 1 + int(len(content) / (self.columns + 1))
216 for tasknum, task in enumerate(tasks[:(self.rows - 2)]): 255 for tasknum, task in enumerate(tasks[:(self.rows - 2)]):
217 content = "%s: %s" % (tasknum, task) 256 if isinstance(task, tuple):
218 print(content) 257 pbar, progress, rate, start_time = task
258 if not pbar.start_time:
259 pbar.start(False)
260 if start_time:
261 pbar.start_time = start_time
262 pbar.setmessage('%s:%s' % (tasknum, pbar.msg.split(':', 1)[1]))
263 if progress > -1:
264 pbar.setextra(rate)
265 output = pbar.update(progress)
266 else:
267 output = pbar.update(1)
268 if not output or (len(output) <= pbar.term_width):
269 print('')
270 else:
271 content = "%s: %s" % (tasknum, task)
272 print(content)
219 lines = lines + 1 + int(len(content) / (self.columns + 1)) 273 lines = lines + 1 + int(len(content) / (self.columns + 1))
220 self.footer_present = lines 274 self.footer_present = lines
221 self.lastpids = runningpids[:] 275 self.lastpids = runningpids[:]
@@ -249,7 +303,8 @@ _evt_list = [ "bb.runqueue.runQueueExitWait", "bb.event.LogExecTTY", "logging.Lo
249 "bb.command.CommandExit", "bb.command.CommandCompleted", "bb.cooker.CookerExit", 303 "bb.command.CommandExit", "bb.command.CommandCompleted", "bb.cooker.CookerExit",
250 "bb.event.MultipleProviders", "bb.event.NoProvider", "bb.runqueue.sceneQueueTaskStarted", 304 "bb.event.MultipleProviders", "bb.event.NoProvider", "bb.runqueue.sceneQueueTaskStarted",
251 "bb.runqueue.runQueueTaskStarted", "bb.runqueue.runQueueTaskFailed", "bb.runqueue.sceneQueueTaskFailed", 305 "bb.runqueue.runQueueTaskStarted", "bb.runqueue.runQueueTaskFailed", "bb.runqueue.sceneQueueTaskFailed",
252 "bb.event.BuildBase", "bb.build.TaskStarted", "bb.build.TaskSucceeded", "bb.build.TaskFailedSilent"] 306 "bb.event.BuildBase", "bb.build.TaskStarted", "bb.build.TaskSucceeded", "bb.build.TaskFailedSilent",
307 "bb.build.TaskProgress"]
253 308
254def main(server, eventHandler, params, tf = TerminalFilter): 309def main(server, eventHandler, params, tf = TerminalFilter):
255 310
@@ -535,7 +590,8 @@ def main(server, eventHandler, params, tf = TerminalFilter):
535 bb.event.OperationStarted, 590 bb.event.OperationStarted,
536 bb.event.OperationCompleted, 591 bb.event.OperationCompleted,
537 bb.event.OperationProgress, 592 bb.event.OperationProgress,
538 bb.event.DiskFull)): 593 bb.event.DiskFull,
594 bb.build.TaskProgress)):
539 continue 595 continue
540 596
541 logger.error("Unknown event: %s", event) 597 logger.error("Unknown event: %s", event)
diff --git a/bitbake/lib/bb/ui/uihelper.py b/bitbake/lib/bb/ui/uihelper.py
index db70b763f3..1915e47703 100644
--- a/bitbake/lib/bb/ui/uihelper.py
+++ b/bitbake/lib/bb/ui/uihelper.py
@@ -18,6 +18,7 @@
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 19
20import bb.build 20import bb.build
21import time
21 22
22class BBUIHelper: 23class BBUIHelper:
23 def __init__(self): 24 def __init__(self):
@@ -31,7 +32,7 @@ class BBUIHelper:
31 32
32 def eventHandler(self, event): 33 def eventHandler(self, event):
33 if isinstance(event, bb.build.TaskStarted): 34 if isinstance(event, bb.build.TaskStarted):
34 self.running_tasks[event.pid] = { 'title' : "%s %s" % (event._package, event._task) } 35 self.running_tasks[event.pid] = { 'title' : "%s %s" % (event._package, event._task), 'starttime' : time.time() }
35 self.running_pids.append(event.pid) 36 self.running_pids.append(event.pid)
36 self.needUpdate = True 37 self.needUpdate = True
37 if isinstance(event, bb.build.TaskSucceeded): 38 if isinstance(event, bb.build.TaskSucceeded):
@@ -52,6 +53,10 @@ class BBUIHelper:
52 self.tasknumber_current = event.stats.completed + event.stats.active + event.stats.failed + 1 53 self.tasknumber_current = event.stats.completed + event.stats.active + event.stats.failed + 1
53 self.tasknumber_total = event.stats.total 54 self.tasknumber_total = event.stats.total
54 self.needUpdate = True 55 self.needUpdate = True
56 if isinstance(event, bb.build.TaskProgress):
57 self.running_tasks[event.pid]['progress'] = event.progress
58 self.running_tasks[event.pid]['rate'] = event.rate
59 self.needUpdate = True
55 60
56 def getTasks(self): 61 def getTasks(self):
57 self.needUpdate = False 62 self.needUpdate = False
diff --git a/bitbake/lib/progressbar/progressbar.py b/bitbake/lib/progressbar/progressbar.py
index 0b9dcf763e..2873ad6cae 100644
--- a/bitbake/lib/progressbar/progressbar.py
+++ b/bitbake/lib/progressbar/progressbar.py
@@ -3,6 +3,8 @@
3# progressbar - Text progress bar library for Python. 3# progressbar - Text progress bar library for Python.
4# Copyright (c) 2005 Nilton Volpato 4# Copyright (c) 2005 Nilton Volpato
5# 5#
6# (With some small changes after importing into BitBake)
7#
6# This library is free software; you can redistribute it and/or 8# This library is free software; you can redistribute it and/or
7# modify it under the terms of the GNU Lesser General Public 9# modify it under the terms of the GNU Lesser General Public
8# License as published by the Free Software Foundation; either 10# License as published by the Free Software Foundation; either
@@ -261,12 +263,14 @@ class ProgressBar(object):
261 now = time.time() 263 now = time.time()
262 self.seconds_elapsed = now - self.start_time 264 self.seconds_elapsed = now - self.start_time
263 self.next_update = self.currval + self.update_interval 265 self.next_update = self.currval + self.update_interval
264 self.fd.write(self._format_line() + '\r') 266 output = self._format_line()
267 self.fd.write(output + '\r')
265 self.fd.flush() 268 self.fd.flush()
266 self.last_update_time = now 269 self.last_update_time = now
270 return output
267 271
268 272
269 def start(self): 273 def start(self, update=True):
270 """Starts measuring time, and prints the bar at 0%. 274 """Starts measuring time, and prints the bar at 0%.
271 275
272 It returns self so you can use it like this: 276 It returns self so you can use it like this:
@@ -289,8 +293,12 @@ class ProgressBar(object):
289 self.update_interval = self.maxval / self.num_intervals 293 self.update_interval = self.maxval / self.num_intervals
290 294
291 295
292 self.start_time = self.last_update_time = time.time() 296 self.start_time = time.time()
293 self.update(0) 297 if update:
298 self.last_update_time = self.start_time
299 self.update(0)
300 else:
301 self.last_update_time = 0
294 302
295 return self 303 return self
296 304
diff --git a/bitbake/lib/progressbar/widgets.py b/bitbake/lib/progressbar/widgets.py
index 6434ad5591..77285ca7a3 100644
--- a/bitbake/lib/progressbar/widgets.py
+++ b/bitbake/lib/progressbar/widgets.py
@@ -353,3 +353,39 @@ class BouncingBar(Bar):
353 if not self.fill_left: rpad, lpad = lpad, rpad 353 if not self.fill_left: rpad, lpad = lpad, rpad
354 354
355 return '%s%s%s%s%s' % (left, lpad, marker, rpad, right) 355 return '%s%s%s%s%s' % (left, lpad, marker, rpad, right)
356
357
358class BouncingSlider(Bar):
359 """
360 A slider that bounces back and forth in response to update() calls
361 without reference to the actual value. Based on a combination of
362 BouncingBar from a newer version of this module and RotatingMarker.
363 """
364 def __init__(self, marker='<=>'):
365 self.curmark = -1
366 self.forward = True
367 Bar.__init__(self, marker=marker)
368 def update(self, pbar, width):
369 left, marker, right = (format_updatable(i, pbar) for i in
370 (self.left, self.marker, self.right))
371
372 width -= len(left) + len(right)
373 if width < 0:
374 return ''
375
376 if pbar.finished: return '%s%s%s' % (left, width * '=', right)
377
378 self.curmark = self.curmark + 1
379 position = int(self.curmark % (width * 2 - 1))
380 if position + len(marker) > width:
381 self.forward = not self.forward
382 self.curmark = 1
383 position = 1
384 lpad = ' ' * (position - 1)
385 rpad = ' ' * (width - len(marker) - len(lpad))
386
387 if not self.forward:
388 temp = lpad
389 lpad = rpad
390 rpad = temp
391 return '%s%s%s%s%s' % (left, lpad, marker, rpad, right)