availability_finder.py revision effb81e5f8246d0db0270817048dc992db66e9fb
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 5from collections import Mapping 6import posixpath 7 8from api_schema_graph import APISchemaGraph 9from branch_utility import BranchUtility 10from extensions_paths import API_PATHS, JSON_TEMPLATES 11from features_bundle import FeaturesBundle 12import features_utility 13from file_system import FileNotFoundError 14from third_party.json_schema_compiler.memoize import memoize 15from third_party.json_schema_compiler.model import UnixName 16 17 18_EXTENSION_API = 'extension_api.json' 19 20# The version where api_features.json is first available. 21_API_FEATURES_MIN_VERSION = 28 22# The version where permission_ and manifest_features.json are available and 23# presented in the current format. 24_ORIGINAL_FEATURES_MIN_VERSION = 20 25# API schemas are aggregated in extension_api.json up to this version. 26_EXTENSION_API_MAX_VERSION = 17 27# The earliest version for which we have SVN data. 28_SVN_MIN_VERSION = 5 29 30 31def _GetChannelFromFeatures(api_name, features): 32 '''Finds API channel information for |api_name| from |features|. 33 Returns None if channel information for the API cannot be located. 34 ''' 35 feature = features.Get().get(api_name) 36 return feature.get('channel') if feature else None 37 38 39class AvailabilityFinder(object): 40 '''Generates availability information for APIs by looking at API schemas and 41 _features files over multiple release versions of Chrome. 42 ''' 43 44 def __init__(self, 45 branch_utility, 46 compiled_fs_factory, 47 file_system_iterator, 48 host_file_system, 49 object_store_creator): 50 self._branch_utility = branch_utility 51 self._compiled_fs_factory = compiled_fs_factory 52 self._file_system_iterator = file_system_iterator 53 self._host_file_system = host_file_system 54 self._object_store_creator = object_store_creator 55 def create_object_store(category): 56 return object_store_creator.Create(AvailabilityFinder, category=category) 57 self._top_level_object_store = create_object_store('top_level') 58 self._node_level_object_store = create_object_store('node_level') 59 self._json_fs = compiled_fs_factory.ForJson(self._host_file_system) 60 61 def _GetPredeterminedAvailability(self, api_name): 62 '''Checks a configuration file for hardcoded (i.e. predetermined) 63 availability information for an API. 64 ''' 65 api_info = self._json_fs.GetFromFile( 66 JSON_TEMPLATES + 'api_availabilities.json').Get().get(api_name) 67 if api_info is None: 68 return None 69 if api_info['channel'] == 'stable': 70 return self._branch_utility.GetStableChannelInfo(api_info['version']) 71 else: 72 return self._branch_utility.GetChannelInfo(api_info['channel']) 73 74 def _GetApiSchemaFilename(self, api_name, file_system, version): 75 '''Gets the name of the file which may contain the schema for |api_name| in 76 |file_system|, or None if the API is not found. Note that this may be the 77 single _EXTENSION_API file which all APIs share in older versions of Chrome, 78 in which case it is unknown whether the API actually exists there. 79 ''' 80 if version == 'trunk' or version > _ORIGINAL_FEATURES_MIN_VERSION: 81 # API schema filenames switch format to unix_hacker_style. 82 api_name = UnixName(api_name) 83 84 futures = [(path, file_system.ReadSingle(path)) 85 for path in API_PATHS] 86 for path, future in futures: 87 try: 88 filenames = future.Get() 89 for ext in ('json', 'idl'): 90 filename = '%s.%s' % (api_name, ext) 91 if filename in filenames: 92 return path + filename 93 if _EXTENSION_API in filenames: 94 return path + _EXTENSION_API 95 except FileNotFoundError: 96 pass 97 return None 98 99 def _GetApiSchema(self, api_name, file_system, version): 100 '''Searches |file_system| for |api_name|'s API schema data, and processes 101 and returns it if found. 102 ''' 103 api_filename = self._GetApiSchemaFilename(api_name, file_system, version) 104 if api_filename is None: 105 # No file for the API could be found in the given |file_system|. 106 return None 107 108 schema_fs = self._compiled_fs_factory.ForApiSchema(file_system) 109 api_schemas = schema_fs.GetFromFile(api_filename).Get() 110 matching_schemas = [api for api in api_schemas 111 if api['namespace'] == api_name] 112 # There should only be a single matching schema per file, or zero in the 113 # case of no API data being found in _EXTENSION_API. 114 assert len(matching_schemas) <= 1 115 return matching_schemas or None 116 117 def _HasApiSchema(self, api_name, file_system, version): 118 '''Whether or not an API schema for |api_name|exists in the given 119 |file_system|. 120 ''' 121 filename = self._GetApiSchemaFilename(api_name, file_system, version) 122 if filename is None: 123 return False 124 if filename.endswith(_EXTENSION_API): 125 return self._GetApiSchema(api_name, file_system, version) is not None 126 return True 127 128 def _CheckStableAvailability(self, api_name, file_system, version): 129 '''Checks for availability of an API, |api_name|, on the stable channel. 130 Considers several _features.json files, file system existence, and 131 extension_api.json depending on the given |version|. 132 ''' 133 if version < _SVN_MIN_VERSION: 134 # SVN data isn't available below this version. 135 return False 136 features_bundle = self._CreateFeaturesBundle(file_system) 137 available_channel = None 138 if version >= _API_FEATURES_MIN_VERSION: 139 # The _api_features.json file first appears in version 28 and should be 140 # the most reliable for finding API availability. 141 available_channel = self._GetChannelFromApiFeatures(api_name, 142 features_bundle) 143 if version >= _ORIGINAL_FEATURES_MIN_VERSION: 144 # The _permission_features.json and _manifest_features.json files are 145 # present in Chrome 20 and onwards. Use these if no information could be 146 # found using _api_features.json. 147 available_channel = ( 148 available_channel or 149 self._GetChannelFromPermissionFeatures(api_name, features_bundle) or 150 self._GetChannelFromManifestFeatures(api_name, features_bundle)) 151 if available_channel is not None: 152 return available_channel == 'stable' 153 if version >= _SVN_MIN_VERSION: 154 # Fall back to a check for file system existence if the API is not 155 # stable in any of the _features.json files, or if the _features files 156 # do not exist (version 19 and earlier). 157 return self._HasApiSchema(api_name, file_system, version) 158 159 def _CheckChannelAvailability(self, api_name, file_system, channel_info): 160 '''Searches through the _features files in a given |file_system|, falling 161 back to checking the file system for API schema existence, to determine 162 whether or not an API is available on the given channel, |channel_info|. 163 ''' 164 features_bundle = self._CreateFeaturesBundle(file_system) 165 available_channel = ( 166 self._GetChannelFromApiFeatures(api_name, features_bundle) or 167 self._GetChannelFromPermissionFeatures(api_name, features_bundle) or 168 self._GetChannelFromManifestFeatures(api_name, features_bundle)) 169 if (available_channel is None and 170 self._HasApiSchema(api_name, file_system, channel_info.version)): 171 # If an API is not represented in any of the _features files, but exists 172 # in the filesystem, then assume it is available in this version. 173 # The chrome.windows API is an example of this. 174 available_channel = channel_info.channel 175 # If the channel we're checking is the same as or newer than the 176 # |available_channel| then the API is available at this channel. 177 newest = BranchUtility.NewestChannel((available_channel, 178 channel_info.channel)) 179 return available_channel is not None and newest == channel_info.channel 180 181 @memoize 182 def _CreateFeaturesBundle(self, file_system): 183 return FeaturesBundle(file_system, 184 self._compiled_fs_factory, 185 self._object_store_creator) 186 187 def _GetChannelFromApiFeatures(self, api_name, features_bundle): 188 return _GetChannelFromFeatures(api_name, features_bundle.GetAPIFeatures()) 189 190 def _GetChannelFromManifestFeatures(self, api_name, features_bundle): 191 # _manifest_features.json uses unix_style API names. 192 api_name = UnixName(api_name) 193 return _GetChannelFromFeatures(api_name, 194 features_bundle.GetManifestFeatures()) 195 196 def _GetChannelFromPermissionFeatures(self, api_name, features_bundle): 197 return _GetChannelFromFeatures(api_name, 198 features_bundle.GetPermissionFeatures()) 199 200 def _CheckApiAvailability(self, api_name, file_system, channel_info): 201 '''Determines the availability for an API at a certain version of Chrome. 202 Two branches of logic are used depending on whether or not the API is 203 determined to be 'stable' at the given version. 204 ''' 205 if channel_info.channel == 'stable': 206 return self._CheckStableAvailability(api_name, 207 file_system, 208 channel_info.version) 209 return self._CheckChannelAvailability(api_name, 210 file_system, 211 channel_info) 212 213 def GetApiAvailability(self, api_name): 214 '''Performs a search for an API's top-level availability by using a 215 HostFileSystemIterator instance to traverse multiple version of the 216 SVN filesystem. 217 ''' 218 availability = self._top_level_object_store.Get(api_name).Get() 219 if availability is not None: 220 return availability 221 222 # Check for predetermined availability and cache this information if found. 223 availability = self._GetPredeterminedAvailability(api_name) 224 if availability is not None: 225 self._top_level_object_store.Set(api_name, availability) 226 return availability 227 228 def check_api_availability(file_system, channel_info): 229 return self._CheckApiAvailability(api_name, file_system, channel_info) 230 231 availability = self._file_system_iterator.Descending( 232 self._branch_utility.GetChannelInfo('dev'), 233 check_api_availability) 234 if availability is None: 235 # The API wasn't available on 'dev', so it must be a 'trunk'-only API. 236 availability = self._branch_utility.GetChannelInfo('trunk') 237 self._top_level_object_store.Set(api_name, availability) 238 return availability 239 240 def GetApiNodeAvailability(self, api_name): 241 '''Returns an APISchemaGraph annotated with each node's availability (the 242 ChannelInfo at the oldest channel it's available in). 243 ''' 244 availability_graph = self._node_level_object_store.Get(api_name).Get() 245 if availability_graph is not None: 246 return availability_graph 247 248 def assert_not_none(value): 249 assert value is not None 250 return value 251 252 availability_graph = APISchemaGraph() 253 254 host_fs = self._host_file_system 255 trunk_stat = assert_not_none(host_fs.Stat(self._GetApiSchemaFilename( 256 api_name, host_fs, 'trunk'))) 257 258 # Weird object thing here because nonlocal is Python 3. 259 previous = type('previous', (object,), {'stat': None, 'graph': None}) 260 261 def update_availability_graph(file_system, channel_info): 262 version_filename = assert_not_none(self._GetApiSchemaFilename( 263 api_name, file_system, channel_info.version)) 264 version_stat = assert_not_none(file_system.Stat(version_filename)) 265 266 # Important optimisation: only re-parse the graph if the file changed in 267 # the last revision. Parsing the same schema and forming a graph on every 268 # iteration is really expensive. 269 if version_stat == previous.stat: 270 version_graph = previous.graph 271 else: 272 # Keep track of any new schema elements from this version by adding 273 # them to |availability_graph|. 274 # 275 # Calling |availability_graph|.Lookup() on the nodes being updated 276 # will return the |annotation| object -- the current |channel_info|. 277 version_graph = APISchemaGraph(self._GetApiSchema( 278 api_name, file_system, channel_info.version)) 279 availability_graph.Update(version_graph.Subtract(availability_graph), 280 annotation=channel_info) 281 282 previous.stat = version_stat 283 previous.graph = version_graph 284 285 # Continue looping until there are no longer differences between this 286 # version and trunk. 287 return version_stat != trunk_stat 288 289 self._file_system_iterator.Ascending(self.GetApiAvailability(api_name), 290 update_availability_graph) 291 292 self._node_level_object_store.Set(api_name, availability_graph) 293 return availability_graph 294