| OLD | NEW |
| 1 # Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 from collections import Mapping | 5 from collections import Mapping |
| 6 import posixpath | 6 import posixpath |
| 7 | 7 |
| 8 from api_schema_graph import APISchemaGraph | 8 from api_schema_graph import APISchemaGraph |
| 9 from branch_utility import BranchUtility, ChannelInfo | 9 from branch_utility import BranchUtility, ChannelInfo |
| 10 from extensions_paths import API_PATHS, JSON_TEMPLATES | 10 from extensions_paths import API_PATHS, JSON_TEMPLATES |
| (...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 89 api_info = self._json_fs.GetFromFile( | 89 api_info = self._json_fs.GetFromFile( |
| 90 JSON_TEMPLATES + 'api_availabilities.json').Get().get(api_name) | 90 JSON_TEMPLATES + 'api_availabilities.json').Get().get(api_name) |
| 91 if api_info is None: | 91 if api_info is None: |
| 92 return None | 92 return None |
| 93 if api_info['channel'] == 'stable': | 93 if api_info['channel'] == 'stable': |
| 94 return AvailabilityInfo( | 94 return AvailabilityInfo( |
| 95 self._branch_utility.GetStableChannelInfo(api_info['version'])) | 95 self._branch_utility.GetStableChannelInfo(api_info['version'])) |
| 96 return AvailabilityInfo( | 96 return AvailabilityInfo( |
| 97 self._branch_utility.GetChannelInfo(api_info['channel'])) | 97 self._branch_utility.GetChannelInfo(api_info['channel'])) |
| 98 | 98 |
| 99 def _GetApiSchemaFilename(self, api_name, file_system, version): | 99 def _GetAPISchemaFilename(self, api_name, file_system, version): |
| 100 '''Gets the name of the file which may contain the schema for |api_name| in | 100 '''Gets the name of the file which may contain the schema for |api_name| in |
| 101 |file_system|, or None if the API is not found. Note that this may be the | 101 |file_system|, or None if the API is not found. Note that this may be the |
| 102 single _EXTENSION_API file which all APIs share in older versions of Chrome, | 102 single _EXTENSION_API file which all APIs share in older versions of Chrome, |
| 103 in which case it is unknown whether the API actually exists there. | 103 in which case it is unknown whether the API actually exists there. |
| 104 ''' | 104 ''' |
| 105 if version == 'trunk' or version > _ORIGINAL_FEATURES_MIN_VERSION: | 105 if version == 'trunk' or version > _ORIGINAL_FEATURES_MIN_VERSION: |
| 106 # API schema filenames switch format to unix_hacker_style. | 106 # API schema filenames switch format to unix_hacker_style. |
| 107 api_name = UnixName(api_name) | 107 api_name = UnixName(api_name) |
| 108 | 108 |
| 109 found_files = file_system.Read(API_PATHS, skip_not_found=True) | 109 found_files = file_system.Read(API_PATHS, skip_not_found=True) |
| 110 for path, filenames in found_files.Get().iteritems(): | 110 for path, filenames in found_files.Get().iteritems(): |
| 111 try: | 111 try: |
| 112 for ext in ('json', 'idl'): | 112 for ext in ('json', 'idl'): |
| 113 filename = '%s.%s' % (api_name, ext) | 113 filename = '%s.%s' % (api_name, ext) |
| 114 if filename in filenames: | 114 if filename in filenames: |
| 115 return path + filename | 115 return path + filename |
| 116 if _EXTENSION_API in filenames: | 116 if _EXTENSION_API in filenames: |
| 117 return path + _EXTENSION_API | 117 return path + _EXTENSION_API |
| 118 except FileNotFoundError: | 118 except FileNotFoundError: |
| 119 pass | 119 pass |
| 120 return None | 120 return None |
| 121 | 121 |
| 122 def _GetApiSchema(self, api_name, file_system, version): | 122 def _GetAPISchema(self, api_name, file_system, version): |
| 123 '''Searches |file_system| for |api_name|'s API schema data, and processes | 123 '''Searches |file_system| for |api_name|'s API schema data, and processes |
| 124 and returns it if found. | 124 and returns it if found. |
| 125 ''' | 125 ''' |
| 126 api_filename = self._GetApiSchemaFilename(api_name, file_system, version) | 126 api_filename = self._GetAPISchemaFilename(api_name, file_system, version) |
| 127 if api_filename is None: | 127 if api_filename is None: |
| 128 # No file for the API could be found in the given |file_system|. | 128 # No file for the API could be found in the given |file_system|. |
| 129 return None | 129 return None |
| 130 | 130 |
| 131 schema_fs = self._compiled_fs_factory.ForApiSchema(file_system) | 131 schema_fs = self._compiled_fs_factory.ForAPISchema(file_system) |
| 132 api_schemas = schema_fs.GetFromFile(api_filename).Get() | 132 api_schemas = schema_fs.GetFromFile(api_filename).Get() |
| 133 matching_schemas = [api for api in api_schemas | 133 matching_schemas = [api for api in api_schemas |
| 134 if api['namespace'] == api_name] | 134 if api['namespace'] == api_name] |
| 135 # There should only be a single matching schema per file, or zero in the | 135 # There should only be a single matching schema per file, or zero in the |
| 136 # case of no API data being found in _EXTENSION_API. | 136 # case of no API data being found in _EXTENSION_API. |
| 137 assert len(matching_schemas) <= 1 | 137 assert len(matching_schemas) <= 1 |
| 138 return matching_schemas or None | 138 return matching_schemas or None |
| 139 | 139 |
| 140 def _HasApiSchema(self, api_name, file_system, version): | 140 def _HasAPISchema(self, api_name, file_system, version): |
| 141 '''Whether or not an API schema for |api_name|exists in the given | 141 '''Whether or not an API schema for |api_name|exists in the given |
| 142 |file_system|. | 142 |file_system|. |
| 143 ''' | 143 ''' |
| 144 filename = self._GetApiSchemaFilename(api_name, file_system, version) | 144 filename = self._GetAPISchemaFilename(api_name, file_system, version) |
| 145 if filename is None: | 145 if filename is None: |
| 146 return False | 146 return False |
| 147 if filename.endswith(_EXTENSION_API): | 147 if filename.endswith(_EXTENSION_API): |
| 148 return self._GetApiSchema(api_name, file_system, version) is not None | 148 return self._GetAPISchema(api_name, file_system, version) is not None |
| 149 return True | 149 return True |
| 150 | 150 |
| 151 def _CheckStableAvailability(self, api_name, file_system, version): | 151 def _CheckStableAvailability(self, api_name, file_system, version): |
| 152 '''Checks for availability of an API, |api_name|, on the stable channel. | 152 '''Checks for availability of an API, |api_name|, on the stable channel. |
| 153 Considers several _features.json files, file system existence, and | 153 Considers several _features.json files, file system existence, and |
| 154 extension_api.json depending on the given |version|. | 154 extension_api.json depending on the given |version|. |
| 155 ''' | 155 ''' |
| 156 if version < _SVN_MIN_VERSION: | 156 if version < _SVN_MIN_VERSION: |
| 157 # SVN data isn't available below this version. | 157 # SVN data isn't available below this version. |
| 158 return False | 158 return False |
| 159 features_bundle = self._CreateFeaturesBundle(file_system) | 159 features_bundle = self._CreateFeaturesBundle(file_system) |
| 160 available_channel = None | 160 available_channel = None |
| 161 if version >= _API_FEATURES_MIN_VERSION: | 161 if version >= _API_FEATURES_MIN_VERSION: |
| 162 # The _api_features.json file first appears in version 28 and should be | 162 # The _api_features.json file first appears in version 28 and should be |
| 163 # the most reliable for finding API availability. | 163 # the most reliable for finding API availability. |
| 164 available_channel = self._GetChannelFromApiFeatures(api_name, | 164 available_channel = self._GetChannelFromAPIFeatures(api_name, |
| 165 features_bundle) | 165 features_bundle) |
| 166 if version >= _ORIGINAL_FEATURES_MIN_VERSION: | 166 if version >= _ORIGINAL_FEATURES_MIN_VERSION: |
| 167 # The _permission_features.json and _manifest_features.json files are | 167 # The _permission_features.json and _manifest_features.json files are |
| 168 # present in Chrome 20 and onwards. Use these if no information could be | 168 # present in Chrome 20 and onwards. Use these if no information could be |
| 169 # found using _api_features.json. | 169 # found using _api_features.json. |
| 170 available_channel = ( | 170 available_channel = ( |
| 171 available_channel or | 171 available_channel or |
| 172 self._GetChannelFromPermissionFeatures(api_name, features_bundle) or | 172 self._GetChannelFromPermissionFeatures(api_name, features_bundle) or |
| 173 self._GetChannelFromManifestFeatures(api_name, features_bundle)) | 173 self._GetChannelFromManifestFeatures(api_name, features_bundle)) |
| 174 if available_channel is not None: | 174 if available_channel is not None: |
| 175 return available_channel == 'stable' | 175 return available_channel == 'stable' |
| 176 if version >= _SVN_MIN_VERSION: | 176 if version >= _SVN_MIN_VERSION: |
| 177 # Fall back to a check for file system existence if the API is not | 177 # Fall back to a check for file system existence if the API is not |
| 178 # stable in any of the _features.json files, or if the _features files | 178 # stable in any of the _features.json files, or if the _features files |
| 179 # do not exist (version 19 and earlier). | 179 # do not exist (version 19 and earlier). |
| 180 return self._HasApiSchema(api_name, file_system, version) | 180 return self._HasAPISchema(api_name, file_system, version) |
| 181 | 181 |
| 182 def _CheckChannelAvailability(self, api_name, file_system, channel_info): | 182 def _CheckChannelAvailability(self, api_name, file_system, channel_info): |
| 183 '''Searches through the _features files in a given |file_system|, falling | 183 '''Searches through the _features files in a given |file_system|, falling |
| 184 back to checking the file system for API schema existence, to determine | 184 back to checking the file system for API schema existence, to determine |
| 185 whether or not an API is available on the given channel, |channel_info|. | 185 whether or not an API is available on the given channel, |channel_info|. |
| 186 ''' | 186 ''' |
| 187 features_bundle = self._CreateFeaturesBundle(file_system) | 187 features_bundle = self._CreateFeaturesBundle(file_system) |
| 188 available_channel = ( | 188 available_channel = ( |
| 189 self._GetChannelFromApiFeatures(api_name, features_bundle) or | 189 self._GetChannelFromAPIFeatures(api_name, features_bundle) or |
| 190 self._GetChannelFromPermissionFeatures(api_name, features_bundle) or | 190 self._GetChannelFromPermissionFeatures(api_name, features_bundle) or |
| 191 self._GetChannelFromManifestFeatures(api_name, features_bundle)) | 191 self._GetChannelFromManifestFeatures(api_name, features_bundle)) |
| 192 if (available_channel is None and | 192 if (available_channel is None and |
| 193 self._HasApiSchema(api_name, file_system, channel_info.version)): | 193 self._HasAPISchema(api_name, file_system, channel_info.version)): |
| 194 # If an API is not represented in any of the _features files, but exists | 194 # If an API is not represented in any of the _features files, but exists |
| 195 # in the filesystem, then assume it is available in this version. | 195 # in the filesystem, then assume it is available in this version. |
| 196 # The chrome.windows API is an example of this. | 196 # The chrome.windows API is an example of this. |
| 197 available_channel = channel_info.channel | 197 available_channel = channel_info.channel |
| 198 # If the channel we're checking is the same as or newer than the | 198 # If the channel we're checking is the same as or newer than the |
| 199 # |available_channel| then the API is available at this channel. | 199 # |available_channel| then the API is available at this channel. |
| 200 newest = BranchUtility.NewestChannel((available_channel, | 200 newest = BranchUtility.NewestChannel((available_channel, |
| 201 channel_info.channel)) | 201 channel_info.channel)) |
| 202 return available_channel is not None and newest == channel_info.channel | 202 return available_channel is not None and newest == channel_info.channel |
| 203 | 203 |
| 204 @memoize | 204 @memoize |
| 205 def _CreateFeaturesBundle(self, file_system): | 205 def _CreateFeaturesBundle(self, file_system): |
| 206 return FeaturesBundle(file_system, | 206 return FeaturesBundle(file_system, |
| 207 self._compiled_fs_factory, | 207 self._compiled_fs_factory, |
| 208 self._object_store_creator) | 208 self._object_store_creator) |
| 209 | 209 |
| 210 def _GetChannelFromApiFeatures(self, api_name, features_bundle): | 210 def _GetChannelFromAPIFeatures(self, api_name, features_bundle): |
| 211 return _GetChannelFromFeatures(api_name, features_bundle.GetAPIFeatures()) | 211 return _GetChannelFromFeatures(api_name, features_bundle.GetAPIFeatures()) |
| 212 | 212 |
| 213 def _GetChannelFromManifestFeatures(self, api_name, features_bundle): | 213 def _GetChannelFromManifestFeatures(self, api_name, features_bundle): |
| 214 # _manifest_features.json uses unix_style API names. | 214 # _manifest_features.json uses unix_style API names. |
| 215 api_name = UnixName(api_name) | 215 api_name = UnixName(api_name) |
| 216 return _GetChannelFromFeatures(api_name, | 216 return _GetChannelFromFeatures(api_name, |
| 217 features_bundle.GetManifestFeatures()) | 217 features_bundle.GetManifestFeatures()) |
| 218 | 218 |
| 219 def _GetChannelFromPermissionFeatures(self, api_name, features_bundle): | 219 def _GetChannelFromPermissionFeatures(self, api_name, features_bundle): |
| 220 return _GetChannelFromFeatures(api_name, | 220 return _GetChannelFromFeatures(api_name, |
| 221 features_bundle.GetPermissionFeatures()) | 221 features_bundle.GetPermissionFeatures()) |
| 222 | 222 |
| 223 def _CheckApiAvailability(self, api_name, file_system, channel_info): | 223 def _CheckAPIAvailability(self, api_name, file_system, channel_info): |
| 224 '''Determines the availability for an API at a certain version of Chrome. | 224 '''Determines the availability for an API at a certain version of Chrome. |
| 225 Two branches of logic are used depending on whether or not the API is | 225 Two branches of logic are used depending on whether or not the API is |
| 226 determined to be 'stable' at the given version. | 226 determined to be 'stable' at the given version. |
| 227 ''' | 227 ''' |
| 228 if channel_info.channel == 'stable': | 228 if channel_info.channel == 'stable': |
| 229 return self._CheckStableAvailability(api_name, | 229 return self._CheckStableAvailability(api_name, |
| 230 file_system, | 230 file_system, |
| 231 channel_info.version) | 231 channel_info.version) |
| 232 return self._CheckChannelAvailability(api_name, | 232 return self._CheckChannelAvailability(api_name, |
| 233 file_system, | 233 file_system, |
| 234 channel_info) | 234 channel_info) |
| 235 | 235 |
| 236 def _FindScheduled(self, api_name): | 236 def _FindScheduled(self, api_name): |
| 237 '''Determines the earliest version of Chrome where the API is stable. | 237 '''Determines the earliest version of Chrome where the API is stable. |
| 238 Unlike the code in GetApiAvailability, this checks if the API is stable | 238 Unlike the code in GetAPIAvailability, this checks if the API is stable |
| 239 even when Chrome is in dev or beta, which shows that the API is scheduled | 239 even when Chrome is in dev or beta, which shows that the API is scheduled |
| 240 to be stable in that verison of Chrome. | 240 to be stable in that verison of Chrome. |
| 241 ''' | 241 ''' |
| 242 def check_scheduled(file_system, channel_info): | 242 def check_scheduled(file_system, channel_info): |
| 243 return self._CheckStableAvailability( | 243 return self._CheckStableAvailability( |
| 244 api_name, file_system, channel_info.version) | 244 api_name, file_system, channel_info.version) |
| 245 | 245 |
| 246 stable_channel = self._file_system_iterator.Descending( | 246 stable_channel = self._file_system_iterator.Descending( |
| 247 self._branch_utility.GetChannelInfo('dev'), check_scheduled) | 247 self._branch_utility.GetChannelInfo('dev'), check_scheduled) |
| 248 | 248 |
| 249 return stable_channel.version if stable_channel else None | 249 return stable_channel.version if stable_channel else None |
| 250 | 250 |
| 251 def GetApiAvailability(self, api_name): | 251 def GetAPIAvailability(self, api_name): |
| 252 '''Performs a search for an API's top-level availability by using a | 252 '''Performs a search for an API's top-level availability by using a |
| 253 HostFileSystemIterator instance to traverse multiple version of the | 253 HostFileSystemIterator instance to traverse multiple version of the |
| 254 SVN filesystem. | 254 SVN filesystem. |
| 255 ''' | 255 ''' |
| 256 availability = self._top_level_object_store.Get(api_name).Get() | 256 availability = self._top_level_object_store.Get(api_name).Get() |
| 257 if availability is not None: | 257 if availability is not None: |
| 258 return availability | 258 return availability |
| 259 | 259 |
| 260 # Check for predetermined availability and cache this information if found. | 260 # Check for predetermined availability and cache this information if found. |
| 261 availability = self._GetPredeterminedAvailability(api_name) | 261 availability = self._GetPredeterminedAvailability(api_name) |
| 262 if availability is not None: | 262 if availability is not None: |
| 263 self._top_level_object_store.Set(api_name, availability) | 263 self._top_level_object_store.Set(api_name, availability) |
| 264 return availability | 264 return availability |
| 265 | 265 |
| 266 def check_api_availability(file_system, channel_info): | 266 def check_api_availability(file_system, channel_info): |
| 267 return self._CheckApiAvailability(api_name, file_system, channel_info) | 267 return self._CheckAPIAvailability(api_name, file_system, channel_info) |
| 268 | 268 |
| 269 channel_info = self._file_system_iterator.Descending( | 269 channel_info = self._file_system_iterator.Descending( |
| 270 self._branch_utility.GetChannelInfo('dev'), | 270 self._branch_utility.GetChannelInfo('dev'), |
| 271 check_api_availability) | 271 check_api_availability) |
| 272 if channel_info is None: | 272 if channel_info is None: |
| 273 # The API wasn't available on 'dev', so it must be a 'trunk'-only API. | 273 # The API wasn't available on 'dev', so it must be a 'trunk'-only API. |
| 274 channel_info = self._branch_utility.GetChannelInfo('trunk') | 274 channel_info = self._branch_utility.GetChannelInfo('trunk') |
| 275 | 275 |
| 276 # If the API is not stable, check when it will be scheduled to be stable. | 276 # If the API is not stable, check when it will be scheduled to be stable. |
| 277 if channel_info.channel == 'stable': | 277 if channel_info.channel == 'stable': |
| 278 scheduled = None | 278 scheduled = None |
| 279 else: | 279 else: |
| 280 scheduled = self._FindScheduled(api_name) | 280 scheduled = self._FindScheduled(api_name) |
| 281 | 281 |
| 282 availability = AvailabilityInfo(channel_info, scheduled=scheduled) | 282 availability = AvailabilityInfo(channel_info, scheduled=scheduled) |
| 283 | 283 |
| 284 self._top_level_object_store.Set(api_name, availability) | 284 self._top_level_object_store.Set(api_name, availability) |
| 285 return availability | 285 return availability |
| 286 | 286 |
| 287 def GetApiNodeAvailability(self, api_name): | 287 def GetAPINodeAvailability(self, api_name): |
| 288 '''Returns an APISchemaGraph annotated with each node's availability (the | 288 '''Returns an APISchemaGraph annotated with each node's availability (the |
| 289 ChannelInfo at the oldest channel it's available in). | 289 ChannelInfo at the oldest channel it's available in). |
| 290 ''' | 290 ''' |
| 291 availability_graph = self._node_level_object_store.Get(api_name).Get() | 291 availability_graph = self._node_level_object_store.Get(api_name).Get() |
| 292 if availability_graph is not None: | 292 if availability_graph is not None: |
| 293 return availability_graph | 293 return availability_graph |
| 294 | 294 |
| 295 def assert_not_none(value): | 295 def assert_not_none(value): |
| 296 assert value is not None | 296 assert value is not None |
| 297 return value | 297 return value |
| 298 | 298 |
| 299 availability_graph = APISchemaGraph() | 299 availability_graph = APISchemaGraph() |
| 300 | 300 |
| 301 host_fs = self._host_file_system | 301 host_fs = self._host_file_system |
| 302 trunk_stat = assert_not_none(host_fs.Stat(self._GetApiSchemaFilename( | 302 trunk_stat = assert_not_none(host_fs.Stat(self._GetAPISchemaFilename( |
| 303 api_name, host_fs, 'trunk'))) | 303 api_name, host_fs, 'trunk'))) |
| 304 | 304 |
| 305 # Weird object thing here because nonlocal is Python 3. | 305 # Weird object thing here because nonlocal is Python 3. |
| 306 previous = type('previous', (object,), {'stat': None, 'graph': None}) | 306 previous = type('previous', (object,), {'stat': None, 'graph': None}) |
| 307 | 307 |
| 308 def update_availability_graph(file_system, channel_info): | 308 def update_availability_graph(file_system, channel_info): |
| 309 version_filename = assert_not_none(self._GetApiSchemaFilename( | 309 version_filename = assert_not_none(self._GetAPISchemaFilename( |
| 310 api_name, file_system, channel_info.version)) | 310 api_name, file_system, channel_info.version)) |
| 311 version_stat = assert_not_none(file_system.Stat(version_filename)) | 311 version_stat = assert_not_none(file_system.Stat(version_filename)) |
| 312 | 312 |
| 313 # Important optimisation: only re-parse the graph if the file changed in | 313 # Important optimisation: only re-parse the graph if the file changed in |
| 314 # the last revision. Parsing the same schema and forming a graph on every | 314 # the last revision. Parsing the same schema and forming a graph on every |
| 315 # iteration is really expensive. | 315 # iteration is really expensive. |
| 316 if version_stat == previous.stat: | 316 if version_stat == previous.stat: |
| 317 version_graph = previous.graph | 317 version_graph = previous.graph |
| 318 else: | 318 else: |
| 319 # Keep track of any new schema elements from this version by adding | 319 # Keep track of any new schema elements from this version by adding |
| 320 # them to |availability_graph|. | 320 # them to |availability_graph|. |
| 321 # | 321 # |
| 322 # Calling |availability_graph|.Lookup() on the nodes being updated | 322 # Calling |availability_graph|.Lookup() on the nodes being updated |
| 323 # will return the |annotation| object -- the current |channel_info|. | 323 # will return the |annotation| object -- the current |channel_info|. |
| 324 version_graph = APISchemaGraph(self._GetApiSchema( | 324 version_graph = APISchemaGraph(self._GetAPISchema( |
| 325 api_name, file_system, channel_info.version)) | 325 api_name, file_system, channel_info.version)) |
| 326 availability_graph.Update(version_graph.Subtract(availability_graph), | 326 availability_graph.Update(version_graph.Subtract(availability_graph), |
| 327 annotation=channel_info) | 327 annotation=channel_info) |
| 328 | 328 |
| 329 previous.stat = version_stat | 329 previous.stat = version_stat |
| 330 previous.graph = version_graph | 330 previous.graph = version_graph |
| 331 | 331 |
| 332 # Continue looping until there are no longer differences between this | 332 # Continue looping until there are no longer differences between this |
| 333 # version and trunk. | 333 # version and trunk. |
| 334 return version_stat != trunk_stat | 334 return version_stat != trunk_stat |
| 335 | 335 |
| 336 self._file_system_iterator.Ascending( | 336 self._file_system_iterator.Ascending( |
| 337 self.GetApiAvailability(api_name).channel_info, | 337 self.GetAPIAvailability(api_name).channel_info, |
| 338 update_availability_graph) | 338 update_availability_graph) |
| 339 | 339 |
| 340 self._node_level_object_store.Set(api_name, availability_graph) | 340 self._node_level_object_store.Set(api_name, availability_graph) |
| 341 return availability_graph | 341 return availability_graph |
| OLD | NEW |