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 fnmatch 6import json 7import os 8import pipes 9import shlex 10import shutil 11import subprocess 12import sys 13import traceback 14 15 16def MakeDirectory(dir_path): 17 try: 18 os.makedirs(dir_path) 19 except OSError: 20 pass 21 22 23def DeleteDirectory(dir_path): 24 if os.path.exists(dir_path): 25 shutil.rmtree(dir_path) 26 27 28def Touch(path): 29 MakeDirectory(os.path.dirname(path)) 30 with open(path, 'a'): 31 os.utime(path, None) 32 33 34def FindInDirectory(directory, filter): 35 files = [] 36 for root, dirnames, filenames in os.walk(directory): 37 matched_files = fnmatch.filter(filenames, filter) 38 files.extend((os.path.join(root, f) for f in matched_files)) 39 return files 40 41 42def FindInDirectories(directories, filter): 43 all_files = [] 44 for directory in directories: 45 all_files.extend(FindInDirectory(directory, filter)) 46 return all_files 47 48 49def ParseGypList(gyp_string): 50 # The ninja generator doesn't support $ in strings, so use ## to 51 # represent $. 52 # TODO(cjhopman): Remove when 53 # https://code.google.com/p/gyp/issues/detail?id=327 54 # is addressed. 55 gyp_string = gyp_string.replace('##', '$') 56 return shlex.split(gyp_string) 57 58 59def CheckOptions(options, parser, required=[]): 60 for option_name in required: 61 if not getattr(options, option_name): 62 parser.error('--%s is required' % option_name.replace('_', '-')) 63 64def WriteJson(obj, path, only_if_changed=False): 65 old_dump = None 66 if os.path.exists(path): 67 with open(path, 'r') as oldfile: 68 old_dump = oldfile.read() 69 70 new_dump = json.dumps(obj) 71 72 if not only_if_changed or old_dump != new_dump: 73 with open(path, 'w') as outfile: 74 outfile.write(new_dump) 75 76def ReadJson(path): 77 with open(path, 'r') as jsonfile: 78 return json.load(jsonfile) 79 80 81class CalledProcessError(Exception): 82 """This exception is raised when the process run by CheckOutput 83 exits with a non-zero exit code.""" 84 85 def __init__(self, cwd, args, output): 86 self.cwd = cwd 87 self.args = args 88 self.output = output 89 90 def __str__(self): 91 # A user should be able to simply copy and paste the command that failed 92 # into their shell. 93 copyable_command = '( cd {}; {} )'.format(os.path.abspath(self.cwd), 94 ' '.join(map(pipes.quote, self.args))) 95 return 'Command failed: {}\n{}'.format(copyable_command, self.output) 96 97 98# This can be used in most cases like subprocess.check_output(). The output, 99# particularly when the command fails, better highlights the command's failure. 100# If the command fails, raises a build_utils.CalledProcessError. 101def CheckOutput(args, cwd=None, print_stdout=False, print_stderr=True, 102 fail_if_stderr=False): 103 if not cwd: 104 cwd = os.getcwd() 105 106 child = subprocess.Popen(args, 107 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd) 108 stdout, stderr = child.communicate() 109 110 if child.returncode or (stderr and fail_if_stderr): 111 raise CalledProcessError(cwd, args, stdout + stderr) 112 113 if print_stdout: 114 sys.stdout.write(stdout) 115 if print_stderr: 116 sys.stderr.write(stderr) 117 118 return stdout 119 120 121def GetModifiedTime(path): 122 # For a symlink, the modified time should be the greater of the link's 123 # modified time and the modified time of the target. 124 return max(os.lstat(path).st_mtime, os.stat(path).st_mtime) 125 126 127def IsTimeStale(output, inputs): 128 if not os.path.exists(output): 129 return True 130 131 output_time = GetModifiedTime(output) 132 for input in inputs: 133 if GetModifiedTime(input) > output_time: 134 return True 135 return False 136 137 138def IsDeviceReady(): 139 device_state = CheckOutput(['adb', 'get-state']) 140 return device_state.strip() == 'device' 141 142 143def PrintWarning(message): 144 print 'WARNING: ' + message 145 146 147def PrintBigWarning(message): 148 print '***** ' * 8 149 PrintWarning(message) 150 print '***** ' * 8 151