availability_finder.py revision 7dbb3d5cf0c15f500944d211057644d6a2f37371
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 5import collections 6import os 7 8from branch_utility import BranchUtility 9from compiled_file_system import CompiledFileSystem 10from file_system import FileNotFoundError 11import svn_constants 12from third_party.json_schema_compiler import json_parse, model 13from 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 22class AvailabilityInfo(object): 23 def __init__(self, channel, version): 24 self.channel = channel 25 self.version = version 26 27def _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 44def _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 51def _GetChannelFromPermissionFeatures(api_name, file_system): 52 return _GetChannelFromFeatures(api_name, file_system, _PERMISSION_FEATURES) 53 54def _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 60def _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 68def _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 83class 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 host_file_system_creator): 94 self._object_store_creator = object_store_creator 95 self._compiled_host_fs_factory = compiled_host_fs_factory 96 self._branch_utility = branch_utility 97 self._host_file_system_creator = host_file_system_creator 98 99 def Create(self): 100 return AvailabilityFinder(self._object_store_creator, 101 self._compiled_host_fs_factory, 102 self._branch_utility, 103 self._host_file_system_creator) 104 105 def __init__(self, 106 object_store_creator, 107 compiled_host_fs_factory, 108 branch_utility, 109 host_file_system_creator): 110 self._object_store_creator = object_store_creator 111 self._json_cache = compiled_host_fs_factory.Create( 112 lambda _, json: json_parse.Parse(json), 113 AvailabilityFinder, 114 'json-cache') 115 self._branch_utility = branch_utility 116 self._host_file_system_creator = host_file_system_creator 117 self._object_store = object_store_creator.Create(AvailabilityFinder) 118 119 @memoize 120 def _CreateFeaturesAndNamesFileSystems(self, version): 121 '''The 'features' compiled file system's populate function parses and 122 returns the contents of a _features.json file. The 'names' compiled file 123 system's populate function creates a list of file names with .json or .idl 124 extensions. 125 ''' 126 fs_factory = CompiledFileSystem.Factory( 127 self._host_file_system_creator.Create( 128 self._branch_utility.GetBranchForVersion(version)), 129 self._object_store_creator) 130 features_fs = fs_factory.Create(lambda _, json: json_parse.Parse(json), 131 AvailabilityFinder, 132 category='features') 133 names_fs = fs_factory.Create(self._GetExtNames, 134 AvailabilityFinder, 135 category='names') 136 return (features_fs, names_fs) 137 138 def _GetExtNames(self, base_path, apis): 139 return [os.path.splitext(api)[0] for api in apis 140 if os.path.splitext(api)[1][1:] in ['json', 'idl']] 141 142 def _FindEarliestStableAvailability(self, api_name, version): 143 '''Searches in descending order through filesystem caches tied to specific 144 chrome version numbers and looks for the availability of an API, |api_name|, 145 on the stable channel. When a version is found where the API is no longer 146 available on stable, returns the previous version number (the last known 147 version where the API was stable). 148 ''' 149 available = True 150 while available: 151 if version < 5: 152 # SVN data isn't available below version 5. 153 return version + 1 154 available = False 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 = _GetChannelFromApiFeatures(api_name, features_fs) == _STABLE 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 = available or ( 168 _GetChannelFromPermissionFeatures(api_name, features_fs) == _STABLE 169 or _GetChannelFromManifestFeatures(api_name, features_fs) == _STABLE 170 or _ExistsInFileSystem(api_name, names_fs)) 171 elif version >= 18: 172 # These versions are a little troublesome. Version 19 has 173 # _permission_features.json, but it lacks 'channel' information. 174 # Version 18 lacks all of the _features.json files. For now, we're using 175 # a simple check for filesystem existence here. 176 available = _ExistsInFileSystem(api_name, names_fs) 177 elif version >= 5: 178 # Versions 17 and down to 5 have an extension_api.json file which 179 # contains namespaces for each API that was available at the time. We 180 # can use this file to check for API existence. 181 available = _ExistsInExtensionApi(api_name, features_fs) 182 183 if not available: 184 return version + 1 185 version -= 1 186 187 def _GetAvailableChannelForVersion(self, api_name, version): 188 '''Searches through the _features files for a given |version| and returns 189 the channel that the given API is determined to be available on. 190 ''' 191 features_fs, names_fs = self._CreateFeaturesAndNamesFileSystems(version) 192 channel = (_GetChannelFromApiFeatures(api_name, features_fs) 193 or _GetChannelFromPermissionFeatures(api_name, features_fs) 194 or _GetChannelFromManifestFeatures(api_name, features_fs)) 195 if channel is None and _ExistsInFileSystem(api_name, names_fs): 196 # If an API is not represented in any of the _features files, but exists 197 # in the filesystem, then assume it is available in this version. 198 # The windows API is an example of this. 199 return self._branch_utility.GetChannelForVersion(version) 200 201 return channel 202 203 def GetApiAvailability(self, api_name): 204 '''Determines the availability for an API by testing several scenarios. 205 (i.e. Is the API experimental? Only available on certain development 206 channels? If it's stable, when did it first become stable? etc.) 207 ''' 208 availability = self._object_store.Get(api_name).Get() 209 if availability is not None: 210 return availability 211 212 # Check for a predetermined availability for this API. 213 api_info = self._json_cache.GetFromFile(_API_AVAILABILITIES).get(api_name) 214 if api_info is not None: 215 channel = api_info.get('channel') 216 return AvailabilityInfo( 217 channel, 218 api_info.get('version') if channel == _STABLE else None) 219 220 # Check for the API in the development channels. 221 availability = None 222 for channel_info in self._branch_utility.GetAllChannelInfo(): 223 available_channel = self._GetAvailableChannelForVersion( 224 api_name, 225 channel_info.version) 226 # If the |available_channel| for the API is the same as, or older than, 227 # the channel we're checking, then the API is available on this channel. 228 if (available_channel is not None and 229 BranchUtility.NewestChannel((available_channel, channel_info.channel)) 230 == channel_info.channel): 231 availability = AvailabilityInfo(channel_info.channel, 232 channel_info.version) 233 break 234 235 # The API should at least be available on trunk. It's a bug otherwise. 236 assert availability, 'No availability found for %s' % api_name 237 238 # If the API is in stable, find the chrome version in which it became 239 # stable. 240 if availability.channel == _STABLE: 241 availability.version = self._FindEarliestStableAvailability( 242 api_name, 243 availability.version) 244 245 self._object_store.Set(api_name, availability) 246 return availability 247