1# Copyright 2013 The Chromium 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 5import contextlib 6import fnmatch 7import json 8import os 9import pipes 10import shlex 11import shutil 12import subprocess 13import sys 14import tempfile 15import zipfile 16 17CHROMIUM_SRC = os.path.join(os.path.dirname(__file__), 18 os.pardir, os.pardir, os.pardir, os.pardir) 19COLORAMA_ROOT = os.path.join(CHROMIUM_SRC, 20 'third_party', 'colorama', 'src') 21 22 23@contextlib.contextmanager 24def TempDir(): 25 dirname = tempfile.mkdtemp() 26 try: 27 yield dirname 28 finally: 29 shutil.rmtree(dirname) 30 31 32def MakeDirectory(dir_path): 33 try: 34 os.makedirs(dir_path) 35 except OSError: 36 pass 37 38 39def DeleteDirectory(dir_path): 40 if os.path.exists(dir_path): 41 shutil.rmtree(dir_path) 42 43 44def Touch(path): 45 MakeDirectory(os.path.dirname(path)) 46 with open(path, 'a'): 47 os.utime(path, None) 48 49 50def FindInDirectory(directory, filename_filter): 51 files = [] 52 for root, _dirnames, filenames in os.walk(directory): 53 matched_files = fnmatch.filter(filenames, filename_filter) 54 files.extend((os.path.join(root, f) for f in matched_files)) 55 return files 56 57 58def FindInDirectories(directories, filename_filter): 59 all_files = [] 60 for directory in directories: 61 all_files.extend(FindInDirectory(directory, filename_filter)) 62 return all_files 63 64 65def ParseGypList(gyp_string): 66 # The ninja generator doesn't support $ in strings, so use ## to 67 # represent $. 68 # TODO(cjhopman): Remove when 69 # https://code.google.com/p/gyp/issues/detail?id=327 70 # is addressed. 71 gyp_string = gyp_string.replace('##', '$') 72 return shlex.split(gyp_string) 73 74 75def CheckOptions(options, parser, required=None): 76 if not required: 77 return 78 for option_name in required: 79 if getattr(options, option_name) is None: 80 parser.error('--%s is required' % option_name.replace('_', '-')) 81 82def WriteJson(obj, path, only_if_changed=False): 83 old_dump = None 84 if os.path.exists(path): 85 with open(path, 'r') as oldfile: 86 old_dump = oldfile.read() 87 88 new_dump = json.dumps(obj, sort_keys=True, indent=2, separators=(',', ': ')) 89 90 if not only_if_changed or old_dump != new_dump: 91 with open(path, 'w') as outfile: 92 outfile.write(new_dump) 93 94def ReadJson(path): 95 with open(path, 'r') as jsonfile: 96 return json.load(jsonfile) 97 98 99class CalledProcessError(Exception): 100 """This exception is raised when the process run by CheckOutput 101 exits with a non-zero exit code.""" 102 103 def __init__(self, cwd, args, output): 104 super(CalledProcessError, self).__init__() 105 self.cwd = cwd 106 self.args = args 107 self.output = output 108 109 def __str__(self): 110 # A user should be able to simply copy and paste the command that failed 111 # into their shell. 112 copyable_command = '( cd {}; {} )'.format(os.path.abspath(self.cwd), 113 ' '.join(map(pipes.quote, self.args))) 114 return 'Command failed: {}\n{}'.format(copyable_command, self.output) 115 116 117# This can be used in most cases like subprocess.check_output(). The output, 118# particularly when the command fails, better highlights the command's failure. 119# If the command fails, raises a build_utils.CalledProcessError. 120def CheckOutput(args, cwd=None, 121 print_stdout=False, print_stderr=True, 122 stdout_filter=None, 123 stderr_filter=None, 124 fail_func=lambda returncode, stderr: returncode != 0): 125 if not cwd: 126 cwd = os.getcwd() 127 128 child = subprocess.Popen(args, 129 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd) 130 stdout, stderr = child.communicate() 131 132 if stdout_filter is not None: 133 stdout = stdout_filter(stdout) 134 135 if stderr_filter is not None: 136 stderr = stderr_filter(stderr) 137 138 if fail_func(child.returncode, stderr): 139 raise CalledProcessError(cwd, args, stdout + stderr) 140 141 if print_stdout: 142 sys.stdout.write(stdout) 143 if print_stderr: 144 sys.stderr.write(stderr) 145 146 return stdout 147 148 149def GetModifiedTime(path): 150 # For a symlink, the modified time should be the greater of the link's 151 # modified time and the modified time of the target. 152 return max(os.lstat(path).st_mtime, os.stat(path).st_mtime) 153 154 155def IsTimeStale(output, inputs): 156 if not os.path.exists(output): 157 return True 158 159 output_time = GetModifiedTime(output) 160 for i in inputs: 161 if GetModifiedTime(i) > output_time: 162 return True 163 return False 164 165 166def IsDeviceReady(): 167 device_state = CheckOutput(['adb', 'get-state']) 168 return device_state.strip() == 'device' 169 170 171def CheckZipPath(name): 172 if os.path.normpath(name) != name: 173 raise Exception('Non-canonical zip path: %s' % name) 174 if os.path.isabs(name): 175 raise Exception('Absolute zip path: %s' % name) 176 177 178def ExtractAll(zip_path, path=None, no_clobber=True): 179 if path is None: 180 path = os.getcwd() 181 elif not os.path.exists(path): 182 MakeDirectory(path) 183 184 with zipfile.ZipFile(zip_path) as z: 185 for name in z.namelist(): 186 CheckZipPath(name) 187 if no_clobber: 188 output_path = os.path.join(path, name) 189 if os.path.exists(output_path): 190 raise Exception( 191 'Path already exists from zip: %s %s %s' 192 % (zip_path, name, output_path)) 193 194 z.extractall(path=path) 195 196 197def DoZip(inputs, output, base_dir): 198 with zipfile.ZipFile(output, 'w') as outfile: 199 for f in inputs: 200 CheckZipPath(os.path.relpath(f, base_dir)) 201 outfile.write(f, os.path.relpath(f, base_dir)) 202 203 204def PrintWarning(message): 205 print 'WARNING: ' + message 206 207 208def PrintBigWarning(message): 209 print '***** ' * 8 210 PrintWarning(message) 211 print '***** ' * 8 212