| OLD | NEW |
| 1 # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. | 1 # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """Operation, including output and progress display | 5 """Operation, including output and progress display |
| 6 | 6 |
| 7 This module implements the concept of an operation, which has regular progress | 7 This module implements the concept of an operation, which has regular progress |
| 8 updates, verbose text display and perhaps some errors. | 8 updates, verbose text display and perhaps some errors. |
| 9 """ | 9 """ |
| 10 | 10 |
| (...skipping 15 matching lines...) Expand all Loading... |
| 26 it looking for errors and progress information. Optionally it can output the | 26 it looking for errors and progress information. Optionally it can output the |
| 27 stderr and stdout to the terminal, but it is normally supressed. | 27 stderr and stdout to the terminal, but it is normally supressed. |
| 28 | 28 |
| 29 Progress information is garnered from the subprocess output based on | 29 Progress information is garnered from the subprocess output based on |
| 30 knowledge of the legacy scripts, but at some point will move over to using | 30 knowledge of the legacy scripts, but at some point will move over to using |
| 31 real progress information reported through new python methods which will | 31 real progress information reported through new python methods which will |
| 32 replace the scripts. | 32 replace the scripts. |
| 33 | 33 |
| 34 Each operation has a name, and this class handles displaying this name | 34 Each operation has a name, and this class handles displaying this name |
| 35 as it reports progress. | 35 as it reports progress. |
| 36 | |
| 37 | |
| 38 Operation Objects | |
| 39 ================= | |
| 40 | |
| 41 verbose: True / False | |
| 42 In verbose mode all output from subprocesses is displayed, otherwise | |
| 43 this output is normally supressed, unless we think it indicates an error. | |
| 44 | |
| 45 progress: True / False | |
| 46 The output from subprocesses can be analysed in a very basic manner to | |
| 47 try to present progress information to the user. | |
| 48 """ | 36 """ |
| 49 # Force color on/off, or use color only if stdout is a terminal. | 37 # Force color on/off, or use color only if stdout is a terminal. |
| 50 COLOR_OFF, COLOR_ON, COLOR_IF_TERMINAL = range(3) | 38 COLOR_OFF, COLOR_ON, COLOR_IF_TERMINAL = range(3) |
| 51 | 39 |
| 52 def __init__(self, name, color=COLOR_IF_TERMINAL): | 40 def __init__(self, name, color=COLOR_IF_TERMINAL): |
| 53 """Create a new operation. | 41 """Create a new operation. |
| 54 | 42 |
| 55 Args: | 43 Args: |
| 56 name: Operation name in a form to be displayed for the user. | 44 name: Operation name in a form to be displayed for the user. |
| 57 color: Determines policy for sending color to stdout: | 45 color: Determines policy for sending color to stdout: |
| 58 COLOR_OFF: never send color. | 46 COLOR_OFF: never send color. |
| 59 COLOR_ON: always send color. | 47 COLOR_ON: always send color. |
| 60 COLOR_IF_TERMINAL: send color if output apperas to be a terminal. | 48 COLOR_IF_TERMINAL: send color if output apperas to be a terminal. |
| 61 """ | 49 """ |
| 62 self._name = name # Operation name. | 50 self._name = name # Operation name. |
| 63 self.verbose = False # True to echo subprocess output. | 51 self._verbose = False # True to echo subprocess output. |
| 64 self.progress = True # True to report progress of the operation | 52 self._progress = True # True to report progress of the operation |
| 65 self._column = 0 # Current output column (always 0 unless verbose). | 53 self._column = 0 # Current output column (always 0 unless verbose). |
| 66 self._update_len = 0 # Length of last progress update message. | 54 self._update_len = 0 # Length of last progress update message. |
| 67 self._line = '' # text of current line, so far | 55 self._line = '' # text of current line, so far |
| 68 | 56 |
| 69 # By default, we display ANSI colors unless output is redirected. | 57 # By default, we display ANSI colors unless output is redirected. |
| 70 want_color = (color == self.COLOR_ON or | 58 want_color = (color == self.COLOR_ON or |
| 71 (color == self.COLOR_IF_TERMINAL and os.isatty(sys.stdout.fileno()))) | 59 (color == self.COLOR_IF_TERMINAL and os.isatty(sys.stdout.fileno()))) |
| 72 self._color = Color(want_color) | 60 self._color = Color(want_color) |
| 73 | 61 |
| 74 # -1 = no newline pending | 62 # -1 = no newline pending |
| (...skipping 10 matching lines...) Expand all Loading... |
| 85 def __del__(self): | 73 def __del__(self): |
| 86 """Object is about to be destroyed, so finish out output cleanly.""" | 74 """Object is about to be destroyed, so finish out output cleanly.""" |
| 87 self.FinishOutput() | 75 self.FinishOutput() |
| 88 | 76 |
| 89 def FinishOutput(self): | 77 def FinishOutput(self): |
| 90 """Finish off any pending output. | 78 """Finish off any pending output. |
| 91 | 79 |
| 92 This finishes any output line currently in progress and resets the color | 80 This finishes any output line currently in progress and resets the color |
| 93 back to normal. | 81 back to normal. |
| 94 """ | 82 """ |
| 95 self._FinishLine(self.verbose, final=True) | 83 self._FinishLine(self._verbose, final=True) |
| 96 if self._column and self.verbose: | 84 if self._column and self._verbose: |
| 97 print self._color.Stop() | 85 print self._color.Stop() |
| 98 self._column = 0 | 86 self._column = 0 |
| 99 | 87 |
| 100 def WereErrorsDetected(self): | 88 def WereErrorsDetected(self): |
| 101 """Returns whether any errors have been detected. | 89 """Returns whether any errors have been detected. |
| 102 | 90 |
| 103 Returns: | 91 Returns: |
| 104 True if any errors have been detected in subprocess output so far. | 92 True if any errors have been detected in subprocess output so far. |
| 105 False otherwise | 93 False otherwise |
| 106 """ | 94 """ |
| 107 return self._error_count > 0 | 95 return self._error_count > 0 |
| 108 | 96 |
| 97 def SetVerbose(self, verbose): |
| 98 """Enable / disable verbose mode. |
| 99 |
| 100 In verbose mode all output from subprocesses is displayed, otherwise |
| 101 this output is normally supressed, unless we think it indicates an error. |
| 102 |
| 103 Args: |
| 104 verbose: True to display all subprocess output, False to supress. |
| 105 """ |
| 106 self._verbose = verbose |
| 107 |
| 108 def SetProgress(self, progress): |
| 109 """Enable / disable progress display. |
| 110 |
| 111 The output from subprocesses can be analysed in a very basic manner to |
| 112 try to present progress information to the user. |
| 113 |
| 114 Args: |
| 115 progress: True to enable progress display, False to disable. |
| 116 """ |
| 117 self._progress = progress |
| 118 |
| 109 def SetName(self, name): | 119 def SetName(self, name): |
| 110 """Set the name of the operation as displayed to the user. | 120 """Set the name of the operation as displayed to the user. |
| 111 | 121 |
| 112 Args: | 122 Args: |
| 113 name: Operation name. | 123 name: Operation name. |
| 114 """ | 124 """ |
| 115 self._name = name | 125 self._name = name |
| 116 | 126 |
| 117 def _FilterOutputForErrors(self, line, print_error): | 127 def _FilterOutputForErrors(self, line, print_error): |
| 118 """Filter a line of output to look for and display errors. | 128 """Filter a line of output to look for and display errors. |
| 119 | 129 |
| 120 This uses a few regular expression searches to spot common error reports | 130 This uses a few regular expression searches to spot common error reports |
| 121 from subprocesses. A count of these is kept so we know how many occurred. | 131 from subprocesses. A count of these is kept so we know how many occurred. |
| 122 Optionally they are displayed in red on the terminal. | 132 Optionally they are displayed in red on the terminal. |
| 123 | 133 |
| 124 Args: | 134 Args: |
| 125 line: the output line to filter, as a string. | 135 line: the output line to filter, as a string. |
| 126 print_error: True to print the error, False to just record it. | 136 print_it: True to print the error, False to just record it. |
| 127 """ | 137 """ |
| 128 bad_things = ['Cannot GET', 'ERROR', '!!!', 'FAILED'] | 138 bad_things = ['Cannot GET', 'ERROR', '!!!', 'FAILED'] |
| 129 for bad_thing in bad_things: | 139 for bad_thing in bad_things: |
| 130 if re.search(bad_thing, line, flags=re.IGNORECASE): | 140 if re.search(bad_thing, line, flags=re.IGNORECASE): |
| 131 self._error_count += 1 | 141 self._error_count += 1 |
| 132 if print_error: | 142 if print_error: |
| 133 print self._color.Color(self._color.RED, line) | 143 print self._color.Color(self._color.RED, line) |
| 134 break | 144 break |
| 135 | 145 |
| 136 def _FilterOutputForProgress(self, line): | 146 def _FilterOutputForProgress(self, line): |
| (...skipping 14 matching lines...) Expand all Loading... |
| 151 def _Progress(self, upto, total): | 161 def _Progress(self, upto, total): |
| 152 """Record and optionally display progress information. | 162 """Record and optionally display progress information. |
| 153 | 163 |
| 154 Args: | 164 Args: |
| 155 upto: which step we are up to in the operation (integer, from 0). | 165 upto: which step we are up to in the operation (integer, from 0). |
| 156 total: total number of steps in operation, | 166 total: total number of steps in operation, |
| 157 """ | 167 """ |
| 158 if total > 0: | 168 if total > 0: |
| 159 update_str = '%s...%d%% (%d of %d)' % (self._name, | 169 update_str = '%s...%d%% (%d of %d)' % (self._name, |
| 160 upto * 100 // total, upto, total) | 170 upto * 100 // total, upto, total) |
| 161 if self.progress: | 171 if self._progress: |
| 162 # Finish the current line, print progress, and remember its length. | 172 # Finish the current line, print progress, and remember its length. |
| 163 self._FinishLine(self.verbose) | 173 self._FinishLine(self._verbose) |
| 164 | 174 |
| 165 # Sometimes the progress string shrinks and in this case we need to | 175 # Sometimes the progress string shrinks and in this case we need to |
| 166 # blank out the characters at the end of the line that will not be | 176 # blank out the characters at the end of the line that will not be |
| 167 # overwritten by the new line | 177 # overwritten by the new line |
| 168 pad = max(self._update_len - len(update_str), 0) | 178 pad = max(self._update_len - len(update_str), 0) |
| 169 sys.stdout.write(update_str + (' ' * pad) + '\r') | 179 sys.stdout.write(update_str + (' ' * pad) + '\r') |
| 170 self._update_len = len(update_str) | 180 self._update_len = len(update_str) |
| 171 | 181 |
| 172 def _FinishLine(self, display, final=False): | 182 def _FinishLine(self, display, final=False): |
| 173 """Finish off the current line and prepare to start a new one. | 183 """Finish off the current line and prepare to start a new one. |
| (...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 277 self._column += len(text) | 287 self._column += len(text) |
| 278 # If a newline is required, remember to output it later. | 288 # If a newline is required, remember to output it later. |
| 279 if newline: | 289 if newline: |
| 280 self._pending_nl = self._column | 290 self._pending_nl = self._column |
| 281 self._column = 0 | 291 self._column = 0 |
| 282 | 292 |
| 283 self._line += text | 293 self._line += text |
| 284 | 294 |
| 285 # If we now have a whole line, check it for errors and progress. | 295 # If we now have a whole line, check it for errors and progress. |
| 286 if newline: | 296 if newline: |
| 287 self._FilterOutputForErrors(self._line, print_error=not display) | 297 self._FilterOutputForErrors(self._line, print_it=not display) |
| 288 self._FilterOutputForProgress(self._line) | 298 self._FilterOutputForProgress(self._line) |
| 289 self._line = '' | 299 self._line = '' |
| 290 | 300 |
| 291 def Output(self, stream, data): | 301 def Output(self, stream, data): |
| 292 """Handle the output of a block of text from the subprocess. | 302 """Handle the output of a block of text from the subprocess. |
| 293 | 303 |
| 294 All subprocess output should be sent through this method. It is split into | 304 All subprocess output should be sent through this method. It is split into |
| 295 lines which are processed separately using the _Out() method. | 305 lines which are processed separately using the _Out() method. |
| 296 | 306 |
| 297 Args: | 307 Args: |
| 298 stream: Which file the output come in on: | 308 stream: Which file the output come in on: |
| 299 sys.stdout: stdout | 309 sys.stdout: stdout |
| 300 sys.stderr: stderr | 310 sys.stderr: stderr |
| 301 None: Our own internal output | 311 None: Our own internal output |
| 302 data: Output data as a big string, potentially containing many lines of | 312 data: Output data as a big string, potentially containing many lines of |
| 303 text. Each line should end with \r\n. There is no requirement to send | 313 text. Each line should end with \r\n. There is no requirement to send |
| 304 whole lines - this method happily handles fragments and tries to | 314 whole lines - this method happily handles fragments and tries to |
| 305 present then to the user as early as possible | 315 present then to the user as early as possible |
| 306 | 316 |
| 307 #TODO(sjg): Just use a list as the input parameter to avoid the split. | 317 #TODO(sjg): Just use a list as the input parameter to avoid the split. |
| 308 """ | 318 """ |
| 309 # We cannot use splitlines() here as we need this exact behavior | 319 lines = data.splitlines() |
| 310 lines = data.split('\r\n') | |
| 311 | 320 |
| 312 # Output each full line, with a \n after it. | 321 # Output each full line, with a \n after it. |
| 313 for line in lines[:-1]: | 322 for line in lines[:-1]: |
| 314 self._Out(stream, line, display=self.verbose, newline=True) | 323 self._Out(stream, line, display=self._verbose, newline=True) |
| 315 | 324 |
| 316 # If we have a partial line at the end, output what we have. | 325 # If we have a partial line at the end, output what we have. |
| 317 # We will continue it later. | 326 # We will continue it later. |
| 318 if lines[-1]: | 327 if lines[-1]: |
| 319 self._Out(stream, lines[-1], display=self.verbose) | 328 self._Out(stream, lines[-1], display=self._verbose) |
| 320 | 329 |
| 321 # Flush so that the terminal will receive partial line output (now!) | 330 # Flush so that the terminal will receive partial line output (now!) |
| 322 sys.stdout.flush() | 331 sys.stdout.flush() |
| 323 | 332 |
| 324 def Outline(self, line): | 333 def Outline(self, line): |
| 325 """Output a line of text to the display. | 334 """Output a line of text to the display. |
| 326 | 335 |
| 327 This outputs text generated internally, such as a warning message or error | 336 This outputs text generated internally, such as a warning message or error |
| 328 summary. It ensures that our message plays nicely with child output if | 337 summary. It ensures that our message plays nicely with child output if |
| 329 any. | 338 any. |
| 330 | 339 |
| 331 Args: | 340 Args: |
| 332 line: text to output (without \n on the end) | 341 line: text to output (without \n on the end) |
| 333 """ | 342 """ |
| 334 self._Out(None, line, display=True, newline=True) | 343 self._Out(None, line, display=True, newline=True) |
| 335 self._FinishLine(display=True) | 344 self._FinishLine(display=True) |
| 336 | |
| 337 def Info(self, line): | |
| 338 """Output a line of information text to the display in verbose mode. | |
| 339 | |
| 340 Args: | |
| 341 line: text to output (without \n on the end) | |
| 342 """ | |
| 343 self._Out(None, self._color.Color(self._color.BLUE, line), | |
| 344 display=self.verbose, newline=True) | |
| 345 self._FinishLine(display=True) | |
| OLD | NEW |