• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2014 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 glob
7import os
8import shutil
9import sys
10import tempfile
11
12from devil.utils import cmd_helper
13from pylib import constants
14from pylib.constants import host_paths
15
16
17_ISOLATE_SCRIPT = os.path.join(
18    host_paths.DIR_SOURCE_ROOT, 'tools', 'swarming_client', 'isolate.py')
19
20
21def DefaultPathVariables():
22  return {
23    'DEPTH': host_paths.DIR_SOURCE_ROOT,
24    'PRODUCT_DIR': constants.GetOutDirectory(),
25  }
26
27
28def DefaultConfigVariables():
29  # Note: This list must match the --config-vars in build/isolate.gypi
30  return {
31    'CONFIGURATION_NAME': constants.GetBuildType(),
32    'OS': 'android',
33    'asan': '0',
34    'branding': 'Chromium',
35    'chromeos': '0',
36    'component': 'static_library',
37    'enable_pepper_cdms': '0',
38    'enable_plugins': '0',
39    'fastbuild': '0',
40    'icu_use_data_file_flag': '1',
41    'kasko': '0',
42    'lsan': '0',
43    'msan': '0',
44    # TODO(maruel): This may not always be true.
45    'target_arch': 'arm',
46    'tsan': '0',
47    'use_custom_libcxx': '0',
48    'use_instrumented_libraries': '0',
49    'use_prebuilt_instrumented_libraries': '0',
50    'use_ozone': '0',
51    'use_x11': '0',
52    'v8_use_external_startup_data': '1',
53    'msvs_version': '0',
54  }
55
56
57def IsIsolateEmpty(isolate_path):
58  """Returns whether there are no files in the .isolate."""
59  with open(isolate_path) as f:
60    return "'files': []" in f.read()
61
62
63class Isolator(object):
64  """Manages calls to isolate.py for the android test runner scripts."""
65
66  def __init__(self):
67    self._isolate_deps_dir = tempfile.mkdtemp()
68
69  @property
70  def isolate_deps_dir(self):
71    return self._isolate_deps_dir
72
73  def Clear(self):
74    """Deletes the isolate dependency directory."""
75    if os.path.exists(self._isolate_deps_dir):
76      shutil.rmtree(self._isolate_deps_dir)
77
78  def Remap(self, isolate_abs_path, isolated_abs_path,
79            path_variables=None, config_variables=None):
80    """Remaps data dependencies into |self._isolate_deps_dir|.
81
82    Args:
83      isolate_abs_path: The absolute path to the .isolate file, which specifies
84        data dependencies in the source tree.
85      isolated_abs_path: The absolute path to the .isolated file, which is
86        generated by isolate.py and specifies data dependencies in
87        |self._isolate_deps_dir| and their digests.
88      path_variables: A dict containing everything that should be passed
89        as a |--path-variable| to the isolate script. Defaults to the return
90        value of |DefaultPathVariables()|.
91      config_variables: A dict containing everything that should be passed
92        as a |--config-variable| to the isolate script. Defaults to the return
93        value of |DefaultConfigVariables()|.
94    Raises:
95      Exception if the isolate command fails for some reason.
96    """
97    if not path_variables:
98      path_variables = DefaultPathVariables()
99    if not config_variables:
100      config_variables = DefaultConfigVariables()
101
102    isolate_cmd = [
103      sys.executable, _ISOLATE_SCRIPT, 'remap',
104      '--isolate', isolate_abs_path,
105      '--isolated', isolated_abs_path,
106      '--outdir', self._isolate_deps_dir,
107    ]
108    for k, v in path_variables.iteritems():
109      isolate_cmd.extend(['--path-variable', k, v])
110    for k, v in config_variables.iteritems():
111      isolate_cmd.extend(['--config-variable', k, v])
112
113    exit_code, _ = cmd_helper.GetCmdStatusAndOutput(isolate_cmd)
114    if exit_code:
115      raise Exception('isolate command failed: %s' % ' '.join(isolate_cmd))
116
117  def VerifyHardlinks(self):
118    """Checks |isolate_deps_dir| for a hardlink.
119
120    Returns:
121      True if a hardlink is found.
122      False if nothing is found.
123    Raises:
124      Exception if a non-hardlink is found.
125    """
126    for root, _, filenames in os.walk(self._isolate_deps_dir):
127      if filenames:
128        linked_file = os.path.join(root, filenames[0])
129        orig_file = os.path.join(
130            self._isolate_deps_dir,
131            os.path.relpath(linked_file, self._isolate_deps_dir))
132        if os.stat(linked_file).st_ino == os.stat(orig_file).st_ino:
133          return True
134        else:
135          raise Exception('isolate remap command did not use hardlinks.')
136    return False
137
138  def PurgeExcluded(self, deps_exclusion_list):
139    """Deletes anything on |deps_exclusion_list| from |self._isolate_deps_dir|.
140
141    Args:
142      deps_exclusion_list: A list of globs to exclude from the isolate
143        dependency directory.
144    """
145    excluded_paths = (
146        x for y in deps_exclusion_list
147        for x in glob.glob(
148            os.path.abspath(os.path.join(self._isolate_deps_dir, y))))
149    for p in excluded_paths:
150      if os.path.isdir(p):
151        shutil.rmtree(p)
152      else:
153        os.remove(p)
154
155  @classmethod
156  def _DestructiveMerge(cls, src, dest):
157    if os.path.exists(dest) and os.path.isdir(dest):
158      for p in os.listdir(src):
159        cls._DestructiveMerge(os.path.join(src, p), os.path.join(dest, p))
160      os.rmdir(src)
161    else:
162      shutil.move(src, dest)
163
164
165  def MoveOutputDeps(self):
166    """Moves files from the output directory to the top level of
167      |self._isolate_deps_dir|.
168
169    Moves pak files from the output directory to to <isolate_deps_dir>/paks
170    Moves files from the product directory to <isolate_deps_dir>
171    """
172    # On Android, all pak files need to be in the top-level 'paks' directory.
173    paks_dir = os.path.join(self._isolate_deps_dir, 'paks')
174    os.mkdir(paks_dir)
175
176    deps_out_dir = os.path.join(
177        self._isolate_deps_dir,
178        os.path.relpath(os.path.join(constants.GetOutDirectory(), os.pardir),
179                        host_paths.DIR_SOURCE_ROOT))
180    for root, _, filenames in os.walk(deps_out_dir):
181      for filename in fnmatch.filter(filenames, '*.pak'):
182        shutil.move(os.path.join(root, filename), paks_dir)
183
184    # Move everything in PRODUCT_DIR to top level.
185    deps_product_dir = os.path.join(
186        deps_out_dir, os.path.basename(constants.GetOutDirectory()))
187    if os.path.isdir(deps_product_dir):
188      for p in os.listdir(deps_product_dir):
189        Isolator._DestructiveMerge(os.path.join(deps_product_dir, p),
190                                   os.path.join(self._isolate_deps_dir, p))
191      os.rmdir(deps_product_dir)
192      os.rmdir(deps_out_dir)
193