Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(491)

Side by Side Diff: tools/json_schema_compiler/feature_compiler.py

Issue 2494653005: Support API aliases (Closed)
Patch Set: . Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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 10
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after
91 # version_info::Channel::STABLE to channel. The keys in this map 91 # version_info::Channel::STABLE to channel. The keys in this map
92 # also serve as a list all of possible values. 92 # also serve as a list all of possible values.
93 # 'allow_all': Only applicable for lists. If present, this will check for 93 # '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 94 # a value of "all" for a list value, and will replace it with
95 # the collection of all possible values. For instance, if a 95 # the collection of all possible values. For instance, if a
96 # feature specifies 'contexts': 'all', the generated C++ will 96 # feature specifies 'contexts': 'all', the generated C++ will
97 # assign the list of Feature::BLESSED_EXTENSION_CONTEXT, 97 # assign the list of Feature::BLESSED_EXTENSION_CONTEXT,
98 # Feature::BLESSED_WEB_PAGE_CONTEXT et al for contexts. If not 98 # Feature::BLESSED_WEB_PAGE_CONTEXT et al for contexts. If not
99 # specified, defaults to false. 99 # specified, defaults to false.
100 # 'values': A list of all possible allowed values for a given key. 100 # 'values': A list of all possible allowed values for a given key.
101 # 'shared': Boolean that, if set, ensures that only one of the associated
102 # features has the feature property set. Used primarily for complex
103 # features - for simple features, there is always at most one feature
104 # setting an option.
105 # 'feature_reference': Only applicable for strings. An object that, if
Devlin 2016/11/21 16:55:53 This feature reference code looks like it adds a l
tbarzic 2016/11/21 23:49:47 Done (though the error message returned using this
106 # provided, verifies that the value is set to a name of another feature
107 # defined in the same features file. It can following options:
108 # 'reverse_reference': String that should be equal to another feature
109 # option. If set, it verifies that the feature referenced by
110 # |feature_reference| value references this feature using
111 # |reverse_reference| option.
101 # If a type definition does not have any restrictions (beyond the type itself), 112 # If a type definition does not have any restrictions (beyond the type itself),
102 # an empty definition ({}) is used. 113 # an empty definition ({}) is used.
103 FEATURE_GRAMMAR = ( 114 FEATURE_GRAMMAR = (
104 { 115 {
116 'alias': {
117 unicode: {},
118 'feature_reference': {
119 'reverse_reference': 'source'
120 },
121 'shared': True
122 },
105 'blacklist': { 123 'blacklist': {
106 list: {'subtype': unicode} 124 list: {'subtype': unicode}
107 }, 125 },
108 'channel': { 126 'channel': {
109 unicode: { 127 unicode: {
110 'enum_map': { 128 'enum_map': {
111 'trunk': 'version_info::Channel::UNKNOWN', 129 'trunk': 'version_info::Channel::UNKNOWN',
112 'canary': 'version_info::Channel::CANARY', 130 'canary': 'version_info::Channel::CANARY',
113 'dev': 'version_info::Channel::DEV', 131 'dev': 'version_info::Channel::DEV',
114 'beta': 'version_info::Channel::BETA', 132 'beta': 'version_info::Channel::BETA',
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after
190 } 208 }
191 }, 209 },
192 'session_types': { 210 'session_types': {
193 list: { 211 list: {
194 'enum_map': { 212 'enum_map': {
195 'regular': 'FeatureSessionType::REGULAR', 213 'regular': 'FeatureSessionType::REGULAR',
196 'kiosk': 'FeatureSessionType::KIOSK', 214 'kiosk': 'FeatureSessionType::KIOSK',
197 } 215 }
198 } 216 }
199 }, 217 },
218 'source': {
219 unicode: {},
220 'feature_reference': {
221 'reverse_reference': 'alias'
222 },
223 'shared': True
224 },
200 'whitelist': { 225 'whitelist': {
201 list: {'subtype': unicode} 226 list: {'subtype': unicode}
202 }, 227 },
203 }) 228 })
204 229
205 FEATURE_CLASSES = ['APIFeature', 'BehaviorFeature', 230 FEATURE_CLASSES = ['APIFeature', 'BehaviorFeature',
206 'ManifestFeature', 'PermissionFeature'] 231 'ManifestFeature', 'PermissionFeature']
207 232
208 def HasProperty(property_name, value): 233 def HasProperty(property_name, value):
209 return property_name in value 234 return property_name in value
(...skipping 11 matching lines...) Expand all
221 ], 246 ],
222 'APIFeature': [ 247 'APIFeature': [
223 (partial(HasProperty, 'contexts'), 248 (partial(HasProperty, 'contexts'),
224 'APIFeatures must specify at least one context') 249 'APIFeatures must specify at least one context')
225 ], 250 ],
226 'ManifestFeature': [ 251 'ManifestFeature': [
227 (partial(HasProperty, 'extension_types'), 252 (partial(HasProperty, 'extension_types'),
228 'ManifestFeatures must specify at least one extension type'), 253 'ManifestFeatures must specify at least one extension type'),
229 (partial(DoesNotHaveProperty, 'contexts'), 254 (partial(DoesNotHaveProperty, 'contexts'),
230 'ManifestFeatures do not support contexts.'), 255 'ManifestFeatures do not support contexts.'),
256 (partial(DoesNotHaveProperty, 'alias'),
257 'ManifestFeatures do not support alias.'),
258 (partial(DoesNotHaveProperty, 'source'),
259 'ManifestFeatures do not support source.'),
231 ], 260 ],
232 'BehaviorFeature': [], 261 'BehaviorFeature': [
262 (partial(DoesNotHaveProperty, 'alias'),
263 'BehaviorFeatures do not support alias.'),
264 (partial(DoesNotHaveProperty, 'source'),
265 'BehaviorFeatures do not support source.'),
266 ],
233 'PermissionFeature': [ 267 'PermissionFeature': [
234 (partial(HasProperty, 'extension_types'), 268 (partial(HasProperty, 'extension_types'),
235 'PermissionFeatures must specify at least one extension type'), 269 'PermissionFeatures must specify at least one extension type'),
236 (partial(DoesNotHaveProperty, 'contexts'), 270 (partial(DoesNotHaveProperty, 'contexts'),
237 'PermissionFeatures do not support contexts.'), 271 'PermissionFeatures do not support contexts.'),
272 (partial(DoesNotHaveProperty, 'alias'),
273 'PermissionFeatures do not support alias.'),
274 (partial(DoesNotHaveProperty, 'source'),
275 'PermissionFeatures do not support source.'),
238 ], 276 ],
239 }) 277 })
240 278
241 # These keys are used to find the parents of different features, but are not 279 # These keys are used to find the parents of different features, but are not
242 # compiled into the features themselves. 280 # compiled into the features themselves.
243 IGNORED_KEYS = ['default_parent'] 281 IGNORED_KEYS = ['default_parent']
244 282
245 # By default, if an error is encountered, assert to stop the compilation. This 283 # By default, if an error is encountered, assert to stop the compilation. This
246 # can be disabled for testing. 284 # can be disabled for testing.
247 ENABLE_ASSERTIONS = True 285 ENABLE_ASSERTIONS = True
248 286
249 # JSON parsing returns all strings of characters as unicode types. For testing, 287 # 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'' 288 # we can enable converting all string types to unicode to avoid writing u''
251 # everywhere. 289 # everywhere.
252 STRINGS_TO_UNICODE = False 290 STRINGS_TO_UNICODE = False
253 291
254 class Feature(object): 292 class Feature(object):
255 """A representation of a single simple feature that can handle all parsing, 293 """A representation of a single simple feature that can handle all parsing,
256 validation, and code generation. 294 validation, and code generation.
257 """ 295 """
258 def __init__(self, name): 296 def __init__(self, name):
259 self.name = name 297 self.name = name
260 self.has_parent = False 298 self.has_parent = False
261 self.errors = [] 299 self.errors = []
262 self.feature_values = {} 300 self.feature_values = {}
301 self.shared_values = {}
302 self.feature_references = []
263 303
264 def _GetType(self, value): 304 def _GetType(self, value):
265 """Returns the type of the given value. This can be different than type() if 305 """Returns the type of the given value. This can be different than type() if
266 STRINGS_TO_UNICODE is enabled. 306 STRINGS_TO_UNICODE is enabled.
267 """ 307 """
268 t = type(value) 308 t = type(value)
269 if not STRINGS_TO_UNICODE: 309 if not STRINGS_TO_UNICODE:
270 return t 310 return t
271 if t is str: 311 if t is str:
272 return unicode 312 return unicode
273 return t 313 return t
274 314
275 def _AddError(self, error): 315 def _AddError(self, error):
276 """Adds an error to the feature. If ENABLE_ASSERTIONS is active, this will 316 """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 317 also assert to stop the compilation process (since errors should never be
278 found in production). 318 found in production).
279 """ 319 """
280 self.errors.append(error) 320 self.errors.append(error)
281 if ENABLE_ASSERTIONS: 321 if ENABLE_ASSERTIONS:
282 assert False, error 322 assert False, error
283 323
284 def _AddKeyError(self, key, error): 324 def _AddKeyError(self, key, error):
285 """Adds an error relating to a particular key in the feature. 325 """Adds an error relating to a particular key in the feature.
286 """ 326 """
287 self._AddError('Error parsing feature "%s" at key "%s": %s' % 327 self._AddError('Error parsing feature "%s" at key "%s": %s' %
288 (self.name, key, error)) 328 (self.name, key, error))
289 329
290 def _GetCheckedValue(self, key, expected_type, expected_values, 330 def _GetCheckedValue(self, key, expected_type, expected_values,
291 enum_map, value): 331 enum_map, feature_reference, value):
292 """Returns a string to be used in the generated C++ code for a given key's 332 """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 333 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", 334 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. 335 and for an enum, this looks up the C++ definition in the enum map.
296 key: The key being parsed. 336 key: The key being parsed.
297 expected_type: The expected type for this value, or None if any type is 337 expected_type: The expected type for this value, or None if any type is
298 allowed. 338 allowed.
299 expected_values: The list of allowed values for this value, or None if any 339 expected_values: The list of allowed values for this value, or None if any
300 value is allowed. 340 value is allowed.
301 enum_map: The map from python value -> cpp value for all allowed values, 341 enum_map: The map from python value -> cpp value for all allowed values,
302 or None if no special mapping should be made. 342 or None if no special mapping should be made.
343 feature_reference: Object set if the value should represent a feature
Devlin 2016/11/21 16:55:53 (With the above, we can remove this and most "feat
tbarzic 2016/11/21 23:49:47 Done.
344 reference. If set, the value is expected to be string referencing
345 another feature in the feature file. While it will be checked that the
346 value is string, validation that the value references an existing
347 feature will have to be postponed until all features are parsed - the
348 info needed to verify the key value will be cached in
349 |self.feature_references|.
350 The object may have "reverse_reference" property set - in that case
351 the referenced feature should reference this feature by
352 "reverse_reference" property.
303 value: The value to check. 353 value: The value to check.
304 """ 354 """
305 valid = True 355 valid = True
306 if expected_values and value not in expected_values: 356 if expected_values and value not in expected_values:
307 self._AddKeyError(key, 'Illegal value: "%s"' % value) 357 self._AddKeyError(key, 'Illegal value: "%s"' % value)
308 valid = False 358 valid = False
309 359
310 t = self._GetType(value) 360 t = self._GetType(value)
311 if expected_type and t is not expected_type: 361 if expected_type and t is not expected_type:
312 self._AddKeyError(key, 'Illegal value: "%s"' % value) 362 self._AddKeyError(key, 'Illegal value: "%s"' % value)
313 valid = False 363 valid = False
314 364
365 if feature_reference and not t in [str, unicode]:
366 self._AddKeyError(key, 'Illegal value for feature reference %s' % value)
367 valid = False
368
315 if not valid: 369 if not valid:
316 return None 370 return None
317 371
318 if enum_map: 372 if enum_map:
319 return enum_map[value] 373 return enum_map[value]
320 374
321 if t in [str, unicode]: 375 if t in [str, unicode]:
376 if feature_reference:
377 self.feature_references.append({
378 'value': str(value),
379 'key': key,
380 'reverse_reference': feature_reference.get('reverse_reference')})
322 return '"%s"' % str(value) 381 return '"%s"' % str(value)
323 if t is int: 382 if t is int:
324 return str(value) 383 return str(value)
325 if t is bool: 384 if t is bool:
326 return 'true' if value else 'false' 385 return 'true' if value else 'false'
327 assert False, 'Unsupported type: %s' % value 386 assert False, 'Unsupported type: %s' % value
328 387
329 def _ParseKey(self, key, value, grammar): 388 def _ParseKey(self, key, value, grammar):
330 """Parses the specific key according to the grammar rule for that key if it 389 """Parses the specific key according to the grammar rule for that key if it
331 is present in the json value. 390 is present in the json value.
332 key: The key to parse. 391 key: The key to parse.
333 value: The full value for this feature. 392 value: The full value for this feature.
334 grammar: The rule for the specific key. 393 grammar: The rule for the specific key.
335 """ 394 """
336 if key not in value: 395 if key not in value:
337 return 396 return
338 v = value[key] 397 v = value[key]
339 398
340 is_all = False 399 is_all = False
341 if v == 'all' and list in grammar and 'allow_all' in grammar[list]: 400 if v == 'all' and list in grammar and 'allow_all' in grammar[list]:
342 v = [] 401 v = []
343 is_all = True 402 is_all = True
344 403
404 if 'shared' in grammar and key in self.shared_values:
405 self._AddKeyError(key, 'Key can be set at most once per feature.')
406 return
407
345 value_type = self._GetType(v) 408 value_type = self._GetType(v)
346 if value_type not in grammar: 409 if value_type not in grammar:
347 self._AddKeyError(key, 'Illegal value: "%s"' % v) 410 self._AddKeyError(key, 'Illegal value: "%s"' % v)
348 return 411 return
349 412
350 expected = grammar[value_type] 413 expected = grammar[value_type]
351 expected_values = None 414 expected_values = None
352 enum_map = None 415 enum_map = None
353 if 'values' in expected: 416 if 'values' in expected:
354 expected_values = expected['values'] 417 expected_values = expected['values']
355 elif 'enum_map' in expected: 418 elif 'enum_map' in expected:
356 enum_map = expected['enum_map'] 419 enum_map = expected['enum_map']
357 expected_values = enum_map.keys() 420 expected_values = enum_map.keys()
358 421
359 if is_all: 422 if is_all:
360 v = copy.deepcopy(expected_values) 423 v = copy.deepcopy(expected_values)
361 424
362 expected_type = None 425 expected_type = None
363 if value_type is list and 'subtype' in expected: 426 if value_type is list and 'subtype' in expected:
364 expected_type = expected['subtype'] 427 expected_type = expected['subtype']
365 428
429 feature_reference = None
430 if 'feature_reference' in grammar:
431 feature_reference = {}
432 feature_reference['reverse_reference'] = grammar['feature_reference'].get(
433 'reverse_reference')
434
366 cpp_value = None 435 cpp_value = None
367 # If this value is a list, iterate over each entry and validate. Otherwise, 436 # If this value is a list, iterate over each entry and validate. Otherwise,
368 # validate the single value. 437 # validate the single value.
369 if value_type is list: 438 if value_type is list:
370 cpp_value = [] 439 cpp_value = []
371 for sub_value in v: 440 for sub_value in v:
372 cpp_sub_value = self._GetCheckedValue(key, expected_type, 441 cpp_sub_value = self._GetCheckedValue(key, expected_type,
373 expected_values, enum_map, 442 expected_values, enum_map,
443 feature_reference,
374 sub_value) 444 sub_value)
375 if cpp_sub_value: 445 if cpp_sub_value:
376 cpp_value.append(cpp_sub_value) 446 cpp_value.append(cpp_sub_value)
377 if cpp_value: 447 if cpp_value:
378 cpp_value = '{' + ','.join(cpp_value) + '}' 448 cpp_value = '{' + ','.join(cpp_value) + '}'
379 else: 449 else:
380 cpp_value = self._GetCheckedValue(key, expected_type, expected_values, 450 cpp_value = self._GetCheckedValue(key, expected_type, expected_values,
381 enum_map, v) 451 enum_map, feature_reference, v)
382 452
383 if cpp_value: 453 if cpp_value:
384 self.feature_values[key] = cpp_value 454 if 'shared' in grammar:
455 self.shared_values[key] = cpp_value
456 else:
457 self.feature_values[key] = cpp_value
385 elif key in self.feature_values: 458 elif key in self.feature_values:
386 # If the key is empty and this feature inherited a value from its parent, 459 # If the key is empty and this feature inherited a value from its parent,
387 # remove the inherited value. 460 # remove the inherited value.
388 del self.feature_values[key] 461 del self.feature_values[key]
389 462
390 def SetParent(self, parent): 463 def SetParent(self, parent):
391 """Sets the parent of this feature, and inherits all properties from that 464 """Sets the parent of this feature, and inherits all properties from that
392 parent. 465 parent.
393 """ 466 """
394 assert not self.feature_values, 'Parents must be set before parsing' 467 assert not self.feature_values, 'Parents must be set before parsing'
395 self.feature_values = copy.deepcopy(parent.feature_values) 468 self.feature_values = copy.deepcopy(parent.feature_values)
396 self.has_parent = True 469 self.has_parent = True
397 470
471 def SetSharedValues(self, values):
472 self.shared_values = values
473
398 def Parse(self, parsed_json): 474 def Parse(self, parsed_json):
399 """Parses the feature from the given json value.""" 475 """Parses the feature from the given json value."""
400 for key in parsed_json.keys(): 476 for key in parsed_json.keys():
401 if key not in FEATURE_GRAMMAR: 477 if key not in FEATURE_GRAMMAR:
402 self._AddKeyError(key, 'Unrecognized key') 478 self._AddKeyError(key, 'Unrecognized key')
403 for key, key_grammar in FEATURE_GRAMMAR.iteritems(): 479 for key, key_grammar in FEATURE_GRAMMAR.iteritems():
404 self._ParseKey(key, parsed_json, key_grammar) 480 self._ParseKey(key, parsed_json, key_grammar)
405 481
406 def Validate(self, feature_class): 482 def Validate(self, feature_class):
483 feature_values = self.GetAllFeatureValues()
407 for validator, error in (VALIDATION[feature_class] + VALIDATION['all']): 484 for validator, error in (VALIDATION[feature_class] + VALIDATION['all']):
408 if not validator(self.feature_values): 485 if not validator(feature_values):
409 self._AddError(error) 486 self._AddError(error)
410 487
488 def _GetCodeForFeatureValues(self, feature_values):
489 """ Gets the Code object for setting feature values for this object. """
490 c = Code()
491 for key in sorted(feature_values.keys()):
492 if key in IGNORED_KEYS:
493 continue;
494 c.Append('feature->set_%s(%s);' % (key, feature_values[key]))
495 return c
496
411 def GetCode(self, feature_class): 497 def GetCode(self, feature_class):
412 """Returns the Code object for generating this feature.""" 498 """Returns the Code object for generating this feature."""
413 c = Code() 499 c = Code()
414 c.Append('%s* feature = new %s();' % (feature_class, feature_class)) 500 c.Append('%s* feature = new %s();' % (feature_class, feature_class))
415 c.Append('feature->set_name("%s");' % self.name) 501 c.Append('feature->set_name("%s");' % self.name)
416 for key in sorted(self.feature_values.keys()): 502 c.Concat(self._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 503 return c
421 504
505 def AsParent(self):
506 """ Returns the feature values that should be inherited by children features
507 when this feature is set as parent.
508 """
509 return self
510
511 def GetAllFeatureValues(self):
512 """ Gets all values set for this feature. """
513 values = self.feature_values.copy()
514 values.update(self.shared_values)
515 return values
516
517 def ValidateFeatureReferences(self, features):
518 """ Validates feature values that reference another feature - the validation
519 cannot be done when individual features are compiled (like it is done for
520 other value types), as whole feature set is not know at that point.
521 """
522 for ref in self.feature_references:
523 # Validate that a feature referenced by this feature exists.
524 if not ref['value'] in features:
525 self._AddKeyError(ref['key'], '%s is not a feature.' % ref['value'])
526 return
527
528 # Verify that referenced feature references this feature, if reverse
529 # reference is required.
530 reverse_ref = ref['reverse_reference']
531 if reverse_ref:
532 referenced_feature_values = features[ref['value']].GetAllFeatureValues()
533 reverse_ref_value = referenced_feature_values.get(reverse_ref)
534 if (reverse_ref_value != ('"%s"' % self.name)):
535 self._AddKeyError(ref['key'],
536 'Referenced feature ("%s") should have "%s" set to "%s".' %
537 (ref['value'], reverse_ref, self.name))
538
539 def GetErrors(self):
540 return self.errors;
541
542 class ComplexFeature(Feature):
543 """ Complex feature - feature that is comprised of list of features.
544 Overall complex feature is available if any of contained
545 feature is available.
546 """
547 def __init__(self, name, shared_values):
Devlin 2016/11/21 16:55:53 self.shared_values should start empty, so no need
tbarzic 2016/11/21 23:49:47 Done.
548 Feature.__init__(self, name)
549 self.shared_values = shared_values
550 self.feature_list = []
551
552 def GetCode(self, feature_class):
553 c = Code()
554 c.Append('std::vector<Feature*> features;')
555 for f in self.feature_list:
556 c.Sblock('{')
557 c.Concat(f.GetCode(feature_class))
558 c.Append('features.push_back(feature);')
559 c.Eblock('}')
560 c.Append('ComplexFeature* feature(new ComplexFeature(&features));')
561 c.Append('feature->set_name("%s");' % self.name)
562 c.Concat(self._GetCodeForFeatureValues(self.GetAllFeatureValues()))
563 return c
564
565 def AsParent(self):
566 for p in self.feature_list:
567 if 'default_parent' in p.feature_values:
568 parent = p
569 break
570 assert parent, 'No default parent found for %s' % self.name
571 return parent
572
573 def ValidateFeatureReferences(self, available_features):
574 for feature in self.feature_list:
575 feature.ValidateFeatureReferences(available_features)
576
577 def GetAllFeatureValues(self):
578 return self.shared_values.copy()
579
580 def GetErrors(self):
581 errors = None
582 for feature in self.feature_list:
583 if not errors:
584 errors = []
585 errors.extend(feature.GetErrors())
586 return errors
587
422 class FeatureCompiler(object): 588 class FeatureCompiler(object):
423 """A compiler to load, parse, and generate C++ code for a number of 589 """A compiler to load, parse, and generate C++ code for a number of
424 features.json files.""" 590 features.json files."""
425 def __init__(self, chrome_root, source_files, feature_class, 591 def __init__(self, chrome_root, source_files, feature_class,
426 provider_class, out_root, out_base_filename): 592 provider_class, out_root, out_base_filename):
427 # See __main__'s ArgumentParser for documentation on these properties. 593 # See __main__'s ArgumentParser for documentation on these properties.
428 self._chrome_root = chrome_root 594 self._chrome_root = chrome_root
429 self._source_files = source_files 595 self._source_files = source_files
430 self._feature_class = feature_class 596 self._feature_class = feature_class
431 self._provider_class = provider_class 597 self._provider_class = provider_class
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
479 parent_name = feature_name[:sep] 645 parent_name = feature_name[:sep]
480 646
481 if sep == -1: 647 if sep == -1:
482 # TODO(devlin): It'd be kind of nice to be able to assert that the 648 # 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 649 # 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 650 # 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. 651 # probably just noparent them so that we can assert this.
486 # raise KeyError('Could not find parent "%s" for feature "%s".' % 652 # raise KeyError('Could not find parent "%s" for feature "%s".' %
487 # (parent_name, feature_name)) 653 # (parent_name, feature_name))
488 return None 654 return None
489 parent_value = self._features[parent_name] 655 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 656
499 def _CompileFeature(self, feature_name, feature_value): 657 def _CompileFeature(self, feature_name, feature_value):
500 """Parses a single feature.""" 658 """Parses a single feature."""
501 if 'nocompile' in feature_value: 659 if 'nocompile' in feature_value:
502 assert feature_value['nocompile'], ( 660 assert feature_value['nocompile'], (
503 'nocompile should only be true; otherwise omit this key.') 661 'nocompile should only be true; otherwise omit this key.')
504 return 662 return
505 663
506 def parse_and_validate(name, value, parent): 664 def parse_and_validate(name, value, parent, shared_values):
507 try: 665 try:
508 feature = Feature(name) 666 feature = Feature(name)
509 if parent: 667 if parent:
510 feature.SetParent(parent) 668 feature.SetParent(parent)
669 feature.SetSharedValues(shared_values)
Devlin 2016/11/21 16:55:53 Ideally, this should be handled as part of feature
tbarzic 2016/11/21 23:49:47 parent might not be set
511 feature.Parse(value) 670 feature.Parse(value)
512 feature.Validate(self._feature_class) 671 feature.Validate(self._feature_class)
513 return feature 672 return feature
514 except: 673 except:
515 print('Failure to parse feature "%s"' % feature_name) 674 print('Failure to parse feature "%s"' % feature_name)
516 raise 675 raise
517 676
518 parent = self._FindParent(feature_name, feature_value) 677 parent = self._FindParent(feature_name, feature_value)
678 shared_values = copy.deepcopy(parent.shared_values) if parent else {}
679
519 # Handle complex features, which are lists of simple features. 680 # Handle complex features, which are lists of simple features.
520 if type(feature_value) is list: 681 if type(feature_value) is list:
521 feature_list = [] 682 feature = ComplexFeature(feature_name, shared_values)
683
522 # This doesn't handle nested complex features. I think that's probably for 684 # This doesn't handle nested complex features. I think that's probably for
523 # the best. 685 # the best.
524 for v in feature_value: 686 for v in feature_value:
525 feature_list.append(parse_and_validate(feature_name, v, parent)) 687 feature.feature_list.append(
526 self._features[feature_name] = feature_list 688 parse_and_validate(feature_name, v, parent, feature.shared_values))
527 return 689 self._features[feature_name] = feature
528 690 else:
529 self._features[feature_name] = parse_and_validate( 691 self._features[feature_name] = parse_and_validate(
530 feature_name, feature_value, parent) 692 feature_name, feature_value, parent, shared_values)
531 693
532 def Compile(self): 694 def Compile(self):
533 """Parses all features after loading the input files.""" 695 """Parses all features after loading the input files."""
534 self._Load(); 696 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 # Validate values that reference feature
Devlin 2016/11/21 16:55:53 Then here def _FinalValidation(self): for name,
tbarzic 2016/11/21 23:49:47 Done.
701 for key in self._features:
702 self._features[key].ValidateFeatureReferences(self._features)
538 703
539 def Render(self): 704 def Render(self):
540 """Returns the Code object for the body of the .cc file, which handles the 705 """Returns the Code object for the body of the .cc file, which handles the
541 initialization of all features.""" 706 initialization of all features."""
542 c = Code() 707 c = Code()
543 c.Append('%s::%s() {' % (self._provider_class, self._provider_class)) 708 c.Append('%s::%s() {' % (self._provider_class, self._provider_class))
544 c.Sblock() 709 c.Sblock()
545 for k in sorted(self._features.keys()): 710 for k in sorted(self._features.keys()):
546 c.Sblock('{') 711 c.Sblock('{')
547 feature = self._features[k] 712 feature = self._features[k]
548 if type(feature) is list: 713 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) 714 c.Append('AddFeature("%s", feature);' % k)
560 c.Eblock('}') 715 c.Eblock('}')
561 c.Eblock('}') 716 c.Eblock('}')
562 return c 717 return c
563 718
564 def Write(self): 719 def Write(self):
565 """Writes the output.""" 720 """Writes the output."""
566 header_file_path = self._out_base_filename + '.h' 721 header_file_path = self._out_base_filename + '.h'
567 cc_file_path = self._out_base_filename + '.cc' 722 cc_file_path = self._out_base_filename + '.cc'
568 substitutions = ({ 723 substitutions = ({
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
611 parser.add_argument('source_files', type=str, nargs='+', 766 parser.add_argument('source_files', type=str, nargs='+',
612 help='The source features.json files') 767 help='The source features.json files')
613 args = parser.parse_args() 768 args = parser.parse_args()
614 if args.feature_class not in FEATURE_CLASSES: 769 if args.feature_class not in FEATURE_CLASSES:
615 raise NameError('Unknown feature class: %s' % args.feature_class) 770 raise NameError('Unknown feature class: %s' % args.feature_class)
616 c = FeatureCompiler(args.chrome_root, args.source_files, args.feature_class, 771 c = FeatureCompiler(args.chrome_root, args.source_files, args.feature_class,
617 args.provider_class, args.out_root, 772 args.provider_class, args.out_root,
618 args.out_base_filename) 773 args.out_base_filename)
619 c.Compile() 774 c.Compile()
620 c.Write() 775 c.Write()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698