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