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

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

Issue 2494653005: Support API aliases (Closed)
Patch Set: Fix issue with child features not being able to override parent shared values Created 4 years 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 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
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
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
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
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
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
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()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698