1# Copyright 2015 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 json
7import logging
8import os
9import platform
10import sys
11import tempfile
12import threading
13
14CATAPULT_ROOT_PATH = os.path.abspath(
15    os.path.join(os.path.dirname(__file__), '..', '..'))
16DEPENDENCY_MANAGER_PATH = os.path.join(CATAPULT_ROOT_PATH, 'dependency_manager')
17PYMOCK_PATH = os.path.join(CATAPULT_ROOT_PATH, 'third_party', 'mock')
18
19
20@contextlib.contextmanager
21def SysPath(path):
22  sys.path.append(path)
23  yield
24  if sys.path[-1] != path:
25    sys.path.remove(path)
26  else:
27    sys.path.pop()
28
29
30with SysPath(DEPENDENCY_MANAGER_PATH):
31  import dependency_manager  # pylint: disable=import-error
32
33_ANDROID_BUILD_TOOLS = {'aapt', 'dexdump', 'split-select'}
34
35_DEVIL_DEFAULT_CONFIG = os.path.abspath(
36    os.path.join(os.path.dirname(__file__), 'devil_dependencies.json'))
37
38_LEGACY_ENVIRONMENT_VARIABLES = {
39    'ADB_PATH': {
40        'dependency_name': 'adb',
41        'platform': 'linux2_x86_64',
42    },
43    'ANDROID_SDK_ROOT': {
44        'dependency_name': 'android_sdk',
45        'platform': 'linux2_x86_64',
46    },
47}
48
49
50def EmptyConfig():
51  return {'config_type': 'BaseConfig', 'dependencies': {}}
52
53
54def LocalConfigItem(dependency_name, dependency_platform, dependency_path):
55  if isinstance(dependency_path, basestring):
56    dependency_path = [dependency_path]
57  return {
58      dependency_name: {
59          'file_info': {
60              dependency_platform: {
61                  'local_paths': dependency_path
62              },
63          },
64      },
65  }
66
67
68def _GetEnvironmentVariableConfig():
69  env_config = EmptyConfig()
70  path_config = ((os.environ.get(k), v)
71                 for k, v in _LEGACY_ENVIRONMENT_VARIABLES.iteritems())
72  path_config = ((p, c) for p, c in path_config if p)
73  for p, c in path_config:
74    env_config['dependencies'].update(
75        LocalConfigItem(c['dependency_name'], c['platform'], p))
76  return env_config
77
78
79class _Environment(object):
80  def __init__(self):
81    self._dm_init_lock = threading.Lock()
82    self._dm = None
83    self._logging_init_lock = threading.Lock()
84    self._logging_initialized = False
85
86  def Initialize(self, configs=None, config_files=None):
87    """Initialize devil's environment from configuration files.
88
89    This uses all configurations provided via |configs| and |config_files|
90    to determine the locations of devil's dependencies. Configurations should
91    all take the form described by py_utils.dependency_manager.BaseConfig.
92    If no configurations are provided, a default one will be used if available.
93
94    Args:
95      configs: An optional list of dict configurations.
96      config_files: An optional list of files to load
97    """
98
99    # Make sure we only initialize self._dm once.
100    with self._dm_init_lock:
101      if self._dm is None:
102        if configs is None:
103          configs = []
104
105        env_config = _GetEnvironmentVariableConfig()
106        if env_config:
107          configs.insert(0, env_config)
108        self._InitializeRecursive(configs=configs, config_files=config_files)
109        assert self._dm is not None, 'Failed to create dependency manager.'
110
111  def _InitializeRecursive(self, configs=None, config_files=None):
112    # This recurses through configs to create temporary files for each and
113    # take advantage of context managers to appropriately close those files.
114    # TODO(jbudorick): Remove this recursion if/when dependency_manager
115    # supports loading configurations directly from a dict.
116    if configs:
117      with tempfile.NamedTemporaryFile(delete=False) as next_config_file:
118        try:
119          next_config_file.write(json.dumps(configs[0]))
120          next_config_file.close()
121          self._InitializeRecursive(
122              configs=configs[1:],
123              config_files=[next_config_file.name] + (config_files or []))
124        finally:
125          if os.path.exists(next_config_file.name):
126            os.remove(next_config_file.name)
127    else:
128      config_files = config_files or []
129      if 'DEVIL_ENV_CONFIG' in os.environ:
130        config_files.append(os.environ.get('DEVIL_ENV_CONFIG'))
131      config_files.append(_DEVIL_DEFAULT_CONFIG)
132
133      self._dm = dependency_manager.DependencyManager(
134          [dependency_manager.BaseConfig(c) for c in config_files])
135
136  def InitializeLogging(self, log_level, formatter=None, handler=None):
137    if self._logging_initialized:
138      return
139
140    with self._logging_init_lock:
141      if self._logging_initialized:
142        return
143
144      formatter = formatter or logging.Formatter(
145          '%(threadName)-4s  %(message)s')
146      handler = handler or logging.StreamHandler(sys.stdout)
147      handler.setFormatter(formatter)
148
149      devil_logger = logging.getLogger('devil')
150      devil_logger.setLevel(log_level)
151      devil_logger.propagate = False
152      devil_logger.addHandler(handler)
153
154      import py_utils.cloud_storage
155      lock_logger = py_utils.cloud_storage.logger
156      lock_logger.setLevel(log_level)
157      lock_logger.propagate = False
158      lock_logger.addHandler(handler)
159
160      self._logging_initialized = True
161
162  def FetchPath(self, dependency, arch=None, device=None):
163    if self._dm is None:
164      self.Initialize()
165    if dependency in _ANDROID_BUILD_TOOLS:
166      self.FetchPath('android_build_tools_libc++', arch=arch, device=device)
167    return self._dm.FetchPath(dependency, GetPlatform(arch, device))
168
169  def LocalPath(self, dependency, arch=None, device=None):
170    if self._dm is None:
171      self.Initialize()
172    return self._dm.LocalPath(dependency, GetPlatform(arch, device))
173
174  def PrefetchPaths(self, dependencies=None, arch=None, device=None):
175    return self._dm.PrefetchPaths(
176        GetPlatform(arch, device), dependencies=dependencies)
177
178
179def GetPlatform(arch=None, device=None):
180  if arch or device:
181    return 'android_%s' % (arch or device.product_cpu_abi)
182  return '%s_%s' % (sys.platform, platform.machine())
183
184
185config = _Environment()
186