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 os
8import platform
9import sys
10import tempfile
11import threading
12
13CATAPULT_ROOT_PATH = os.path.abspath(os.path.join(
14    os.path.dirname(__file__), '..', '..'))
15DEPENDENCY_MANAGER_PATH = os.path.join(
16    CATAPULT_ROOT_PATH, 'dependency_manager')
17PYMOCK_PATH = os.path.join(
18    CATAPULT_ROOT_PATH, 'third_party', 'mock')
19
20
21@contextlib.contextmanager
22def SysPath(path):
23  sys.path.append(path)
24  yield
25  if sys.path[-1] != path:
26    sys.path.remove(path)
27  else:
28    sys.path.pop()
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(os.path.join(
36    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 _GetEnvironmentVariableConfig():
51  path_config = (
52      (os.environ.get(k), v)
53      for k, v in _LEGACY_ENVIRONMENT_VARIABLES.iteritems())
54  return {
55    'config_type': 'BaseConfig',
56    'dependencies': {
57      c['dependency_name']: {
58        'file_info': {
59          c['platform']: {
60            'local_paths': [p],
61          },
62        },
63      } for p, c in path_config if p
64    },
65  }
66
67
68class _Environment(object):
69
70  def __init__(self):
71    self._dm_init_lock = threading.Lock()
72    self._dm = None
73
74  def Initialize(self, configs=None, config_files=None):
75    """Initialize devil's environment from configuration files.
76
77    This uses all configurations provided via |configs| and |config_files|
78    to determine the locations of devil's dependencies. Configurations should
79    all take the form described by catapult_base.dependency_manager.BaseConfig.
80    If no configurations are provided, a default one will be used if available.
81
82    Args:
83      configs: An optional list of dict configurations.
84      config_files: An optional list of files to load
85    """
86
87    # Make sure we only initialize self._dm once.
88    with self._dm_init_lock:
89      if self._dm is None:
90        if configs is None:
91          configs = []
92
93        env_config = _GetEnvironmentVariableConfig()
94        if env_config:
95          configs.insert(0, env_config)
96        self._InitializeRecursive(
97            configs=configs,
98            config_files=config_files)
99        assert self._dm is not None, 'Failed to create dependency manager.'
100
101  def _InitializeRecursive(self, configs=None, config_files=None):
102    # This recurses through configs to create temporary files for each and
103    # take advantage of context managers to appropriately close those files.
104    # TODO(jbudorick): Remove this recursion if/when dependency_manager
105    # supports loading configurations directly from a dict.
106    if configs:
107      with tempfile.NamedTemporaryFile(delete=False) as next_config_file:
108        try:
109          next_config_file.write(json.dumps(configs[0]))
110          next_config_file.close()
111          self._InitializeRecursive(
112              configs=configs[1:],
113              config_files=[next_config_file.name] + (config_files or []))
114        finally:
115          if os.path.exists(next_config_file.name):
116            os.remove(next_config_file.name)
117    else:
118      config_files = config_files or []
119      if 'DEVIL_ENV_CONFIG' in os.environ:
120        config_files.append(os.environ.get('DEVIL_ENV_CONFIG'))
121      config_files.append(_DEVIL_DEFAULT_CONFIG)
122
123      self._dm = dependency_manager.DependencyManager(
124          [dependency_manager.BaseConfig(c) for c in config_files])
125
126  def FetchPath(self, dependency, arch=None, device=None):
127    if self._dm is None:
128      self.Initialize()
129    if dependency in _ANDROID_BUILD_TOOLS:
130      self.FetchPath('android_build_tools_libc++', arch=arch, device=device)
131    return self._dm.FetchPath(dependency, GetPlatform(arch, device))
132
133  def LocalPath(self, dependency, arch=None, device=None):
134    if self._dm is None:
135      self.Initialize()
136    return self._dm.LocalPath(dependency, GetPlatform(arch, device))
137
138
139def GetPlatform(arch=None, device=None):
140  if device:
141    return 'android_%s' % (arch or device.product_cpu_abi)
142  return '%s_%s' % (sys.platform, platform.machine())
143
144
145config = _Environment()
146
147