• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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