Chromium Code Reviews| 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 import posixpath | 5 from copy import copy |
| 6 import logging | |
| 6 | 7 |
| 8 from branch_utility import BranchUtility | |
| 7 from compiled_file_system import SingleFile, Unicode | 9 from compiled_file_system import SingleFile, Unicode |
| 10 from docs_server_utils import StringIdentity | |
| 8 from extensions_paths import API_PATHS, JSON_TEMPLATES | 11 from extensions_paths import API_PATHS, JSON_TEMPLATES |
| 9 import features_utility | |
| 10 from file_system import FileNotFoundError | 12 from file_system import FileNotFoundError |
| 11 from future import Future | 13 from future import Future |
| 14 from path_util import Join | |
| 15 from platform_util import GetExtensionTypes, PlatformToExtensionType | |
| 12 from third_party.json_schema_compiler.json_parse import Parse | 16 from third_party.json_schema_compiler.json_parse import Parse |
| 13 | 17 |
| 14 | 18 |
| 15 _API_FEATURES = '_api_features.json' | 19 _API_FEATURES = '_api_features.json' |
| 16 _MANIFEST_FEATURES = '_manifest_features.json' | 20 _MANIFEST_FEATURES = '_manifest_features.json' |
| 17 _PERMISSION_FEATURES = '_permission_features.json' | 21 _PERMISSION_FEATURES = '_permission_features.json' |
| 18 | 22 |
| 19 | 23 |
| 20 def _GetFeaturePaths(feature_file, *extra_paths): | 24 def HasParentFeature(feature_name, feature, all_feature_names): |
| 21 paths = [posixpath.join(api_path, feature_file) for api_path in API_PATHS] | 25 # A feature has a parent if it has a . in its name, its parent exists, |
| 22 paths.extend(extra_paths) | 26 # and it does not explicitly specify that it has no parent. |
| 23 return paths | 27 return ('.' in feature_name and |
| 24 | 28 feature_name.rsplit('.', 1)[0] in all_feature_names and |
| 25 | 29 not feature.get('noparent')) |
| 26 def _AddPlatformsAndChannelsFromDependencies(feature, | 30 |
| 27 api_features, | 31 |
| 28 manifest_features, | 32 def GetParentFeature(feature_name, feature, all_feature_names): |
| 29 permission_features): | 33 '''Returns the name of the parent feature, or None if it does not have a |
| 30 features_map = { | 34 parent. |
| 31 'api': api_features, | 35 ''' |
| 32 'manifest': manifest_features, | 36 if not HasParentFeature(feature_name, feature, all_feature_names): |
| 33 'permission': permission_features, | 37 return None |
| 34 } | 38 return feature_name.rsplit('.', 1)[0] |
| 35 dependencies = feature.get('dependencies') | 39 |
| 36 if dependencies is None: | 40 |
| 37 return | 41 def _CreateFeaturesFromJSONFutures(json_futures): |
| 38 platforms = set() | 42 '''Returns a dict of features. The value of each feature is a list with |
| 39 channel = None | 43 all of its possible values. |
| 40 for dependency in dependencies: | 44 ''' |
| 41 dep_type, dep_name = dependency.split(':') | 45 def ignore_feature(name, value): |
| 42 dependency_features = features_map[dep_type] | 46 '''Returns true if this feature should be ignored. Features are ignored if |
| 43 dependency_feature = dependency_features.get(dep_name) | 47 they are only available to whitelisted apps or component extensions/apps, as |
| 44 # If the dependency can't be resolved, it is inaccessible and therefore | 48 in these cases the APIs are not available to public developers. |
| 45 # so is this feature. | 49 |
| 46 if dependency_feature is None: | 50 Private APIs are also unavailable to public developers, but logic elsewhere |
| 47 return | 51 makes sure they are not listed. So they shouldn't be ignored via this |
| 48 # Import the platforms from the dependency. The logic is a bit odd; if | 52 mechanism. |
| 49 # |feature| specifies platforms the it's considered an override. If not, | 53 ''' |
| 50 # we form the union of all dependency's platforms. | 54 if name.endswith('Private'): |
| 51 # TODO(kalman): Fix this (see http://crbug.com/322094). | 55 return False |
| 52 platforms.update(dependency_feature.get('platforms', set())) | 56 return value.get('location') == 'component' or 'whitelist' in value |
| 53 # Import the channel from the dependency. | 57 |
| 54 channel = dependency_feature.get('channel', channel) | 58 features = {} |
| 55 if platforms and not feature.get('platforms'): | 59 |
| 56 feature['platforms'] = list(platforms) | 60 for json_future in json_futures: |
| 57 if channel and not feature.get('channel'): | 61 try: |
| 58 feature['channel'] = channel | 62 features_json = Parse(json_future.Get()) |
| 63 except FileNotFoundError: | |
| 64 # Not all file system configurations have the extra files. | |
| 65 continue | |
| 66 for name, rawvalue in features_json.iteritems(): | |
| 67 if name not in features: | |
| 68 features[name] = [] | |
| 69 for value in (rawvalue if isinstance(rawvalue, list) else (rawvalue,)): | |
| 70 if not ignore_feature(name, value): | |
| 71 features[name].append(value) | |
| 72 | |
| 73 return features | |
| 74 | |
| 75 | |
| 76 def _MergeFeature(feature_name, | |
| 77 feature_values, | |
| 78 extra_feature_values, | |
| 79 platform, | |
| 80 features_type, | |
| 81 features_map): | |
| 82 '''Filters and combines the possible values for a feature into one dict. | |
| 83 | |
| 84 It uses |features_map| to resolve dependencies for each value and inherit | |
| 85 unspecified platform and channel data. |feature_values| is then filtered | |
| 86 by platform and all values with the most stable platform are merged into one | |
| 87 dict. All values in |extra_feature_values| get merged into this dict. | |
| 88 | |
| 89 Returns |resolve_successful| and |feature|. |resolve_successful| is False | |
| 90 if the feature's dependencies have not been merged yet themselves, meaning | |
| 91 that this feature can not be reliably resolved yet. |feature| is the | |
| 92 resulting feature dict, or None if the feature does not exist on the | |
| 93 platform specified. | |
| 94 ''' | |
| 95 feature = None | |
| 96 most_stable_channel = None | |
| 97 for value in feature_values: | |
| 98 # If 'extension_types' or 'channel' is unspecified, these values should | |
| 99 # be inherited from dependencies. If they are specified, these values | |
| 100 # should override anything specified by dependencies. | |
| 101 inherit_valid_platform = 'extension_types' not in value | |
| 102 if inherit_valid_platform: | |
| 103 valid_platform = None | |
| 104 else: | |
| 105 valid_platform = (value['extension_types'] == 'all' or | |
| 106 platform in value['extension_types']) | |
| 107 inherit_channel = 'channel' not in value | |
| 108 channel = value.get('channel') | |
| 109 | |
| 110 # If this feature has a parent, add the parent as a dependency. | |
|
not at google - send to devlin
2014/06/19 21:06:55
(regarding your question)
I don't know the answer
| |
| 111 dependencies = value.get('dependencies', []) | |
| 112 parent = GetParentFeature( | |
| 113 feature_name, value, features_map[features_type]['all_names']) | |
| 114 if parent is not None: | |
| 115 dependencies.append(features_type + ':' + parent) | |
| 116 | |
| 117 for dependency in dependencies: | |
| 118 dep_type, dep_name = dependency.split(':') | |
| 119 if (dep_type not in features_map or | |
| 120 dep_name in features_map[dep_type].get('unresolved', ())): | |
| 121 # The dependency itself has not been merged yet or the features map | |
| 122 # does not have the needed data. Fail to resolve. | |
| 123 return False, None | |
| 124 | |
| 125 dep = features_map[dep_type]['resolved'].get(dep_name) | |
| 126 if inherit_valid_platform and (valid_platform is None or valid_platform): | |
| 127 # If dep is None, the dependency does not exist because it has been | |
| 128 # filtered out by platform. This feature value does not explicitly | |
| 129 # specify platform data, so filter this feature value out. | |
| 130 # Only run this check if valid_platform is True or None so that it | |
| 131 # can't be reset once it is False. | |
| 132 valid_platform = dep is not None | |
| 133 if inherit_channel and dep and 'channel' in dep: | |
| 134 if channel is None or BranchUtility.NewestChannel( | |
| 135 (dep['channel'], channel)) != channel: | |
| 136 # Inherit the least stable channel from the dependencies. | |
| 137 channel = dep['channel'] | |
| 138 | |
| 139 # Default to stable on all platforms. | |
| 140 if valid_platform is None: | |
| 141 valid_platform = True | |
| 142 if valid_platform and channel is None: | |
| 143 channel = 'stable' | |
| 144 | |
| 145 if valid_platform: | |
| 146 # The feature value is valid. Merge it into the feature dict. | |
| 147 if feature is None or BranchUtility.NewestChannel( | |
| 148 (most_stable_channel, channel)) != channel: | |
| 149 # If this is the first feature value to be merged, copy the dict. | |
| 150 # If this feature value has a more stable channel than the most stable | |
| 151 # channel so far, replace the old dict so that it only merges values | |
| 152 # from the most stable channel. | |
| 153 feature = copy(value) | |
| 154 most_stable_channel = channel | |
| 155 elif channel == most_stable_channel: | |
| 156 feature.update(value) | |
| 157 | |
| 158 if feature is None: | |
| 159 # Nothing was left after filtering the values, but all dependency resolves | |
| 160 # were successful. This feature does not exist on |platform|. | |
| 161 return True, None | |
| 162 | |
| 163 # Merge in any extra values. | |
| 164 for value in extra_feature_values: | |
| 165 feature.update(value) | |
| 166 | |
| 167 # Cleanup, fill in missing fields. | |
| 168 feature.pop('extension_types', None) | |
| 169 if 'name' not in feature: | |
| 170 feature['name'] = feature_name | |
| 171 feature['channel'] = most_stable_channel | |
| 172 | |
| 173 return True, feature | |
| 59 | 174 |
| 60 | 175 |
| 61 class _FeaturesCache(object): | 176 class _FeaturesCache(object): |
| 62 def __init__(self, file_system, compiled_fs_factory, json_paths): | 177 def __init__(self, |
| 63 populate = self._CreateCache | 178 file_system, |
| 64 if len(json_paths) == 1: | 179 compiled_fs_factory, |
| 65 populate = SingleFile(populate) | 180 json_paths, |
| 66 | 181 extra_paths, |
| 67 self._cache = compiled_fs_factory.Create(file_system, populate, type(self)) | 182 platform, |
| 183 features_type): | |
| 184 self._cache = compiled_fs_factory.Create( | |
| 185 file_system, self._CreateCache, type(self), category=platform) | |
| 68 self._text_cache = compiled_fs_factory.ForUnicode(file_system) | 186 self._text_cache = compiled_fs_factory.ForUnicode(file_system) |
| 69 self._json_path = json_paths[0] | 187 self._json_paths = json_paths |
| 70 self._extra_paths = json_paths[1:] | 188 self._extra_paths = extra_paths |
| 189 self._platform = platform | |
| 190 self._features_type = features_type | |
| 71 | 191 |
| 72 @Unicode | 192 @Unicode |
| 73 def _CreateCache(self, _, features_json): | 193 def _CreateCache(self, _, features_json): |
| 194 json_path_futures = [self._text_cache.GetFromFile(path) | |
| 195 for path in self._json_paths[1:]] | |
| 74 extra_path_futures = [self._text_cache.GetFromFile(path) | 196 extra_path_futures = [self._text_cache.GetFromFile(path) |
| 75 for path in self._extra_paths] | 197 for path in self._extra_paths] |
| 76 features = features_utility.Parse(Parse(features_json)) | 198 |
| 77 for path_future in extra_path_futures: | 199 features_values = _CreateFeaturesFromJSONFutures( |
| 78 try: | 200 [Future(value=features_json)] + json_path_futures) |
| 79 extra_json = path_future.Get() | 201 |
| 80 except FileNotFoundError: | 202 extra_features_values = _CreateFeaturesFromJSONFutures(extra_path_futures) |
| 81 # Not all file system configurations have the extra files. | 203 |
| 82 continue | 204 features = { |
| 83 features = features_utility.MergedWith( | 205 'resolved': {}, |
| 84 features_utility.Parse(Parse(extra_json)), features) | 206 'unresolved': copy(features_values), |
| 207 'extra': extra_features_values, | |
| 208 'all_names': set(features_values.keys()) | |
| 209 } | |
| 210 | |
| 211 # Merges as many feature values as possible without resolving dependencies | |
| 212 # from other FeaturesCaches. Pass in a features_map with just this | |
| 213 # FeatureCache's features_type. Makes repeated passes until no new | |
| 214 # resolves are successful. | |
| 215 new_resolves = True | |
| 216 while new_resolves: | |
| 217 new_resolves = False | |
| 218 for feature_name, feature_values in features_values.iteritems(): | |
| 219 if feature_name not in features['unresolved']: | |
| 220 continue | |
| 221 resolve_successful, feature = _MergeFeature( | |
| 222 feature_name, | |
| 223 feature_values, | |
| 224 extra_features_values.get(feature_name, ()), | |
| 225 self._platform, | |
| 226 self._features_type, | |
| 227 {self._features_type: features}) | |
| 228 if resolve_successful: | |
| 229 del features['unresolved'][feature_name] | |
| 230 new_resolves = True | |
| 231 if feature is not None: | |
| 232 features['resolved'][feature_name] = feature | |
| 233 | |
| 85 return features | 234 return features |
| 86 | 235 |
| 87 def GetFeatures(self): | 236 def GetFeatures(self): |
| 88 if self._json_path is None: | 237 if not self._json_paths: |
| 89 return Future(value={}) | 238 return Future(value={}) |
| 90 return self._cache.GetFromFile(self._json_path) | 239 return self._cache.GetFromFile(self._json_paths[0]) |
| 91 | 240 |
| 92 | 241 |
| 93 class FeaturesBundle(object): | 242 class FeaturesBundle(object): |
| 94 '''Provides access to properties of API, Manifest, and Permission features. | 243 '''Provides access to properties of API, Manifest, and Permission features. |
| 95 ''' | 244 ''' |
| 96 def __init__(self, file_system, compiled_fs_factory, object_store_creator): | 245 def __init__(self, |
| 97 self._api_cache = _FeaturesCache( | 246 file_system, |
| 98 file_system, | 247 compiled_fs_factory, |
| 99 compiled_fs_factory, | 248 object_store_creator, |
| 100 _GetFeaturePaths(_API_FEATURES)) | 249 platform): |
| 101 self._manifest_cache = _FeaturesCache( | 250 def create_features_cache(features_type, feature_file, *extra_paths): |
| 102 file_system, | 251 return _FeaturesCache( |
| 103 compiled_fs_factory, | 252 file_system, |
| 104 _GetFeaturePaths(_MANIFEST_FEATURES, | 253 compiled_fs_factory, |
| 105 posixpath.join(JSON_TEMPLATES, 'manifest.json'))) | 254 [Join(path, feature_file) for path in API_PATHS], |
| 106 self._permission_cache = _FeaturesCache( | 255 extra_paths, |
| 107 file_system, | 256 self._platform, |
| 108 compiled_fs_factory, | 257 features_type) |
| 109 _GetFeaturePaths(_PERMISSION_FEATURES, | 258 |
| 110 posixpath.join(JSON_TEMPLATES, 'permissions.json'))) | 259 if platform not in GetExtensionTypes(): |
| 111 self._identity = file_system.GetIdentity() | 260 self._platform = PlatformToExtensionType(platform) |
| 261 else: | |
| 262 self._platform = platform | |
| 263 | |
| 264 self._caches = { | |
| 265 'api': create_features_cache('api', _API_FEATURES), | |
| 266 'manifest': create_features_cache( | |
| 267 'manifest', | |
| 268 _MANIFEST_FEATURES, | |
| 269 Join(JSON_TEMPLATES, 'manifest.json')), | |
| 270 'permission': create_features_cache( | |
| 271 'permission', | |
| 272 _PERMISSION_FEATURES, | |
| 273 Join(JSON_TEMPLATES, 'permissions.json')) | |
| 274 } | |
| 275 # Namespace the object store by the file system ID because this class is | |
| 276 # used by the availability finder cross-channel. | |
| 112 self._object_store = object_store_creator.Create( | 277 self._object_store = object_store_creator.Create( |
| 113 _FeaturesCache, | 278 _FeaturesCache, |
| 114 category=self._identity) | 279 category=StringIdentity(file_system.GetIdentity(), self._platform)) |
| 115 | 280 |
| 116 def GetPermissionFeatures(self): | 281 def GetPermissionFeatures(self): |
| 117 return self._permission_cache.GetFeatures() | 282 return self.GetFeatures('permission', ('permission',)) |
| 118 | 283 |
| 119 def GetManifestFeatures(self): | 284 def GetManifestFeatures(self): |
| 120 return self._manifest_cache.GetFeatures() | 285 return self.GetFeatures('manifest', ('manifest',)) |
| 121 | 286 |
| 122 def GetAPIFeatures(self): | 287 def GetAPIFeatures(self): |
| 123 api_features = self._object_store.Get('api_features').Get() | 288 return self.GetFeatures('api', ('api', 'manifest', 'permission')) |
| 124 if api_features is not None: | 289 |
| 125 return Future(value=api_features) | 290 def GetFeatures(self, features_type, dependencies): |
| 126 | 291 '''Resolves all dependencies in the categories specified by |dependencies|. |
| 127 api_features_future = self._api_cache.GetFeatures() | 292 Returns the features in the |features_type| category. |
| 128 manifest_features_future = self._manifest_cache.GetFeatures() | 293 ''' |
| 129 permission_features_future = self._permission_cache.GetFeatures() | 294 features = self._object_store.Get(features_type).Get() |
| 295 if features is not None: | |
| 296 return Future(value=features) | |
| 297 | |
| 298 futures = {} | |
| 299 for cache_type in dependencies: | |
| 300 dependency_features = self._object_store.Get(cache_type).Get() | |
| 301 if dependency_features is not None: | |
| 302 # Get cached dependencies if possible. If it has been cached, all | |
| 303 # of its features have been resolved, so the other fields are | |
| 304 # unnecessary. | |
| 305 futures[cache_type] = Future(value={'resolved': dependency_features}) | |
| 306 else: | |
| 307 futures[cache_type] = self._caches[cache_type].GetFeatures() | |
| 308 | |
| 130 def resolve(): | 309 def resolve(): |
| 131 api_features = api_features_future.Get() | 310 features_map = {} |
| 132 manifest_features = manifest_features_future.Get() | 311 for cache_type, future in futures.iteritems(): |
| 133 permission_features = permission_features_future.Get() | 312 # Copy down to features_map level because the 'resolved' and |
| 134 # TODO(rockot): Handle inter-API dependencies more gracefully. | 313 # 'unresolved' dicts will be modified. |
| 135 # Not yet a problem because there is only one such case (windows -> tabs). | 314 features_map[cache_type] = dict((c, copy(d)) |
| 136 # If we don't store this value before annotating platforms, inter-API | 315 for c, d in future.Get().iteritems()) |
| 137 # dependencies will lead to infinite recursion. | 316 |
| 138 for feature in api_features.itervalues(): | 317 def has_unresolved(): |
| 139 _AddPlatformsAndChannelsFromDependencies( | 318 '''Determines if there are any unresolved features left over in any |
| 140 feature, api_features, manifest_features, permission_features) | 319 of the categories in |dependencies|. |
| 141 self._object_store.Set('api_features', api_features) | 320 ''' |
| 142 return api_features | 321 return any(cache['unresolved'] for cache in features_map.itervalues()) |
| 322 | |
| 323 # Iterate until everything is resolved. If dependencies are multiple | |
| 324 # levels deep, it might take multiple passes to inherit data to the | |
| 325 # topmost feature. | |
| 326 while has_unresolved(): | |
| 327 for cache_type, cache in features_map.iteritems(): | |
| 328 to_remove = [] | |
| 329 for feature_name, feature_values in cache['unresolved'].iteritems(): | |
| 330 resolve_successful, feature = _MergeFeature( | |
| 331 feature_name, | |
| 332 feature_values, | |
| 333 cache['extra'].get(feature_name, ()), | |
| 334 self._platform, | |
| 335 cache_type, | |
| 336 features_map) | |
| 337 if not resolve_successful: | |
| 338 continue # Try again on the next iteration of the while loop | |
| 339 | |
| 340 # When successfully resolved, remove it from the unresolved dict. | |
| 341 # Add it to the resolved dict if it didn't get deleted. | |
| 342 to_remove.append(feature_name) | |
| 343 if feature is not None: | |
| 344 cache['resolved'][feature_name] = feature | |
| 345 | |
| 346 for key in to_remove: | |
| 347 del cache['unresolved'][key] | |
| 348 | |
| 349 for cache_type, cache in features_map.iteritems(): | |
| 350 self._object_store.Set(cache_type, cache['resolved']) | |
| 351 return features_map[features_type]['resolved'] | |
| 352 | |
| 143 return Future(callback=resolve) | 353 return Future(callback=resolve) |
| 144 | |
| 145 def GetIdentity(self): | |
| 146 return self._identity | |
| OLD | NEW |