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