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