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 |