Home | History | Annotate | Download | only in server2
      1 # Copyright 2013 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 collections
      6 import os
      7 
      8 from branch_utility import BranchUtility
      9 from compiled_file_system import CompiledFileSystem
     10 from file_system import FileNotFoundError
     11 import svn_constants
     12 from third_party.json_schema_compiler import json_parse, model
     13 from third_party.json_schema_compiler.memoize import memoize
     14 
     15 _API_AVAILABILITIES = svn_constants.JSON_PATH + '/api_availabilities.json'
     16 _API_FEATURES = svn_constants.API_PATH + '/_api_features.json'
     17 _EXTENSION_API = svn_constants.API_PATH + '/extension_api.json'
     18 _MANIFEST_FEATURES = svn_constants.API_PATH + '/_manifest_features.json'
     19 _PERMISSION_FEATURES = svn_constants.API_PATH + '/_permission_features.json'
     20 _STABLE = 'stable'
     21 
     22 class AvailabilityInfo(object):
     23   def __init__(self, channel, version):
     24     self.channel = channel
     25     self.version = version
     26 
     27 def _GetChannelFromFeatures(api_name, file_system, path):
     28   '''Finds API channel information within _features.json files at the given
     29   |path| for the given |file_system|. Returns None if channel information for
     30   the API cannot be located.
     31   '''
     32   feature = file_system.GetFromFile(path).get(api_name)
     33 
     34   if feature is None:
     35     return None
     36   if isinstance(feature, collections.Mapping):
     37     # The channel information exists as a solitary dict.
     38     return feature.get('channel')
     39   # The channel information dict is nested within a list for whitelisting
     40   # purposes. Take the newest channel out of all of the entries.
     41   return BranchUtility.NewestChannel(entry.get('channel') for entry in feature)
     42 
     43 def _GetChannelFromApiFeatures(api_name, file_system):
     44   try:
     45     return _GetChannelFromFeatures(api_name, file_system, _API_FEATURES)
     46   except FileNotFoundError:
     47     # TODO(epeterson) Remove except block once _api_features is in all channels.
     48     return None
     49 
     50 def _GetChannelFromPermissionFeatures(api_name, file_system):
     51   return _GetChannelFromFeatures(api_name, file_system, _PERMISSION_FEATURES)
     52 
     53 def _GetChannelFromManifestFeatures(api_name, file_system):
     54   return _GetChannelFromFeatures(#_manifest_features uses unix_style API names
     55                                  model.UnixName(api_name),
     56                                  file_system,
     57                                  _MANIFEST_FEATURES)
     58 
     59 def _ExistsInFileSystem(api_name, file_system):
     60   '''Checks for existence of |api_name| within the list of files in the api/
     61   directory found using the given file system.
     62   '''
     63   file_names = file_system.GetFromFileListing(svn_constants.API_PATH)
     64   # File names switch from unix_hacker_style to camelCase at versions <= 20.
     65   return model.UnixName(api_name) in file_names or api_name in file_names
     66 
     67 def _ExistsInExtensionApi(api_name, file_system):
     68   '''Parses the api/extension_api.json file (available in Chrome versions
     69   before 18) for an API namespace. If this is successfully found, then the API
     70   is considered to have been 'stable' for the given version.
     71   '''
     72   try:
     73     extension_api_json = file_system.GetFromFile(_EXTENSION_API)
     74     api_rows = [row.get('namespace') for row in extension_api_json
     75                 if 'namespace' in row]
     76     return True if api_name in api_rows else False
     77   except FileNotFoundError:
     78     # This should only happen on preview.py since extension_api.json is no
     79     # longer present in trunk.
     80     return False
     81 
     82 class AvailabilityFinder(object):
     83   '''Uses API data sources generated by a ChromeVersionDataSource in order to
     84   search the filesystem for the earliest existence of a specified API throughout
     85   the different versions of Chrome; this constitutes an API's availability.
     86   '''
     87   class Factory(object):
     88     def __init__(self,
     89                  object_store_creator,
     90                  compiled_host_fs_factory,
     91                  branch_utility,
     92                  host_file_system_creator):
     93       self._object_store_creator = object_store_creator
     94       self._compiled_host_fs_factory = compiled_host_fs_factory
     95       self._branch_utility = branch_utility
     96       self._host_file_system_creator = host_file_system_creator
     97 
     98     def Create(self):
     99       return AvailabilityFinder(self._object_store_creator,
    100                                 self._compiled_host_fs_factory,
    101                                 self._branch_utility,
    102                                 self._host_file_system_creator)
    103 
    104   def __init__(self,
    105                object_store_creator,
    106                compiled_host_fs_factory,
    107                branch_utility,
    108                host_file_system_creator):
    109     self._object_store_creator = object_store_creator
    110     self._json_cache = compiled_host_fs_factory.Create(
    111         lambda _, json: json_parse.Parse(json),
    112         AvailabilityFinder,
    113         'json-cache')
    114     self._branch_utility = branch_utility
    115     self._host_file_system_creator = host_file_system_creator
    116     self._object_store = object_store_creator.Create(AvailabilityFinder)
    117 
    118   @memoize
    119   def _CreateFeaturesAndNamesFileSystems(self, version):
    120     '''The 'features' compiled file system's populate function parses and
    121     returns the contents of a _features.json file. The 'names' compiled file
    122     system's populate function creates a list of file names with .json or .idl
    123     extensions.
    124     '''
    125     fs_factory = CompiledFileSystem.Factory(
    126         self._host_file_system_creator.Create(
    127             self._branch_utility.GetBranchForVersion(version)),
    128         self._object_store_creator)
    129     features_fs = fs_factory.Create(lambda _, json: json_parse.Parse(json),
    130                                     AvailabilityFinder,
    131                                     category='features')
    132     names_fs = fs_factory.Create(self._GetExtNames,
    133                                  AvailabilityFinder,
    134                                  category='names')
    135     return (features_fs, names_fs)
    136 
    137   def _GetExtNames(self, base_path, apis):
    138     return [os.path.splitext(api)[0] for api in apis
    139             if os.path.splitext(api)[1][1:] in ['json', 'idl']]
    140 
    141   def _FindEarliestStableAvailability(self, api_name, version):
    142     '''Searches in descending order through filesystem caches tied to specific
    143     chrome version numbers and looks for the availability of an API, |api_name|,
    144     on the stable channel. When a version is found where the API is no longer
    145     available on stable, returns the previous version number (the last known
    146     version where the API was stable).
    147     '''
    148     available = True
    149     while available:
    150       if version < 5:
    151         # SVN data isn't available below version 5.
    152         return version + 1
    153       available = False
    154       available_channel = None
    155       features_fs, names_fs = self._CreateFeaturesAndNamesFileSystems(version)
    156       if version >= 28:
    157         # The _api_features.json file first appears in version 28 and should be
    158         # the most reliable for finding API availabilities, so it gets checked
    159         # first. The _permission_features.json and _manifest_features.json files
    160         # are present in Chrome 20 and onwards. Fall back to a check for file
    161         # system existence if the API is not stable in any of the _features.json
    162         # files.
    163         available_channel = _GetChannelFromApiFeatures(api_name, features_fs)
    164       if version >= 20:
    165         # Check other _features.json files/file existence if the API wasn't
    166         # found in _api_features.json, or if _api_features.json wasn't present.
    167         available_channel = available_channel or (
    168             _GetChannelFromPermissionFeatures(api_name, features_fs)
    169             or _GetChannelFromManifestFeatures(api_name, features_fs))
    170         if available_channel is None:
    171           available = _ExistsInFileSystem(api_name, names_fs)
    172         else:
    173           available = available_channel == _STABLE
    174       elif version >= 18:
    175         # These versions are a little troublesome. Version 19 has
    176         # _permission_features.json, but it lacks 'channel' information.
    177         # Version 18 lacks all of the _features.json files. For now, we're using
    178         # a simple check for filesystem existence here.
    179         available = _ExistsInFileSystem(api_name, names_fs)
    180       elif version >= 5:
    181         # Versions 17 and down to 5 have an extension_api.json file which
    182         # contains namespaces for each API that was available at the time. We
    183         # can use this file to check for API existence.
    184         available = _ExistsInExtensionApi(api_name, features_fs)
    185 
    186       if not available:
    187         return version + 1
    188       version -= 1
    189 
    190   def _GetAvailableChannelForVersion(self, api_name, version):
    191     '''Searches through the _features files for a given |version| and returns
    192     the channel that the given API is determined to be available on.
    193     '''
    194     features_fs, names_fs = self._CreateFeaturesAndNamesFileSystems(version)
    195     available_channel = (_GetChannelFromApiFeatures(api_name, features_fs)
    196         or _GetChannelFromPermissionFeatures(api_name, features_fs)
    197         or _GetChannelFromManifestFeatures(api_name, features_fs))
    198     if available_channel is None and _ExistsInFileSystem(api_name, names_fs):
    199       # If an API is not represented in any of the _features files, but exists
    200       # in the filesystem, then assume it is available in this version.
    201       # The windows API is an example of this.
    202       return self._branch_utility.GetChannelForVersion(version)
    203 
    204     return available_channel
    205 
    206   def GetApiAvailability(self, api_name):
    207     '''Determines the availability for an API by testing several scenarios.
    208     (i.e. Is the API experimental? Only available on certain development
    209     channels? If it's stable, when did it first become stable? etc.)
    210     '''
    211     availability = self._object_store.Get(api_name).Get()
    212     if availability is not None:
    213       return availability
    214 
    215     # Check for a predetermined availability for this API.
    216     api_info = self._json_cache.GetFromFile(_API_AVAILABILITIES).get(api_name)
    217     if api_info is not None:
    218       channel = api_info.get('channel')
    219       if channel == _STABLE:
    220         version = api_info.get('version')
    221       else:
    222         version = self._branch_utility.GetChannelInfo(channel).version
    223       # The file data for predetermined availabilities is already cached, so
    224       # skip caching this result.
    225       return AvailabilityInfo(channel, version)
    226 
    227     # Check for the API in the development channels.
    228     availability = None
    229     for channel_info in self._branch_utility.GetAllChannelInfo():
    230       available_channel = self._GetAvailableChannelForVersion(
    231           api_name,
    232           channel_info.version)
    233       # If the |available_channel| for the API is the same as, or older than,
    234       # the channel we're checking, then the API is available on this channel.
    235       if (available_channel is not None and
    236           BranchUtility.NewestChannel((available_channel, channel_info.channel))
    237               == channel_info.channel):
    238         availability = AvailabilityInfo(channel_info.channel,
    239                                         channel_info.version)
    240         break
    241 
    242     # The API should at least be available on trunk. It's a bug otherwise.
    243     assert availability, 'No availability found for %s' % api_name
    244 
    245     # If the API is in stable, find the chrome version in which it became
    246     # stable.
    247     if availability.channel == _STABLE:
    248       availability.version = self._FindEarliestStableAvailability(
    249           api_name,
    250           availability.version)
    251 
    252     self._object_store.Set(api_name, availability)
    253     return availability
    254