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