Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. | 1 # Copyright 2016 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 argparse | 5 import argparse |
| 6 import copy | 6 import copy |
| 7 from datetime import datetime | 7 from datetime import datetime |
| 8 from functools import partial | 8 from functools import partial |
| 9 import os | 9 import os |
| 10 import re | |
| 10 | 11 |
| 11 from code import Code | 12 from code import Code |
| 12 import json_parse | 13 import json_parse |
| 13 | 14 |
| 14 # The template for the header file of the generated FeatureProvider. | 15 # The template for the header file of the generated FeatureProvider. |
| 15 HEADER_FILE_TEMPLATE = """ | 16 HEADER_FILE_TEMPLATE = """ |
| 16 // Copyright %(year)s The Chromium Authors. All rights reserved. | 17 // Copyright %(year)s The Chromium Authors. All rights reserved. |
| 17 // Use of this source code is governed by a BSD-style license that can be | 18 // Use of this source code is governed by a BSD-style license that can be |
| 18 // found in the LICENSE file. | 19 // found in the LICENSE file. |
| 19 | 20 |
| (...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 91 # version_info::Channel::STABLE to channel. The keys in this map | 92 # version_info::Channel::STABLE to channel. The keys in this map |
| 92 # also serve as a list all of possible values. | 93 # also serve as a list all of possible values. |
| 93 # 'allow_all': Only applicable for lists. If present, this will check for | 94 # 'allow_all': Only applicable for lists. If present, this will check for |
| 94 # a value of "all" for a list value, and will replace it with | 95 # a value of "all" for a list value, and will replace it with |
| 95 # the collection of all possible values. For instance, if a | 96 # the collection of all possible values. For instance, if a |
| 96 # feature specifies 'contexts': 'all', the generated C++ will | 97 # feature specifies 'contexts': 'all', the generated C++ will |
| 97 # assign the list of Feature::BLESSED_EXTENSION_CONTEXT, | 98 # assign the list of Feature::BLESSED_EXTENSION_CONTEXT, |
| 98 # Feature::BLESSED_WEB_PAGE_CONTEXT et al for contexts. If not | 99 # Feature::BLESSED_WEB_PAGE_CONTEXT et al for contexts. If not |
| 99 # specified, defaults to false. | 100 # specified, defaults to false. |
| 100 # 'values': A list of all possible allowed values for a given key. | 101 # 'values': A list of all possible allowed values for a given key. |
| 102 # 'shared': Boolean that, if set, ensures that only one of the associated | |
| 103 # features has the feature property set. Used primarily for complex | |
| 104 # features - for simple features, there is always at most one feature | |
| 105 # setting an option. | |
| 101 # If a type definition does not have any restrictions (beyond the type itself), | 106 # If a type definition does not have any restrictions (beyond the type itself), |
| 102 # an empty definition ({}) is used. | 107 # an empty definition ({}) is used. |
| 103 FEATURE_GRAMMAR = ( | 108 FEATURE_GRAMMAR = ( |
| 104 { | 109 { |
| 110 'alias': { | |
| 111 unicode: {}, | |
| 112 'shared': True | |
| 113 }, | |
| 105 'blacklist': { | 114 'blacklist': { |
| 106 list: {'subtype': unicode} | 115 list: {'subtype': unicode} |
| 107 }, | 116 }, |
| 108 'channel': { | 117 'channel': { |
| 109 unicode: { | 118 unicode: { |
| 110 'enum_map': { | 119 'enum_map': { |
| 111 'trunk': 'version_info::Channel::UNKNOWN', | 120 'trunk': 'version_info::Channel::UNKNOWN', |
| 112 'canary': 'version_info::Channel::CANARY', | 121 'canary': 'version_info::Channel::CANARY', |
| 113 'dev': 'version_info::Channel::DEV', | 122 'dev': 'version_info::Channel::DEV', |
| 114 'beta': 'version_info::Channel::BETA', | 123 'beta': 'version_info::Channel::BETA', |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 190 } | 199 } |
| 191 }, | 200 }, |
| 192 'session_types': { | 201 'session_types': { |
| 193 list: { | 202 list: { |
| 194 'enum_map': { | 203 'enum_map': { |
| 195 'regular': 'FeatureSessionType::REGULAR', | 204 'regular': 'FeatureSessionType::REGULAR', |
| 196 'kiosk': 'FeatureSessionType::KIOSK', | 205 'kiosk': 'FeatureSessionType::KIOSK', |
| 197 } | 206 } |
| 198 } | 207 } |
| 199 }, | 208 }, |
| 209 'source': { | |
| 210 unicode: {}, | |
| 211 'shared': True | |
| 212 }, | |
| 200 'whitelist': { | 213 'whitelist': { |
| 201 list: {'subtype': unicode} | 214 list: {'subtype': unicode} |
| 202 }, | 215 }, |
| 203 }) | 216 }) |
| 204 | 217 |
| 205 FEATURE_CLASSES = ['APIFeature', 'BehaviorFeature', | 218 FEATURE_CLASSES = ['APIFeature', 'BehaviorFeature', |
| 206 'ManifestFeature', 'PermissionFeature'] | 219 'ManifestFeature', 'PermissionFeature'] |
| 207 | 220 |
| 208 def HasProperty(property_name, value): | 221 def HasProperty(property_name, value): |
| 209 return property_name in value | 222 return property_name in value |
| 210 | 223 |
| 211 def HasAtLeastOneProperty(property_names, value): | 224 def HasAtLeastOneProperty(property_names, value): |
| 212 return any([HasProperty(name, value) for name in property_names]) | 225 return any([HasProperty(name, value) for name in property_names]) |
| 213 | 226 |
| 227 def DoesNotHaveAllProperties(property_names, value): | |
| 228 return not all([HasProperty(name, value) for name in property_names]) | |
| 229 | |
| 214 def DoesNotHaveProperty(property_name, value): | 230 def DoesNotHaveProperty(property_name, value): |
| 215 return property_name not in value | 231 return property_name not in value |
| 216 | 232 |
| 233 def IsFeatureCrossReference(property_name, reverse_property_name, feature, | |
| 234 all_features): | |
| 235 value = feature.GetValue(property_name) | |
| 236 if not value: | |
| 237 return True | |
| 238 # String property values will be wrapped in "", strip those. | |
| 239 value_regex = re.compile('^"(.+)"$') | |
| 240 parsed_value = value_regex.match(value) | |
| 241 if not parsed_value: | |
|
Devlin
2016/11/28 20:42:21
Will this ever fail if we only call it for feature
tbarzic
2016/11/28 21:43:51
It shouldn't, but it might be misused - the questi
| |
| 242 return False | |
| 243 | |
| 244 referenced_feature = all_features.get(parsed_value.group(1)) | |
| 245 if not referenced_feature: | |
| 246 return False | |
| 247 reverse_reference_value = referenced_feature.GetValue(reverse_property_name) | |
| 248 if not reverse_reference_value: | |
| 249 return False | |
| 250 # Don't validate reverse reference value for child features - chances are that | |
| 251 # the value was inherited from a feature parent, in which case it won't match | |
| 252 # current feature name. | |
| 253 if feature.has_parent: | |
| 254 return True | |
| 255 return reverse_reference_value == ('"%s"' % feature.name) | |
| 256 | |
| 217 VALIDATION = ({ | 257 VALIDATION = ({ |
| 218 'all': [ | 258 'all': [ |
| 219 (partial(HasAtLeastOneProperty, ['channel', 'dependencies']), | 259 (partial(HasAtLeastOneProperty, ['channel', 'dependencies']), |
| 220 'Features must specify either a channel or dependencies'), | 260 'Features must specify either a channel or dependencies'), |
| 221 ], | 261 ], |
| 222 'APIFeature': [ | 262 'APIFeature': [ |
| 223 (partial(HasProperty, 'contexts'), | 263 (partial(HasProperty, 'contexts'), |
| 224 'APIFeatures must specify at least one context') | 264 'APIFeatures must specify at least one context'), |
| 265 (partial(DoesNotHaveAllProperties, ['alias', 'source']), | |
| 266 'Features cannot specify both alias and source.') | |
| 225 ], | 267 ], |
| 226 'ManifestFeature': [ | 268 'ManifestFeature': [ |
| 227 (partial(HasProperty, 'extension_types'), | 269 (partial(HasProperty, 'extension_types'), |
| 228 'ManifestFeatures must specify at least one extension type'), | 270 'ManifestFeatures must specify at least one extension type'), |
| 229 (partial(DoesNotHaveProperty, 'contexts'), | 271 (partial(DoesNotHaveProperty, 'contexts'), |
| 230 'ManifestFeatures do not support contexts.'), | 272 'ManifestFeatures do not support contexts.'), |
| 273 (partial(DoesNotHaveProperty, 'alias'), | |
| 274 'ManifestFeatures do not support alias.'), | |
| 275 (partial(DoesNotHaveProperty, 'source'), | |
| 276 'ManifestFeatures do not support source.'), | |
| 231 ], | 277 ], |
| 232 'BehaviorFeature': [], | 278 'BehaviorFeature': [ |
| 279 (partial(DoesNotHaveProperty, 'alias'), | |
| 280 'BehaviorFeatures do not support alias.'), | |
| 281 (partial(DoesNotHaveProperty, 'source'), | |
| 282 'BehaviorFeatures do not support source.'), | |
| 283 ], | |
| 233 'PermissionFeature': [ | 284 'PermissionFeature': [ |
| 234 (partial(HasProperty, 'extension_types'), | 285 (partial(HasProperty, 'extension_types'), |
| 235 'PermissionFeatures must specify at least one extension type'), | 286 'PermissionFeatures must specify at least one extension type'), |
| 236 (partial(DoesNotHaveProperty, 'contexts'), | 287 (partial(DoesNotHaveProperty, 'contexts'), |
| 237 'PermissionFeatures do not support contexts.'), | 288 'PermissionFeatures do not support contexts.'), |
| 289 (partial(DoesNotHaveProperty, 'alias'), | |
| 290 'PermissionFeatures do not support alias.'), | |
| 291 (partial(DoesNotHaveProperty, 'source'), | |
| 292 'PermissionFeatures do not support source.'), | |
| 238 ], | 293 ], |
| 239 }) | 294 }) |
| 240 | 295 |
| 296 FINAL_VALIDATION = ({ | |
| 297 'all': [], | |
| 298 'APIFeature': [ | |
| 299 (partial(IsFeatureCrossReference, 'alias', 'source'), | |
| 300 'A feature alias property should reference a feature whose source ' | |
| 301 'property references it back.'), | |
| 302 (partial(IsFeatureCrossReference, 'source', 'alias'), | |
| 303 'A feature source property should reference a feature whose alias ' | |
| 304 'property references it back.') | |
| 305 | |
| 306 ], | |
| 307 'ManifestFeature': [], | |
| 308 'BehaviorFeature': [], | |
| 309 'PermissionFeature': [] | |
| 310 }) | |
| 311 | |
| 241 # These keys are used to find the parents of different features, but are not | 312 # These keys are used to find the parents of different features, but are not |
| 242 # compiled into the features themselves. | 313 # compiled into the features themselves. |
| 243 IGNORED_KEYS = ['default_parent'] | 314 IGNORED_KEYS = ['default_parent'] |
| 244 | 315 |
| 245 # By default, if an error is encountered, assert to stop the compilation. This | 316 # By default, if an error is encountered, assert to stop the compilation. This |
| 246 # can be disabled for testing. | 317 # can be disabled for testing. |
| 247 ENABLE_ASSERTIONS = True | 318 ENABLE_ASSERTIONS = True |
| 248 | 319 |
| 249 # JSON parsing returns all strings of characters as unicode types. For testing, | 320 # JSON parsing returns all strings of characters as unicode types. For testing, |
| 250 # we can enable converting all string types to unicode to avoid writing u'' | 321 # we can enable converting all string types to unicode to avoid writing u'' |
| 251 # everywhere. | 322 # everywhere. |
| 252 STRINGS_TO_UNICODE = False | 323 STRINGS_TO_UNICODE = False |
| 253 | 324 |
| 325 def GetCodeForFeatureValues(feature_values): | |
| 326 """ Gets the Code object for setting feature values for this object. """ | |
| 327 c = Code() | |
| 328 for key in sorted(feature_values.keys()): | |
| 329 if key in IGNORED_KEYS: | |
| 330 continue; | |
| 331 c.Append('feature->set_%s(%s);' % (key, feature_values[key])) | |
| 332 return c | |
| 333 | |
| 254 class Feature(object): | 334 class Feature(object): |
| 255 """A representation of a single simple feature that can handle all parsing, | 335 """A representation of a single simple feature that can handle all parsing, |
| 256 validation, and code generation. | 336 validation, and code generation. |
| 257 """ | 337 """ |
| 258 def __init__(self, name): | 338 def __init__(self, name): |
| 259 self.name = name | 339 self.name = name |
| 260 self.has_parent = False | 340 self.has_parent = False |
| 261 self.errors = [] | 341 self.errors = [] |
| 262 self.feature_values = {} | 342 self.feature_values = {} |
| 343 self.shared_values = {} | |
| 263 | 344 |
| 264 def _GetType(self, value): | 345 def _GetType(self, value): |
| 265 """Returns the type of the given value. This can be different than type() if | 346 """Returns the type of the given value. This can be different than type() if |
| 266 STRINGS_TO_UNICODE is enabled. | 347 STRINGS_TO_UNICODE is enabled. |
| 267 """ | 348 """ |
| 268 t = type(value) | 349 t = type(value) |
| 269 if not STRINGS_TO_UNICODE: | 350 if not STRINGS_TO_UNICODE: |
| 270 return t | 351 return t |
| 271 if t is str: | 352 if t is str: |
| 272 return unicode | 353 return unicode |
| 273 return t | 354 return t |
| 274 | 355 |
| 275 def _AddError(self, error): | 356 def AddError(self, error): |
| 276 """Adds an error to the feature. If ENABLE_ASSERTIONS is active, this will | 357 """Adds an error to the feature. If ENABLE_ASSERTIONS is active, this will |
| 277 also assert to stop the compilation process (since errors should never be | 358 also assert to stop the compilation process (since errors should never be |
| 278 found in production). | 359 found in production). |
| 279 """ | 360 """ |
| 280 self.errors.append(error) | 361 self.errors.append(error) |
| 281 if ENABLE_ASSERTIONS: | 362 if ENABLE_ASSERTIONS: |
| 282 assert False, error | 363 assert False, error |
| 283 | 364 |
| 284 def _AddKeyError(self, key, error): | 365 def _AddKeyError(self, key, error): |
| 285 """Adds an error relating to a particular key in the feature. | 366 """Adds an error relating to a particular key in the feature. |
| 286 """ | 367 """ |
| 287 self._AddError('Error parsing feature "%s" at key "%s": %s' % | 368 self.AddError('Error parsing feature "%s" at key "%s": %s' % |
| 288 (self.name, key, error)) | 369 (self.name, key, error)) |
| 289 | 370 |
| 290 def _GetCheckedValue(self, key, expected_type, expected_values, | 371 def _GetCheckedValue(self, key, expected_type, expected_values, |
| 291 enum_map, value): | 372 enum_map, value): |
| 292 """Returns a string to be used in the generated C++ code for a given key's | 373 """Returns a string to be used in the generated C++ code for a given key's |
| 293 python value, or None if the value is invalid. For example, if the python | 374 python value, or None if the value is invalid. For example, if the python |
| 294 value is True, this returns 'true', for a string foo, this returns "foo", | 375 value is True, this returns 'true', for a string foo, this returns "foo", |
| 295 and for an enum, this looks up the C++ definition in the enum map. | 376 and for an enum, this looks up the C++ definition in the enum map. |
| 296 key: The key being parsed. | 377 key: The key being parsed. |
| 297 expected_type: The expected type for this value, or None if any type is | 378 expected_type: The expected type for this value, or None if any type is |
| 298 allowed. | 379 allowed. |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 319 return enum_map[value] | 400 return enum_map[value] |
| 320 | 401 |
| 321 if t in [str, unicode]: | 402 if t in [str, unicode]: |
| 322 return '"%s"' % str(value) | 403 return '"%s"' % str(value) |
| 323 if t is int: | 404 if t is int: |
| 324 return str(value) | 405 return str(value) |
| 325 if t is bool: | 406 if t is bool: |
| 326 return 'true' if value else 'false' | 407 return 'true' if value else 'false' |
| 327 assert False, 'Unsupported type: %s' % value | 408 assert False, 'Unsupported type: %s' % value |
| 328 | 409 |
| 329 def _ParseKey(self, key, value, grammar): | 410 def _ParseKey(self, key, value, shared_values, grammar): |
| 330 """Parses the specific key according to the grammar rule for that key if it | 411 """Parses the specific key according to the grammar rule for that key if it |
| 331 is present in the json value. | 412 is present in the json value. |
| 332 key: The key to parse. | 413 key: The key to parse. |
| 333 value: The full value for this feature. | 414 value: The full value for this feature. |
| 415 shared_values: Set of shared vfalues associated with this feature. | |
| 334 grammar: The rule for the specific key. | 416 grammar: The rule for the specific key. |
| 335 """ | 417 """ |
| 336 if key not in value: | 418 if key not in value: |
| 337 return | 419 return |
| 338 v = value[key] | 420 v = value[key] |
| 339 | 421 |
| 340 is_all = False | 422 is_all = False |
| 341 if v == 'all' and list in grammar and 'allow_all' in grammar[list]: | 423 if v == 'all' and list in grammar and 'allow_all' in grammar[list]: |
| 342 v = [] | 424 v = [] |
| 343 is_all = True | 425 is_all = True |
| 344 | 426 |
| 427 if 'shared' in grammar and key in shared_values: | |
| 428 self._AddKeyError(key, 'Key can be set at most once per feature.') | |
| 429 return | |
| 430 | |
| 345 value_type = self._GetType(v) | 431 value_type = self._GetType(v) |
| 346 if value_type not in grammar: | 432 if value_type not in grammar: |
| 347 self._AddKeyError(key, 'Illegal value: "%s"' % v) | 433 self._AddKeyError(key, 'Illegal value: "%s"' % v) |
| 348 return | 434 return |
| 349 | 435 |
| 350 expected = grammar[value_type] | 436 expected = grammar[value_type] |
| 351 expected_values = None | 437 expected_values = None |
| 352 enum_map = None | 438 enum_map = None |
| 353 if 'values' in expected: | 439 if 'values' in expected: |
| 354 expected_values = expected['values'] | 440 expected_values = expected['values'] |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 374 sub_value) | 460 sub_value) |
| 375 if cpp_sub_value: | 461 if cpp_sub_value: |
| 376 cpp_value.append(cpp_sub_value) | 462 cpp_value.append(cpp_sub_value) |
| 377 if cpp_value: | 463 if cpp_value: |
| 378 cpp_value = '{' + ','.join(cpp_value) + '}' | 464 cpp_value = '{' + ','.join(cpp_value) + '}' |
| 379 else: | 465 else: |
| 380 cpp_value = self._GetCheckedValue(key, expected_type, expected_values, | 466 cpp_value = self._GetCheckedValue(key, expected_type, expected_values, |
| 381 enum_map, v) | 467 enum_map, v) |
| 382 | 468 |
| 383 if cpp_value: | 469 if cpp_value: |
| 384 self.feature_values[key] = cpp_value | 470 if 'shared' in grammar: |
| 471 shared_values[key] = cpp_value | |
| 472 else: | |
| 473 self.feature_values[key] = cpp_value | |
| 385 elif key in self.feature_values: | 474 elif key in self.feature_values: |
| 386 # If the key is empty and this feature inherited a value from its parent, | 475 # If the key is empty and this feature inherited a value from its parent, |
| 387 # remove the inherited value. | 476 # remove the inherited value. |
| 388 del self.feature_values[key] | 477 del self.feature_values[key] |
| 389 | 478 |
| 390 def SetParent(self, parent): | 479 def SetParent(self, parent): |
| 391 """Sets the parent of this feature, and inherits all properties from that | 480 """Sets the parent of this feature, and inherits all properties from that |
| 392 parent. | 481 parent. |
| 393 """ | 482 """ |
| 394 assert not self.feature_values, 'Parents must be set before parsing' | 483 assert not self.feature_values, 'Parents must be set before parsing' |
| 395 self.feature_values = copy.deepcopy(parent.feature_values) | 484 self.feature_values = copy.deepcopy(parent.feature_values) |
| 396 self.has_parent = True | 485 self.has_parent = True |
| 397 | 486 |
| 398 def Parse(self, parsed_json): | 487 def SetSharedValues(self, values): |
| 488 self.shared_values = values | |
| 489 | |
| 490 def Parse(self, parsed_json, shared_values): | |
| 399 """Parses the feature from the given json value.""" | 491 """Parses the feature from the given json value.""" |
| 400 for key in parsed_json.keys(): | 492 for key in parsed_json.keys(): |
| 401 if key not in FEATURE_GRAMMAR: | 493 if key not in FEATURE_GRAMMAR: |
| 402 self._AddKeyError(key, 'Unrecognized key') | 494 self._AddKeyError(key, 'Unrecognized key') |
| 403 for key, key_grammar in FEATURE_GRAMMAR.iteritems(): | 495 for key, key_grammar in FEATURE_GRAMMAR.iteritems(): |
| 404 self._ParseKey(key, parsed_json, key_grammar) | 496 self._ParseKey(key, parsed_json, shared_values, key_grammar) |
| 405 | 497 |
| 406 def Validate(self, feature_class): | 498 def Validate(self, feature_class, shared_values): |
| 499 feature_values = self.GetAllFeatureValues() | |
| 500 feature_values.update(shared_values) | |
|
Devlin
2016/11/28 20:42:21
Doesn't GetAllFeatureValues already add shared val
tbarzic
2016/11/28 21:43:52
yes, but at this point self.shared_values are not
| |
| 407 for validator, error in (VALIDATION[feature_class] + VALIDATION['all']): | 501 for validator, error in (VALIDATION[feature_class] + VALIDATION['all']): |
| 408 if not validator(self.feature_values): | 502 if not validator(feature_values): |
| 409 self._AddError(error) | 503 self.AddError(error) |
| 410 | 504 |
| 411 def GetCode(self, feature_class): | 505 def GetCode(self, feature_class): |
| 412 """Returns the Code object for generating this feature.""" | 506 """Returns the Code object for generating this feature.""" |
| 413 c = Code() | 507 c = Code() |
| 414 c.Append('%s* feature = new %s();' % (feature_class, feature_class)) | 508 c.Append('%s* feature = new %s();' % (feature_class, feature_class)) |
| 415 c.Append('feature->set_name("%s");' % self.name) | 509 c.Append('feature->set_name("%s");' % self.name) |
| 416 for key in sorted(self.feature_values.keys()): | 510 c.Concat(GetCodeForFeatureValues(self.GetAllFeatureValues())) |
| 417 if key in IGNORED_KEYS: | |
| 418 continue; | |
| 419 c.Append('feature->set_%s(%s);' % (key, self.feature_values[key])) | |
| 420 return c | 511 return c |
| 421 | 512 |
| 513 def AsParent(self): | |
| 514 """ Returns the feature values that should be inherited by children features | |
| 515 when this feature is set as parent. | |
| 516 """ | |
| 517 return self | |
| 518 | |
| 519 def GetValue(self, key): | |
| 520 """ Gets feature value for the specified key """ | |
| 521 value = self.feature_values.get(key) | |
| 522 return value if value else self.shared_values.get(key) | |
| 523 | |
| 524 def GetAllFeatureValues(self): | |
| 525 """ Gets all values set for this feature. """ | |
| 526 values = self.feature_values.copy() | |
| 527 values.update(self.shared_values) | |
| 528 return values | |
| 529 | |
| 530 def GetErrors(self): | |
| 531 return self.errors; | |
| 532 | |
| 533 class ComplexFeature(Feature): | |
| 534 """ Complex feature - feature that is comprised of list of features. | |
| 535 Overall complex feature is available if any of contained | |
| 536 feature is available. | |
| 537 """ | |
| 538 def __init__(self, name): | |
| 539 Feature.__init__(self, name) | |
| 540 self.shared_values = {} | |
|
Devlin
2016/11/28 20:42:21
shared_values should be init'd as part of the feat
tbarzic
2016/11/28 21:43:52
Yes, removed.
| |
| 541 self.feature_list = [] | |
| 542 | |
| 543 def GetCode(self, feature_class): | |
| 544 c = Code() | |
| 545 c.Append('std::vector<Feature*> features;') | |
| 546 for f in self.feature_list: | |
|
Devlin
2016/11/28 20:42:21
As a sanity check, could we here assert that f.sha
tbarzic
2016/11/28 21:43:52
Done.
| |
| 547 c.Sblock('{') | |
| 548 c.Concat(f.GetCode(feature_class)) | |
| 549 c.Append('features.push_back(feature);') | |
| 550 c.Eblock('}') | |
| 551 c.Append('ComplexFeature* feature(new ComplexFeature(&features));') | |
| 552 c.Append('feature->set_name("%s");' % self.name) | |
| 553 c.Concat(GetCodeForFeatureValues(self.GetAllFeatureValues())) | |
|
Devlin
2016/11/28 20:42:21
I think it's more confusing to pass in GetCodeForF
tbarzic
2016/11/28 21:43:52
Done.
| |
| 554 return c | |
| 555 | |
| 556 def AsParent(self): | |
| 557 parent = None | |
| 558 for p in self.feature_list: | |
| 559 if 'default_parent' in p.feature_values: | |
| 560 parent = p | |
| 561 break | |
| 562 assert parent, 'No default parent found for %s' % self.name | |
| 563 return parent | |
| 564 | |
| 565 def GetAllFeatureValues(self): | |
| 566 return self.shared_values.copy() | |
| 567 | |
| 568 def GetErrors(self): | |
| 569 errors = copy.copy(self.errors) | |
| 570 for feature in self.feature_list: | |
| 571 errors.extend(feature.GetErrors()) | |
| 572 return errors | |
| 573 | |
| 422 class FeatureCompiler(object): | 574 class FeatureCompiler(object): |
| 423 """A compiler to load, parse, and generate C++ code for a number of | 575 """A compiler to load, parse, and generate C++ code for a number of |
| 424 features.json files.""" | 576 features.json files.""" |
| 425 def __init__(self, chrome_root, source_files, feature_class, | 577 def __init__(self, chrome_root, source_files, feature_class, |
| 426 provider_class, out_root, out_base_filename): | 578 provider_class, out_root, out_base_filename): |
| 427 # See __main__'s ArgumentParser for documentation on these properties. | 579 # See __main__'s ArgumentParser for documentation on these properties. |
| 428 self._chrome_root = chrome_root | 580 self._chrome_root = chrome_root |
| 429 self._source_files = source_files | 581 self._source_files = source_files |
| 430 self._feature_class = feature_class | 582 self._feature_class = feature_class |
| 431 self._provider_class = provider_class | 583 self._provider_class = provider_class |
| 432 self._out_root = out_root | 584 self._out_root = out_root |
| 433 self._out_base_filename = out_base_filename | 585 self._out_base_filename = out_base_filename |
| 434 | 586 |
| 435 # The json value for the feature files. | 587 # The json value for the feature files. |
| 436 self._json = {} | 588 self._json = {} |
| 437 # The parsed features. | 589 # The parsed features. |
| 438 self._features = {} | 590 self._features = {} |
| 439 | 591 |
| 440 def _Load(self): | 592 def Load(self): |
| 441 """Loads and parses the source from each input file and puts the result in | 593 """Loads and parses the source from each input file and puts the result in |
| 442 self._json.""" | 594 self._json.""" |
| 443 for f in self._source_files: | 595 for f in self._source_files: |
| 444 abs_source_file = os.path.join(self._chrome_root, f) | 596 abs_source_file = os.path.join(self._chrome_root, f) |
| 445 try: | 597 try: |
| 446 with open(abs_source_file, 'r') as f: | 598 with open(abs_source_file, 'r') as f: |
| 447 f_json = json_parse.Parse(f.read()) | 599 f_json = json_parse.Parse(f.read()) |
| 448 except: | 600 except: |
| 449 print('FAILED: Exception encountered while loading "%s"' % | 601 print('FAILED: Exception encountered while loading "%s"' % |
| 450 abs_source_file) | 602 abs_source_file) |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 479 parent_name = feature_name[:sep] | 631 parent_name = feature_name[:sep] |
| 480 | 632 |
| 481 if sep == -1: | 633 if sep == -1: |
| 482 # TODO(devlin): It'd be kind of nice to be able to assert that the | 634 # TODO(devlin): It'd be kind of nice to be able to assert that the |
| 483 # deduced parent name is in our features, but some dotted features don't | 635 # deduced parent name is in our features, but some dotted features don't |
| 484 # have parents and also don't have noparent, e.g. system.cpu. We should | 636 # have parents and also don't have noparent, e.g. system.cpu. We should |
| 485 # probably just noparent them so that we can assert this. | 637 # probably just noparent them so that we can assert this. |
| 486 # raise KeyError('Could not find parent "%s" for feature "%s".' % | 638 # raise KeyError('Could not find parent "%s" for feature "%s".' % |
| 487 # (parent_name, feature_name)) | 639 # (parent_name, feature_name)) |
| 488 return None | 640 return None |
| 489 parent_value = self._features[parent_name] | 641 return self._features[parent_name].AsParent() |
| 490 parent = parent_value | |
| 491 if type(parent_value) is list: | |
| 492 for p in parent_value: | |
| 493 if 'default_parent' in p.feature_values: | |
| 494 parent = p | |
| 495 break | |
| 496 assert parent, 'No default parent found for %s' % parent_name | |
| 497 return parent | |
| 498 | 642 |
| 499 def _CompileFeature(self, feature_name, feature_value): | 643 def _CompileFeature(self, feature_name, feature_value): |
| 500 """Parses a single feature.""" | 644 """Parses a single feature.""" |
| 501 if 'nocompile' in feature_value: | 645 if 'nocompile' in feature_value: |
| 502 assert feature_value['nocompile'], ( | 646 assert feature_value['nocompile'], ( |
| 503 'nocompile should only be true; otherwise omit this key.') | 647 'nocompile should only be true; otherwise omit this key.') |
| 504 return | 648 return |
| 505 | 649 |
| 506 def parse_and_validate(name, value, parent): | 650 def parse_and_validate(name, value, parent, shared_values): |
| 507 try: | 651 try: |
| 508 feature = Feature(name) | 652 feature = Feature(name) |
| 509 if parent: | 653 if parent: |
| 510 feature.SetParent(parent) | 654 feature.SetParent(parent) |
| 511 feature.Parse(value) | 655 feature.Parse(value, shared_values) |
| 512 feature.Validate(self._feature_class) | 656 feature.Validate(self._feature_class, shared_values) |
| 513 return feature | 657 return feature |
| 514 except: | 658 except: |
| 515 print('Failure to parse feature "%s"' % feature_name) | 659 print('Failure to parse feature "%s"' % feature_name) |
| 516 raise | 660 raise |
| 517 | 661 |
| 518 parent = self._FindParent(feature_name, feature_value) | 662 parent = self._FindParent(feature_name, feature_value) |
| 663 shared_values = {} | |
| 664 | |
| 519 # Handle complex features, which are lists of simple features. | 665 # Handle complex features, which are lists of simple features. |
| 520 if type(feature_value) is list: | 666 if type(feature_value) is list: |
| 521 feature_list = [] | 667 feature = ComplexFeature(feature_name) |
| 668 | |
| 522 # This doesn't handle nested complex features. I think that's probably for | 669 # This doesn't handle nested complex features. I think that's probably for |
| 523 # the best. | 670 # the best. |
| 524 for v in feature_value: | 671 for v in feature_value: |
| 525 feature_list.append(parse_and_validate(feature_name, v, parent)) | 672 feature.feature_list.append( |
| 526 self._features[feature_name] = feature_list | 673 parse_and_validate(feature_name, v, parent, shared_values)) |
| 527 return | 674 self._features[feature_name] = feature |
| 675 else: | |
| 676 self._features[feature_name] = parse_and_validate( | |
| 677 feature_name, feature_value, parent, shared_values) | |
| 528 | 678 |
| 529 self._features[feature_name] = parse_and_validate( | 679 # Apply parent shared values at the end to enable child features to |
| 530 feature_name, feature_value, parent) | 680 # override parent shared value - if parent shared values are added to |
| 681 # shared value set before a child feature is parsed, the child feature | |
| 682 # overriding shared values set by its parent would cause an error due to | |
| 683 # shared values being set twice. | |
| 684 final_shared_values = copy.deepcopy(parent.shared_values) if parent else {} | |
| 685 final_shared_values.update(shared_values) | |
| 686 self._features[feature_name].SetSharedValues(final_shared_values) | |
| 687 | |
| 688 def _FinalValidation(self): | |
| 689 validators = FINAL_VALIDATION['all'] + FINAL_VALIDATION[self._feature_class] | |
| 690 for name, feature in self._features.items(): | |
| 691 for validator, error in validators: | |
| 692 if not validator(feature, self._features): | |
| 693 feature.AddError(error) | |
| 531 | 694 |
| 532 def Compile(self): | 695 def Compile(self): |
| 533 """Parses all features after loading the input files.""" | 696 """Parses all features after loading the input files.""" |
| 534 self._Load(); | |
| 535 # Iterate over in sorted order so that parents come first. | 697 # Iterate over in sorted order so that parents come first. |
| 536 for k in sorted(self._json.keys()): | 698 for k in sorted(self._json.keys()): |
| 537 self._CompileFeature(k, self._json[k]) | 699 self._CompileFeature(k, self._json[k]) |
| 700 self._FinalValidation() | |
| 538 | 701 |
| 539 def Render(self): | 702 def Render(self): |
| 540 """Returns the Code object for the body of the .cc file, which handles the | 703 """Returns the Code object for the body of the .cc file, which handles the |
| 541 initialization of all features.""" | 704 initialization of all features.""" |
| 542 c = Code() | 705 c = Code() |
| 543 c.Append('%s::%s() {' % (self._provider_class, self._provider_class)) | 706 c.Append('%s::%s() {' % (self._provider_class, self._provider_class)) |
| 544 c.Sblock() | 707 c.Sblock() |
| 545 for k in sorted(self._features.keys()): | 708 for k in sorted(self._features.keys()): |
| 546 c.Sblock('{') | 709 c.Sblock('{') |
| 547 feature = self._features[k] | 710 feature = self._features[k] |
| 548 if type(feature) is list: | 711 c.Concat(feature.GetCode(self._feature_class)) |
| 549 c.Append('std::vector<Feature*> features;') | |
| 550 for f in feature: | |
| 551 c.Sblock('{') | |
| 552 c.Concat(f.GetCode(self._feature_class)) | |
| 553 c.Append('features.push_back(feature);') | |
| 554 c.Eblock('}') | |
| 555 c.Append('ComplexFeature* feature(new ComplexFeature(&features));') | |
| 556 c.Append('feature->set_name("%s");' % k) | |
| 557 else: | |
| 558 c.Concat(feature.GetCode(self._feature_class)) | |
| 559 c.Append('AddFeature("%s", feature);' % k) | 712 c.Append('AddFeature("%s", feature);' % k) |
| 560 c.Eblock('}') | 713 c.Eblock('}') |
| 561 c.Eblock('}') | 714 c.Eblock('}') |
| 562 return c | 715 return c |
| 563 | 716 |
| 564 def Write(self): | 717 def Write(self): |
| 565 """Writes the output.""" | 718 """Writes the output.""" |
| 566 header_file_path = self._out_base_filename + '.h' | 719 header_file_path = self._out_base_filename + '.h' |
| 567 cc_file_path = self._out_base_filename + '.cc' | 720 cc_file_path = self._out_base_filename + '.cc' |
| 568 substitutions = ({ | 721 substitutions = ({ |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 609 'out_base_filename', type=str, | 762 'out_base_filename', type=str, |
| 610 help='The base filename for the C++ files (.h and .cc will be appended)') | 763 help='The base filename for the C++ files (.h and .cc will be appended)') |
| 611 parser.add_argument('source_files', type=str, nargs='+', | 764 parser.add_argument('source_files', type=str, nargs='+', |
| 612 help='The source features.json files') | 765 help='The source features.json files') |
| 613 args = parser.parse_args() | 766 args = parser.parse_args() |
| 614 if args.feature_class not in FEATURE_CLASSES: | 767 if args.feature_class not in FEATURE_CLASSES: |
| 615 raise NameError('Unknown feature class: %s' % args.feature_class) | 768 raise NameError('Unknown feature class: %s' % args.feature_class) |
| 616 c = FeatureCompiler(args.chrome_root, args.source_files, args.feature_class, | 769 c = FeatureCompiler(args.chrome_root, args.source_files, args.feature_class, |
| 617 args.provider_class, args.out_root, | 770 args.provider_class, args.out_root, |
| 618 args.out_base_filename) | 771 args.out_base_filename) |
| 772 c.Load() | |
| 619 c.Compile() | 773 c.Compile() |
| 620 c.Write() | 774 c.Write() |
| OLD | NEW |