Chromium Code Reviews| 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 # TestBranchUtility sends funky fake data as a str to this method, but | |
| 151 # otherwise a numerical version will be passed in. | |
|
not at google - send to devlin
2013/06/20 16:15:20
I see. Yeah special test-divergent logic is bad. I
epeterson
2013/06/20 16:41:34
Thanks for the review. I'll get right to work on f
epeterson
2013/06/21 00:48:26
test-divergent logic is gone.
| |
| 152 if isinstance(version, str): | |
| 153 return version | |
| 154 | |
| 155 available = True | |
| 156 while available: | |
| 157 if version < 5: | |
|
not at google - send to devlin
2013/06/20 16:15:20
"We don't have any data below version 5"?
epeterson
2013/06/21 00:48:26
Done.
| |
| 158 return version + 1 | |
| 159 available = False | |
| 160 features_fs, names_fs = self._CreateFeaturesAndNamesFileSystems(version) | |
| 161 if version >= 28: | |
| 162 # The _api_features.json file first appears in version 28 and should be | |
| 163 # the most reliable for finding API availabilities, so it gets checked | |
| 164 # first. The _permission_features.json and _manifest_features.json files | |
| 165 # are present in Chrome 20 and onwards. Fall back to a check for file | |
| 166 # system existence if the API is not stable in any of the _features.json | |
| 167 # files. | |
| 168 available = _GetChannelFromApiFeatures(api_name, features_fs) == _STABLE | |
| 169 if version >= 20: | |
| 170 # Check other _features.json files/file existence if the API wasn't | |
| 171 # found in _api_features.json, or if _api_features.json wasn't present. | |
| 172 available = available or ( | |
| 173 _GetChannelFromPermissionFeatures(api_name, features_fs) == _STABLE | |
| 174 or _GetChannelFromManifestFeatures(api_name, features_fs) == _STABLE | |
| 175 or _ExistsInFileSystem(api_name, names_fs)) | |
| 176 elif version >= 18: | |
| 177 # These versions are a little troublesome. Version 19 has | |
| 178 # _permission_features.json, but it lacks 'channel' information. | |
| 179 # Version 18 lacks all of the _features.json files. For now, we're using | |
| 180 # a simple check for filesystem existence here. | |
| 181 available = _ExistsInFileSystem(api_name, names_fs) | |
| 182 elif version >= 5: | |
| 183 # Versions 17 and down to 5 have an extension_api.json file which | |
| 184 # contains namespaces for each API that was available at the time. We | |
| 185 # can use this file to check for API existence. | |
| 186 available = _ExistsInExtensionApi(api_name, features_fs) | |
| 187 | |
| 188 if not available: | |
| 189 return version + 1 | |
| 190 version -= 1 | |
| 191 | |
| 192 def _GetAvailableChannelForVersion(self, api_name, version): | |
| 193 '''Searches through the _features files for a given |version| and returns | |
| 194 the channel that the given API is determined to be available on. | |
| 195 ''' | |
| 196 features_fs, names_fs = self._CreateFeaturesAndNamesFileSystems(version) | |
| 197 channel = (_GetChannelFromApiFeatures(api_name, features_fs) | |
| 198 or _GetChannelFromPermissionFeatures(api_name, features_fs) | |
| 199 or _GetChannelFromManifestFeatures(api_name, features_fs)) | |
| 200 if channel is None and _ExistsInFileSystem(api_name, names_fs): | |
| 201 # If an API is not represented in any of the _features files, but exists | |
| 202 # in the filesystem, then assume it is available in this version. | |
| 203 # The windows API is an example of this. | |
| 204 for channel_info in self._branch_utility.GetAllChannelInfo(): | |
| 205 if version == channel_info.version: | |
| 206 channel = channel_info.channel | |
| 207 break | |
|
not at google - send to devlin
2013/06/20 16:15:20
maybe add this thing to branch_utility? channel =
epeterson
2013/06/21 00:48:26
Done.
| |
| 208 return channel | |
| 209 | |
| 210 def GetApiAvailability(self, api_name): | |
| 211 '''Determines the availability for an API by testing several scenarios. | |
| 212 (i.e. Is the API experimental? Only available on certain development | |
| 213 channels? If it's stable, when did it first become stable? etc.) | |
| 214 ''' | |
| 215 availability = self._object_store.Get(api_name).Get() | |
| 216 if availability is not None: | |
| 217 return availability | |
| 218 | |
| 219 # Check for a predetermined availability for this API. | |
| 220 api_info = self._json_cache.GetFromFile(_API_AVAILABILITIES).get(api_name) | |
| 221 if api_info is not None: | |
| 222 return AvailabilityInfo( | |
| 223 api_info.get('channel'), | |
| 224 api_info.get('version') | |
| 225 if api_info.get('channel') == _STABLE else None) | |
|
not at google - send to devlin
2013/06/20 16:15:20
maybe save a reference to channel so that this can
epeterson
2013/06/21 00:48:26
Done.
| |
| 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 BranchUtility.NewestChannel( | |
|
not at google - send to devlin
2013/06/20 16:15:20
would prefer a line break after the 'and'. Easier
epeterson
2013/06/21 00:48:26
Done.
| |
| 236 (available_channel, | |
| 237 channel_info.channel)) == 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 | |
| OLD | NEW |