Scott Zawalski | 6bc41ac | 2010-09-08 12:47:28 -0700 | [diff] [blame] | 1 | # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
| 4 | |
| 5 | """Common python commands used by various build scripts.""" |
| 6 | |
Scott Zawalski | 98ac6b2 | 2010-09-08 15:59:23 -0700 | [diff] [blame] | 7 | import os |
Scott Zawalski | 6bc41ac | 2010-09-08 12:47:28 -0700 | [diff] [blame] | 8 | import subprocess |
| 9 | import sys |
| 10 | |
| 11 | _STDOUT_IS_TTY = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty() |
| 12 | |
| 13 | def RunCommand(cmd, print_cmd=True, error_ok=False, error_message=None, |
| 14 | exit_code=False, redirect_stdout=False, redirect_stderr=False, |
David James | 6db8f52 | 2010-09-09 10:49:11 -0700 | [diff] [blame] | 15 | cwd=None, input=None, enter_chroot=False, shell=False): |
| 16 | """Runs a command. |
Scott Zawalski | 6bc41ac | 2010-09-08 12:47:28 -0700 | [diff] [blame] | 17 | |
| 18 | Keyword arguments: |
David James | 6db8f52 | 2010-09-09 10:49:11 -0700 | [diff] [blame] | 19 | cmd - cmd to run. Should be input to subprocess.Popen. |
Scott Zawalski | 6bc41ac | 2010-09-08 12:47:28 -0700 | [diff] [blame] | 20 | print_cmd -- prints the command before running it. |
| 21 | error_ok -- does not raise an exception on error. |
| 22 | error_message -- prints out this message when an error occurrs. |
| 23 | exit_code -- returns the return code of the shell command. |
| 24 | redirect_stdout -- returns the stdout. |
| 25 | redirect_stderr -- holds stderr output until input is communicated. |
| 26 | cwd -- the working directory to run this cmd. |
| 27 | input -- input to pipe into this command through stdin. |
| 28 | enter_chroot -- this command should be run from within the chroot. If set, |
| 29 | cwd must point to the scripts directory. |
David James | 6db8f52 | 2010-09-09 10:49:11 -0700 | [diff] [blame] | 30 | shell -- If shell is True, the specified command will be executed through the shell. |
Scott Zawalski | 6bc41ac | 2010-09-08 12:47:28 -0700 | [diff] [blame] | 31 | Raises: |
| 32 | Exception: Raises generic exception on error with optional error_message. |
| 33 | """ |
| 34 | # Set default for variables. |
| 35 | stdout = None |
| 36 | stderr = None |
| 37 | stdin = None |
| 38 | output = '' |
| 39 | |
| 40 | # Modify defaults based on parameters. |
| 41 | if redirect_stdout: stdout = subprocess.PIPE |
| 42 | if redirect_stderr: stderr = subprocess.PIPE |
| 43 | if input: stdin = subprocess.PIPE |
David James | 6db8f52 | 2010-09-09 10:49:11 -0700 | [diff] [blame] | 44 | if isinstance(cmd, basestring): |
| 45 | if enter_chroot: cmd = './enter_chroot.sh -- ' + cmd |
| 46 | cmd_str = cmd |
| 47 | else: |
| 48 | if enter_chroot: cmd = ['./enter_chroot.sh', '--'] + cmd |
| 49 | cmd_str = ' '.join(cmd) |
Scott Zawalski | 6bc41ac | 2010-09-08 12:47:28 -0700 | [diff] [blame] | 50 | |
| 51 | # Print out the command before running. |
| 52 | if print_cmd: |
David James | 6db8f52 | 2010-09-09 10:49:11 -0700 | [diff] [blame] | 53 | Info('RunCommand: %s' % cmd_str) |
Scott Zawalski | 6bc41ac | 2010-09-08 12:47:28 -0700 | [diff] [blame] | 54 | |
| 55 | try: |
| 56 | proc = subprocess.Popen(cmd, cwd=cwd, stdin=stdin, |
David James | 6db8f52 | 2010-09-09 10:49:11 -0700 | [diff] [blame] | 57 | stdout=stdout, stderr=stderr, |
| 58 | shell=shell) |
Scott Zawalski | 6bc41ac | 2010-09-08 12:47:28 -0700 | [diff] [blame] | 59 | (output, error) = proc.communicate(input) |
| 60 | if exit_code: |
| 61 | return proc.returncode |
| 62 | |
| 63 | if not error_ok and proc.returncode: |
David James | 6db8f52 | 2010-09-09 10:49:11 -0700 | [diff] [blame] | 64 | raise Exception('Command "%s" failed.\n' % cmd_str + |
Scott Zawalski | 6bc41ac | 2010-09-08 12:47:28 -0700 | [diff] [blame] | 65 | (error_message or error or output or '')) |
| 66 | except Exception,e: |
| 67 | if not error_ok: |
| 68 | raise |
| 69 | else: |
| 70 | Warning(str(e)) |
| 71 | |
| 72 | return output |
| 73 | |
| 74 | |
| 75 | class Color(object): |
| 76 | """Conditionally wraps text in ANSI color escape sequences.""" |
| 77 | BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) |
| 78 | BOLD = -1 |
| 79 | COLOR_START = '\033[1;%dm' |
| 80 | BOLD_START = '\033[1m' |
| 81 | RESET = '\033[0m' |
| 82 | |
| 83 | def __init__(self, enabled=True): |
| 84 | self._enabled = enabled |
| 85 | |
| 86 | def Color(self, color, text): |
| 87 | """Returns text with conditionally added color escape sequences. |
| 88 | |
| 89 | Keyword arguments: |
| 90 | color: Text color -- one of the color constants defined in this class. |
| 91 | text: The text to color. |
| 92 | |
| 93 | Returns: |
| 94 | If self._enabled is False, returns the original text. If it's True, |
| 95 | returns text with color escape sequences based on the value of color. |
| 96 | """ |
| 97 | if not self._enabled: |
| 98 | return text |
| 99 | if color == self.BOLD: |
| 100 | start = self.BOLD_START |
| 101 | else: |
| 102 | start = self.COLOR_START % (color + 30) |
| 103 | return start + text + self.RESET |
| 104 | |
| 105 | |
| 106 | def Die(message): |
| 107 | """Emits a red error message and halts execution. |
| 108 | |
| 109 | Keyword arguments: |
| 110 | message: The message to be emitted before exiting. |
| 111 | """ |
| 112 | print >> sys.stderr, ( |
| 113 | Color(_STDOUT_IS_TTY).Color(Color.RED, '\nERROR: ' + message)) |
| 114 | sys.exit(1) |
| 115 | |
| 116 | |
| 117 | def Warning(message): |
| 118 | """Emits a yellow warning message and continues execution. |
| 119 | |
| 120 | Keyword arguments: |
| 121 | message: The message to be emitted. |
| 122 | """ |
| 123 | print >> sys.stderr, ( |
| 124 | Color(_STDOUT_IS_TTY).Color(Color.YELLOW, '\nWARNING: ' + message)) |
| 125 | |
| 126 | |
| 127 | def Info(message): |
| 128 | """Emits a blue informational message and continues execution. |
| 129 | |
| 130 | Keyword arguments: |
| 131 | message: The message to be emitted. |
| 132 | """ |
| 133 | print >> sys.stderr, ( |
| 134 | Color(_STDOUT_IS_TTY).Color(Color.BLUE, '\nINFO: ' + message)) |
Scott Zawalski | 98ac6b2 | 2010-09-08 15:59:23 -0700 | [diff] [blame] | 135 | |
| 136 | |
| 137 | def ListFiles(base_dir): |
| 138 | """Recurively list files in a directory. |
| 139 | |
| 140 | Keyword arguments: |
| 141 | base_dir: directory to start recursively listing in. |
| 142 | |
| 143 | Returns: |
| 144 | A list of files relative to the base_dir path or |
| 145 | An empty list of there are no files in the directories. |
| 146 | """ |
| 147 | directories = [base_dir] |
| 148 | files_list = [] |
| 149 | while directories: |
| 150 | directory = directories.pop() |
| 151 | for name in os.listdir(directory): |
| 152 | fullpath = os.path.join(directory, name) |
| 153 | if os.path.isfile(fullpath): |
| 154 | files_list.append(fullpath) |
| 155 | elif os.path.isdir(fullpath): |
| 156 | directories.append(fullpath) |
| 157 | |
| 158 | return files_list |