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