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') 18PY_UTILS_PATH = os.path.join(CATAPULT_ROOT_PATH, 'common', 'py_utils') 19SIX_PATH = os.path.join(CATAPULT_ROOT_PATH, 'third_party', 'six') 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 31 32with SysPath(DEPENDENCY_MANAGER_PATH): 33 import dependency_manager # pylint: disable=import-error 34 35with SysPath(SIX_PATH): 36 import six 37 38_ANDROID_BUILD_TOOLS = {'aapt', 'dexdump', 'split-select'} 39 40_DEVIL_DEFAULT_CONFIG = os.path.abspath( 41 os.path.join(os.path.dirname(__file__), 'devil_dependencies.json')) 42 43_LEGACY_ENVIRONMENT_VARIABLES = { 44 'ADB_PATH': { 45 'dependency_name': 'adb', 46 'platform': 'linux2_x86_64', 47 }, 48 'ANDROID_SDK_ROOT': { 49 'dependency_name': 'android_sdk', 50 'platform': 'linux2_x86_64', 51 }, 52} 53 54 55def EmptyConfig(): 56 return {'config_type': 'BaseConfig', 'dependencies': {}} 57 58 59def LocalConfigItem(dependency_name, dependency_platform, dependency_path): 60 if isinstance(dependency_path, six.string_types): 61 dependency_path = [dependency_path] 62 return { 63 dependency_name: { 64 'file_info': { 65 dependency_platform: { 66 'local_paths': dependency_path 67 }, 68 }, 69 }, 70 } 71 72 73def _GetEnvironmentVariableConfig(): 74 env_config = EmptyConfig() 75 path_config = ((os.environ.get(k), v) 76 for k, v in six.iteritems(_LEGACY_ENVIRONMENT_VARIABLES)) 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 def __init__(self): 86 self._dm_init_lock = threading.Lock() 87 self._dm = None 88 self._logging_init_lock = threading.Lock() 89 self._logging_initialized = False 90 91 def Initialize(self, configs=None, config_files=None): 92 """Initialize devil's environment from configuration files. 93 94 This uses all configurations provided via |configs| and |config_files| 95 to determine the locations of devil's dependencies. Configurations should 96 all take the form described by py_utils.dependency_manager.BaseConfig. 97 If no configurations are provided, a default one will be used if available. 98 99 Args: 100 configs: An optional list of dict configurations. 101 config_files: An optional list of files to load 102 """ 103 104 # Make sure we only initialize self._dm once. 105 with self._dm_init_lock: 106 if self._dm is None: 107 if configs is None: 108 configs = [] 109 110 env_config = _GetEnvironmentVariableConfig() 111 if env_config: 112 configs.insert(0, env_config) 113 self._InitializeRecursive(configs=configs, config_files=config_files) 114 assert self._dm is not None, 'Failed to create dependency manager.' 115 116 def _InitializeRecursive(self, configs=None, config_files=None): 117 # This recurses through configs to create temporary files for each and 118 # take advantage of context managers to appropriately close those files. 119 # TODO(jbudorick): Remove this recursion if/when dependency_manager 120 # supports loading configurations directly from a dict. 121 if configs: 122 with tempfile.NamedTemporaryFile(mode='w', 123 delete=False) as next_config_file: 124 try: 125 next_config_file.write(json.dumps(configs[0])) 126 next_config_file.close() 127 self._InitializeRecursive( 128 configs=configs[1:], 129 config_files=[next_config_file.name] + (config_files or [])) 130 finally: 131 if os.path.exists(next_config_file.name): 132 os.remove(next_config_file.name) 133 else: 134 config_files = config_files or [] 135 if 'DEVIL_ENV_CONFIG' in os.environ: 136 config_files.append(os.environ.get('DEVIL_ENV_CONFIG')) 137 config_files.append(_DEVIL_DEFAULT_CONFIG) 138 139 self._dm = dependency_manager.DependencyManager( 140 [dependency_manager.BaseConfig(c) for c in config_files]) 141 142 def InitializeLogging(self, log_level, formatter=None, handler=None): 143 if self._logging_initialized: 144 return 145 146 with self._logging_init_lock: 147 if self._logging_initialized: 148 return 149 150 formatter = formatter or logging.Formatter( 151 '%(threadName)-4s %(message)s') 152 handler = handler or logging.StreamHandler(sys.stdout) 153 handler.setFormatter(formatter) 154 155 devil_logger = logging.getLogger('devil') 156 devil_logger.setLevel(log_level) 157 devil_logger.propagate = False 158 devil_logger.addHandler(handler) 159 160 with SysPath(PY_UTILS_PATH): 161 import py_utils.cloud_storage 162 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 # use 'linux2' for linux as this is already used in json file 191 return '%s_%s' % ( 192 sys.platform if not sys.platform.startswith('linux') else 'linux2', 193 platform.machine()) 194 195 196config = _Environment() 197