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