Home | History | Annotate | Download | only in devil
      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 
      5 import contextlib
      6 import json
      7 import logging
      8 import os
      9 import platform
     10 import sys
     11 import tempfile
     12 import threading
     13 
     14 CATAPULT_ROOT_PATH = os.path.abspath(os.path.join(
     15     os.path.dirname(__file__), '..', '..'))
     16 DEPENDENCY_MANAGER_PATH = os.path.join(
     17     CATAPULT_ROOT_PATH, 'dependency_manager')
     18 PYMOCK_PATH = os.path.join(
     19     CATAPULT_ROOT_PATH, 'third_party', 'mock')
     20 
     21 
     22 @contextlib.contextmanager
     23 def 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 with 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 
     51 def EmptyConfig():
     52   return {
     53     'config_type': 'BaseConfig',
     54     'dependencies': {}
     55   }
     56 
     57 
     58 def 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 
     72 def _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 
     84 class _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 
    187 def 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 
    193 config = _Environment()
    194 
    195