Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / build / android / gyp / util / build_utils.py
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
5 import ast
6 import contextlib
7 import fnmatch
8 import json
9 import os
10 import pipes
11 import re
12 import shlex
13 import shutil
14 import subprocess
15 import sys
16 import tempfile
17 import zipfile
18
19
20 CHROMIUM_SRC = os.path.normpath(
21     os.path.join(os.path.dirname(__file__),
22                  os.pardir, os.pardir, os.pardir, os.pardir))
23 COLORAMA_ROOT = os.path.join(CHROMIUM_SRC,
24                              'third_party', 'colorama', 'src')
25
26
27 @contextlib.contextmanager
28 def TempDir():
29   dirname = tempfile.mkdtemp()
30   try:
31     yield dirname
32   finally:
33     shutil.rmtree(dirname)
34
35
36 def MakeDirectory(dir_path):
37   try:
38     os.makedirs(dir_path)
39   except OSError:
40     pass
41
42
43 def DeleteDirectory(dir_path):
44   if os.path.exists(dir_path):
45     shutil.rmtree(dir_path)
46
47
48 def Touch(path, fail_if_missing=False):
49   if fail_if_missing and not os.path.exists(path):
50     raise Exception(path + ' doesn\'t exist.')
51
52   MakeDirectory(os.path.dirname(path))
53   with open(path, 'a'):
54     os.utime(path, None)
55
56
57 def FindInDirectory(directory, filename_filter):
58   files = []
59   for root, _dirnames, filenames in os.walk(directory):
60     matched_files = fnmatch.filter(filenames, filename_filter)
61     files.extend((os.path.join(root, f) for f in matched_files))
62   return files
63
64
65 def FindInDirectories(directories, filename_filter):
66   all_files = []
67   for directory in directories:
68     all_files.extend(FindInDirectory(directory, filename_filter))
69   return all_files
70
71
72 def ParseGnList(gn_string):
73   return ast.literal_eval(gn_string)
74
75
76 def ParseGypList(gyp_string):
77   # The ninja generator doesn't support $ in strings, so use ## to
78   # represent $.
79   # TODO(cjhopman): Remove when
80   # https://code.google.com/p/gyp/issues/detail?id=327
81   # is addressed.
82   gyp_string = gyp_string.replace('##', '$')
83
84   if gyp_string.startswith('['):
85     return ParseGnList(gyp_string)
86   return shlex.split(gyp_string)
87
88
89 def CheckOptions(options, parser, required=None):
90   if not required:
91     return
92   for option_name in required:
93     if getattr(options, option_name) is None:
94       parser.error('--%s is required' % option_name.replace('_', '-'))
95
96
97 def WriteJson(obj, path, only_if_changed=False):
98   old_dump = None
99   if os.path.exists(path):
100     with open(path, 'r') as oldfile:
101       old_dump = oldfile.read()
102
103   new_dump = json.dumps(obj, sort_keys=True, indent=2, separators=(',', ': '))
104
105   if not only_if_changed or old_dump != new_dump:
106     with open(path, 'w') as outfile:
107       outfile.write(new_dump)
108
109
110 def ReadJson(path):
111   with open(path, 'r') as jsonfile:
112     return json.load(jsonfile)
113
114
115 class CalledProcessError(Exception):
116   """This exception is raised when the process run by CheckOutput
117   exits with a non-zero exit code."""
118
119   def __init__(self, cwd, args, output):
120     super(CalledProcessError, self).__init__()
121     self.cwd = cwd
122     self.args = args
123     self.output = output
124
125   def __str__(self):
126     # A user should be able to simply copy and paste the command that failed
127     # into their shell.
128     copyable_command = '( cd {}; {} )'.format(os.path.abspath(self.cwd),
129         ' '.join(map(pipes.quote, self.args)))
130     return 'Command failed: {}\n{}'.format(copyable_command, self.output)
131
132
133 # This can be used in most cases like subprocess.check_output(). The output,
134 # particularly when the command fails, better highlights the command's failure.
135 # If the command fails, raises a build_utils.CalledProcessError.
136 def CheckOutput(args, cwd=None,
137                 print_stdout=False, print_stderr=True,
138                 stdout_filter=None,
139                 stderr_filter=None,
140                 fail_func=lambda returncode, stderr: returncode != 0):
141   if not cwd:
142     cwd = os.getcwd()
143
144   child = subprocess.Popen(args,
145       stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd)
146   stdout, stderr = child.communicate()
147
148   if stdout_filter is not None:
149     stdout = stdout_filter(stdout)
150
151   if stderr_filter is not None:
152     stderr = stderr_filter(stderr)
153
154   if fail_func(child.returncode, stderr):
155     raise CalledProcessError(cwd, args, stdout + stderr)
156
157   if print_stdout:
158     sys.stdout.write(stdout)
159   if print_stderr:
160     sys.stderr.write(stderr)
161
162   return stdout
163
164
165 def GetModifiedTime(path):
166   # For a symlink, the modified time should be the greater of the link's
167   # modified time and the modified time of the target.
168   return max(os.lstat(path).st_mtime, os.stat(path).st_mtime)
169
170
171 def IsTimeStale(output, inputs):
172   if not os.path.exists(output):
173     return True
174
175   output_time = GetModifiedTime(output)
176   for i in inputs:
177     if GetModifiedTime(i) > output_time:
178       return True
179   return False
180
181
182 def IsDeviceReady():
183   device_state = CheckOutput(['adb', 'get-state'])
184   return device_state.strip() == 'device'
185
186
187 def CheckZipPath(name):
188   if os.path.normpath(name) != name:
189     raise Exception('Non-canonical zip path: %s' % name)
190   if os.path.isabs(name):
191     raise Exception('Absolute zip path: %s' % name)
192
193
194 def ExtractAll(zip_path, path=None, no_clobber=True):
195   if path is None:
196     path = os.getcwd()
197   elif not os.path.exists(path):
198     MakeDirectory(path)
199
200   with zipfile.ZipFile(zip_path) as z:
201     for name in z.namelist():
202       if name.endswith('/'):
203         continue
204       CheckZipPath(name)
205       if no_clobber:
206         output_path = os.path.join(path, name)
207         if os.path.exists(output_path):
208           raise Exception(
209               'Path already exists from zip: %s %s %s'
210               % (zip_path, name, output_path))
211
212     z.extractall(path=path)
213
214
215 def DoZip(inputs, output, base_dir):
216   with zipfile.ZipFile(output, 'w') as outfile:
217     for f in inputs:
218       CheckZipPath(os.path.relpath(f, base_dir))
219       outfile.write(f, os.path.relpath(f, base_dir))
220
221
222 def ZipDir(output, base_dir):
223   with zipfile.ZipFile(output, 'w') as outfile:
224     for root, _, files in os.walk(base_dir):
225       for f in files:
226         path = os.path.join(root, f)
227         archive_path = os.path.relpath(path, base_dir)
228         CheckZipPath(archive_path)
229         outfile.write(path, archive_path)
230
231
232 def PrintWarning(message):
233   print 'WARNING: ' + message
234
235
236 def PrintBigWarning(message):
237   print '*****     ' * 8
238   PrintWarning(message)
239   print '*****     ' * 8
240
241
242 def GetSortedTransitiveDependencies(top, deps_func):
243   """Gets the list of all transitive dependencies in sorted order.
244
245   There should be no cycles in the dependency graph.
246
247   Args:
248     top: a list of the top level nodes
249     deps_func: A function that takes a node and returns its direct dependencies.
250   Returns:
251     A list of all transitive dependencies of nodes in top, in order (a node will
252     appear in the list at a higher index than all of its dependencies).
253   """
254   def Node(dep):
255     return (dep, deps_func(dep))
256
257   # First: find all deps
258   unchecked_deps = list(top)
259   all_deps = set(top)
260   while unchecked_deps:
261     dep = unchecked_deps.pop()
262     new_deps = deps_func(dep).difference(all_deps)
263     unchecked_deps.extend(new_deps)
264     all_deps = all_deps.union(new_deps)
265
266   # Then: simple, slow topological sort.
267   sorted_deps = []
268   unsorted_deps = dict(map(Node, all_deps))
269   while unsorted_deps:
270     for library, dependencies in unsorted_deps.items():
271       if not dependencies.intersection(unsorted_deps.keys()):
272         sorted_deps.append(library)
273         del unsorted_deps[library]
274
275   return sorted_deps
276
277
278 def GetPythonDependencies():
279   """Gets the paths of imported non-system python modules.
280
281   A path is assumed to be a "system" import if it is outside of chromium's
282   src/. The paths will be relative to the current directory.
283   """
284   module_paths = (m.__file__ for m in sys.modules.itervalues()
285                   if m is not None and hasattr(m, '__file__'))
286
287   abs_module_paths = map(os.path.abspath, module_paths)
288
289   non_system_module_paths = [
290       p for p in abs_module_paths if p.startswith(CHROMIUM_SRC)]
291   def ConvertPycToPy(s):
292     if s.endswith('.pyc'):
293       return s[:-1]
294     return s
295
296   non_system_module_paths = map(ConvertPycToPy, non_system_module_paths)
297   non_system_module_paths = map(os.path.relpath, non_system_module_paths)
298   return sorted(set(non_system_module_paths))
299
300
301 def AddDepfileOption(parser):
302   parser.add_option('--depfile',
303                     help='Path to depfile. This must be specified as the '
304                     'action\'s first output.')
305
306
307 def WriteDepfile(path, dependencies):
308   with open(path, 'w') as depfile:
309     depfile.write(path)
310     depfile.write(': ')
311     depfile.write(' '.join(dependencies))
312     depfile.write('\n')
313
314
315 def ExpandFileArgs(args):
316   """Replaces file-arg placeholders in args.
317
318   These placeholders have the form:
319     @FileArg(filename:key1:key2:...:keyn)
320
321   The value of such a placeholder is calculated by reading 'filename' as json.
322   And then extracting the value at [key1][key2]...[keyn].
323
324   Note: This intentionally does not return the list of files that appear in such
325   placeholders. An action that uses file-args *must* know the paths of those
326   files prior to the parsing of the arguments (typically by explicitly listing
327   them in the action's inputs in build files).
328   """
329   new_args = list(args)
330   file_jsons = dict()
331   r = re.compile('@FileArg\((.*?)\)')
332   for i, arg in enumerate(args):
333     match = r.search(arg)
334     if not match:
335       continue
336
337     if match.end() != len(arg):
338       raise Exception('Unexpected characters after FileArg: ' + arg)
339
340     lookup_path = match.group(1).split(':')
341     file_path = lookup_path[0]
342     if not file_path in file_jsons:
343       file_jsons[file_path] = ReadJson(file_path)
344
345     expansion = file_jsons[file_path]
346     for k in lookup_path[1:]:
347       expansion = expansion[k]
348
349     new_args[i] = arg[:match.start()] + str(expansion)
350
351   return new_args
352