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 3# found in the LICENSE file. 4 5from __future__ import print_function 6 7import argparse 8import copy 9from datetime import datetime 10from functools import partial 11import os 12import re 13import sys 14 15from code import Code 16import json_parse 17 18# The template for the header file of the generated FeatureProvider. 19HEADER_FILE_TEMPLATE = """ 20// Copyright %(year)s The Chromium Authors. All rights reserved. 21// Use of this source code is governed by a BSD-style license that can be 22// found in the LICENSE file. 23 24// GENERATED FROM THE FEATURES FILE: 25// %(source_files)s 26// DO NOT EDIT. 27 28#ifndef %(header_guard)s 29#define %(header_guard)s 30 31namespace extensions { 32class FeatureProvider; 33 34void %(method_name)s(FeatureProvider* provider); 35 36} // namespace extensions 37 38#endif // %(header_guard)s 39""" 40 41# The beginning of the .cc file for the generated FeatureProvider. 42CC_FILE_BEGIN = """ 43// Copyright %(year)s The Chromium Authors. All rights reserved. 44// Use of this source code is governed by a BSD-style license that can be 45// found in the LICENSE file. 46 47// GENERATED FROM THE FEATURES FILE: 48// %(source_files)s 49// DO NOT EDIT. 50 51#include "%(header_file_path)s" 52 53#include "extensions/common/features/complex_feature.h" 54#include "extensions/common/features/feature_provider.h" 55#include "extensions/common/features/manifest_feature.h" 56#include "extensions/common/features/permission_feature.h" 57 58namespace extensions { 59 60void %(method_name)s(FeatureProvider* provider) { 61""" 62 63# The end of the .cc file for the generated FeatureProvider. 64CC_FILE_END = """ 65} 66 67} // namespace extensions 68""" 69 70# Returns true if the list 'l' only contains strings that are a hex-encoded SHA1 71# hashes. 72def ListContainsOnlySha1Hashes(l): 73 return len(list(filter(lambda s: not re.match("^[A-F0-9]{40}$", s), l))) == 0 74 75# A "grammar" for what is and isn't allowed in the features.json files. This 76# grammar has to list all possible keys and the requirements for each. The 77# format of each entry is: 78# 'key': { 79# allowed_type_1: optional_properties, 80# allowed_type_2: optional_properties, 81# } 82# |allowed_types| are the types of values that can be used for a given key. The 83# possible values are list, str, bool, and int. 84# |optional_properties| provide more restrictions on the given type. The options 85# are: 86# 'subtype': Only applicable for lists. If provided, this enforces that each 87# entry in the list is of the specified type. 88# 'enum_map': A map of strings to C++ enums. When the compiler sees the given 89# enum string, it will replace it with the C++ version in the 90# compiled code. For instance, if a feature specifies 91# 'channel': 'stable', the generated C++ will assign 92# version_info::Channel::STABLE to channel. The keys in this map 93# also serve as a list all of possible values. 94# 'allow_all': Only applicable for lists. If present, this will check for 95# a value of "all" for a list value, and will replace it with 96# the collection of all possible values. For instance, if a 97# feature specifies 'contexts': 'all', the generated C++ will 98# assign the list of Feature::BLESSED_EXTENSION_CONTEXT, 99# Feature::BLESSED_WEB_PAGE_CONTEXT et al for contexts. If not 100# specified, defaults to false. 101# 'allow_empty': Only applicable for lists. Whether an empty list is a valid 102# value. If omitted, empty lists are prohibited. 103# 'validators': A list of (function, str) pairs with a function to run on the 104# value for a feature. Validators allow for more flexible or 105# one-off style validation than just what's in the grammar (such 106# as validating the content of a string). The validator function 107# should return True if the value is valid, and False otherwise. 108# If the value is invalid, the specified error will be added for 109# that key. 110# 'values': A list of all possible allowed values for a given key. 111# 'shared': Boolean that, if set, ensures that only one of the associated 112# features has the feature property set. Used primarily for complex 113# features - for simple features, there is always at most one feature 114# setting an option. 115# If a type definition does not have any restrictions (beyond the type itself), 116# an empty definition ({}) is used. 117FEATURE_GRAMMAR = ( 118 { 119 'alias': { 120 str: {}, 121 'shared': True 122 }, 123 'blacklist': { 124 list: { 125 'subtype': str, 126 'validators': [ 127 (ListContainsOnlySha1Hashes, 128 'list should only have hex-encoded SHA1 hashes of extension ids') 129 ] 130 } 131 }, 132 'channel': { 133 str: { 134 'enum_map': { 135 'trunk': 'version_info::Channel::UNKNOWN', 136 'canary': 'version_info::Channel::CANARY', 137 'dev': 'version_info::Channel::DEV', 138 'beta': 'version_info::Channel::BETA', 139 'stable': 'version_info::Channel::STABLE', 140 } 141 } 142 }, 143 'command_line_switch': { 144 str: {} 145 }, 146 'component_extensions_auto_granted': { 147 bool: {} 148 }, 149 'contexts': { 150 list: { 151 'enum_map': { 152 'blessed_extension': 'Feature::BLESSED_EXTENSION_CONTEXT', 153 'blessed_web_page': 'Feature::BLESSED_WEB_PAGE_CONTEXT', 154 'content_script': 'Feature::CONTENT_SCRIPT_CONTEXT', 155 'lock_screen_extension': 'Feature::LOCK_SCREEN_EXTENSION_CONTEXT', 156 'web_page': 'Feature::WEB_PAGE_CONTEXT', 157 'webui': 'Feature::WEBUI_CONTEXT', 158 'webui_untrusted': 'Feature::WEBUI_UNTRUSTED_CONTEXT', 159 'unblessed_extension': 'Feature::UNBLESSED_EXTENSION_CONTEXT', 160 }, 161 'allow_all': True 162 }, 163 }, 164 'default_parent': { 165 bool: {'values': [True]} 166 }, 167 'dependencies': { 168 list: { 169 # We allow an empty list of dependencies for child features that want 170 # to override their parents' dependency set. 171 'allow_empty': True, 172 'subtype': str 173 } 174 }, 175 'disallow_for_service_workers': { 176 bool: {} 177 }, 178 'extension_types': { 179 list: { 180 'enum_map': { 181 'extension': 'Manifest::TYPE_EXTENSION', 182 'hosted_app': 'Manifest::TYPE_HOSTED_APP', 183 'legacy_packaged_app': 'Manifest::TYPE_LEGACY_PACKAGED_APP', 184 'platform_app': 'Manifest::TYPE_PLATFORM_APP', 185 'shared_module': 'Manifest::TYPE_SHARED_MODULE', 186 'theme': 'Manifest::TYPE_THEME', 187 'login_screen_extension': 'Manifest::TYPE_LOGIN_SCREEN_EXTENSION', 188 }, 189 'allow_all': True 190 }, 191 }, 192 'location': { 193 str: { 194 'enum_map': { 195 'component': 'SimpleFeature::COMPONENT_LOCATION', 196 'external_component': 'SimpleFeature::EXTERNAL_COMPONENT_LOCATION', 197 'policy': 'SimpleFeature::POLICY_LOCATION', 198 'unpacked': 'SimpleFeature::UNPACKED_LOCATION', 199 } 200 } 201 }, 202 'internal': { 203 bool: {'values': [True]} 204 }, 205 'matches': { 206 list: {'subtype': str} 207 }, 208 'max_manifest_version': { 209 int: {'values': [1, 2]} 210 }, 211 'min_manifest_version': { 212 int: {'values': [2, 3]} 213 }, 214 'noparent': { 215 bool: {'values': [True]} 216 }, 217 'platforms': { 218 list: { 219 'enum_map': { 220 'chromeos': 'Feature::CHROMEOS_PLATFORM', 221 'linux': 'Feature::LINUX_PLATFORM', 222 'bsd': 'Feature::LINUX_PLATFORM', 223 'mac': 'Feature::MACOSX_PLATFORM', 224 'win': 'Feature::WIN_PLATFORM', 225 } 226 } 227 }, 228 'session_types': { 229 list: { 230 'enum_map': { 231 'regular': 'FeatureSessionType::REGULAR', 232 'kiosk': 'FeatureSessionType::KIOSK', 233 'kiosk.autolaunched': 'FeatureSessionType::AUTOLAUNCHED_KIOSK', 234 } 235 } 236 }, 237 'source': { 238 str: {}, 239 'shared': True 240 }, 241 'whitelist': { 242 list: { 243 'subtype': str, 244 'validators': [ 245 (ListContainsOnlySha1Hashes, 246 'list should only have hex-encoded SHA1 hashes of extension ids') 247 ] 248 } 249 }, 250 }) 251 252FEATURE_TYPES = ['APIFeature', 'BehaviorFeature', 253 'ManifestFeature', 'PermissionFeature'] 254 255def HasProperty(property_name, value): 256 return property_name in value 257 258def HasAtLeastOneProperty(property_names, value): 259 return any([HasProperty(name, value) for name in property_names]) 260 261def DoesNotHaveAllProperties(property_names, value): 262 return not all([HasProperty(name, value) for name in property_names]) 263 264def DoesNotHaveProperty(property_name, value): 265 return property_name not in value 266 267def IsFeatureCrossReference(property_name, reverse_property_name, feature, 268 all_features): 269 """ Verifies that |property_name| on |feature| references a feature that 270 references |feature| back using |reverse_property_name| property. 271 |property_name| and |reverse_property_name| are expected to have string 272 values. 273 """ 274 value = feature.GetValue(property_name) 275 if not value: 276 return True 277 # String property values will be wrapped in "", strip those. 278 value_regex = re.compile('^"(.+)"$') 279 parsed_value = value_regex.match(value) 280 assert parsed_value, ( 281 'IsFeatureCrossReference should only be used on unicode properties') 282 283 referenced_feature = all_features.get(parsed_value.group(1)) 284 if not referenced_feature: 285 return False 286 reverse_reference_value = referenced_feature.GetValue(reverse_property_name) 287 if not reverse_reference_value: 288 return False 289 # Don't validate reverse reference value for child features - chances are that 290 # the value was inherited from a feature parent, in which case it won't match 291 # current feature name. 292 if feature.has_parent: 293 return True 294 return reverse_reference_value == ('"%s"' % feature.name) 295 296SIMPLE_FEATURE_CPP_CLASSES = ({ 297 'APIFeature': 'SimpleFeature', 298 'ManifestFeature': 'ManifestFeature', 299 'PermissionFeature': 'PermissionFeature', 300 'BehaviorFeature': 'SimpleFeature', 301}) 302 303VALIDATION = ({ 304 'all': [ 305 (partial(HasAtLeastOneProperty, ['channel', 'dependencies']), 306 'Features must specify either a channel or dependencies'), 307 ], 308 'APIFeature': [ 309 (partial(HasProperty, 'contexts'), 310 'APIFeatures must specify at least one context'), 311 (partial(DoesNotHaveAllProperties, ['alias', 'source']), 312 'Features cannot specify both alias and source.') 313 ], 314 'ManifestFeature': [ 315 (partial(HasProperty, 'extension_types'), 316 'ManifestFeatures must specify at least one extension type'), 317 (partial(DoesNotHaveProperty, 'contexts'), 318 'ManifestFeatures do not support contexts.'), 319 (partial(DoesNotHaveProperty, 'alias'), 320 'ManifestFeatures do not support alias.'), 321 (partial(DoesNotHaveProperty, 'source'), 322 'ManifestFeatures do not support source.'), 323 ], 324 'BehaviorFeature': [ 325 (partial(DoesNotHaveProperty, 'alias'), 326 'BehaviorFeatures do not support alias.'), 327 (partial(DoesNotHaveProperty, 'source'), 328 'BehaviorFeatures do not support source.'), 329 ], 330 'PermissionFeature': [ 331 (partial(HasProperty, 'extension_types'), 332 'PermissionFeatures must specify at least one extension type'), 333 (partial(DoesNotHaveProperty, 'contexts'), 334 'PermissionFeatures do not support contexts.'), 335 (partial(DoesNotHaveProperty, 'alias'), 336 'PermissionFeatures do not support alias.'), 337 (partial(DoesNotHaveProperty, 'source'), 338 'PermissionFeatures do not support source.'), 339 ], 340}) 341 342FINAL_VALIDATION = ({ 343 'all': [], 344 'APIFeature': [ 345 (partial(IsFeatureCrossReference, 'alias', 'source'), 346 'A feature alias property should reference a feature whose source ' 347 'property references it back.'), 348 (partial(IsFeatureCrossReference, 'source', 'alias'), 349 'A feature source property should reference a feature whose alias ' 350 'property references it back.') 351 352 ], 353 'ManifestFeature': [], 354 'BehaviorFeature': [], 355 'PermissionFeature': [] 356}) 357 358# These keys are used to find the parents of different features, but are not 359# compiled into the features themselves. 360IGNORED_KEYS = ['default_parent'] 361 362# By default, if an error is encountered, assert to stop the compilation. This 363# can be disabled for testing. 364ENABLE_ASSERTIONS = True 365 366def GetCodeForFeatureValues(feature_values): 367 """ Gets the Code object for setting feature values for this object. """ 368 c = Code() 369 for key in sorted(feature_values.keys()): 370 if key in IGNORED_KEYS: 371 continue; 372 373 # TODO(devlin): Remove this hack as part of 842387. 374 set_key = key 375 if key == "whitelist": 376 set_key = "allowlist" 377 elif key == "blacklist": 378 set_key = "blocklist" 379 380 c.Append('feature->set_%s(%s);' % (set_key, feature_values[key])) 381 return c 382 383class Feature(object): 384 """A representation of a single simple feature that can handle all parsing, 385 validation, and code generation. 386 """ 387 def __init__(self, name): 388 self.name = name 389 self.has_parent = False 390 self.errors = [] 391 self.feature_values = {} 392 self.shared_values = {} 393 394 def _GetType(self, value): 395 """Returns the type of the given value. 396 """ 397 # For Py3 compatibility we use str in the grammar and treat unicode as str 398 # in Py2. 399 if sys.version_info.major == 2 and type(value) is unicode: 400 return str 401 402 return type(value) 403 404 def AddError(self, error): 405 """Adds an error to the feature. If ENABLE_ASSERTIONS is active, this will 406 also assert to stop the compilation process (since errors should never be 407 found in production). 408 """ 409 self.errors.append(error) 410 if ENABLE_ASSERTIONS: 411 assert False, error 412 413 def _AddKeyError(self, key, error): 414 """Adds an error relating to a particular key in the feature. 415 """ 416 self.AddError('Error parsing feature "%s" at key "%s": %s' % 417 (self.name, key, error)) 418 419 def _GetCheckedValue(self, key, expected_type, expected_values, 420 enum_map, value): 421 """Returns a string to be used in the generated C++ code for a given key's 422 python value, or None if the value is invalid. For example, if the python 423 value is True, this returns 'true', for a string foo, this returns "foo", 424 and for an enum, this looks up the C++ definition in the enum map. 425 key: The key being parsed. 426 expected_type: The expected type for this value, or None if any type is 427 allowed. 428 expected_values: The list of allowed values for this value, or None if any 429 value is allowed. 430 enum_map: The map from python value -> cpp value for all allowed values, 431 or None if no special mapping should be made. 432 value: The value to check. 433 """ 434 valid = True 435 if expected_values and value not in expected_values: 436 self._AddKeyError(key, 'Illegal value: "%s"' % value) 437 valid = False 438 439 t = self._GetType(value) 440 if expected_type and t is not expected_type: 441 self._AddKeyError(key, 'Illegal value: "%s"' % value) 442 valid = False 443 444 if not valid: 445 return None 446 447 if enum_map: 448 return enum_map[value] 449 450 if t is str: 451 return '"%s"' % str(value) 452 if t is int: 453 return str(value) 454 if t is bool: 455 return 'true' if value else 'false' 456 assert False, 'Unsupported type: %s' % value 457 458 def _ParseKey(self, key, value, shared_values, grammar): 459 """Parses the specific key according to the grammar rule for that key if it 460 is present in the json value. 461 key: The key to parse. 462 value: The full value for this feature. 463 shared_values: Set of shared vfalues associated with this feature. 464 grammar: The rule for the specific key. 465 """ 466 if key not in value: 467 return 468 v = value[key] 469 470 is_all = False 471 if v == 'all' and list in grammar and 'allow_all' in grammar[list]: 472 assert grammar[list]['allow_all'], '`allow_all` only supports `True`.' 473 v = [] 474 is_all = True 475 476 if 'shared' in grammar and key in shared_values: 477 self._AddKeyError(key, 'Key can be set at most once per feature.') 478 return 479 480 value_type = self._GetType(v) 481 if value_type not in grammar: 482 self._AddKeyError(key, 'Illegal value: "%s"' % v) 483 return 484 485 if value_type is list and not is_all and len(v) == 0: 486 if 'allow_empty' in grammar[list]: 487 assert grammar[list]['allow_empty'], \ 488 '`allow_empty` only supports `True`.' 489 else: 490 self._AddKeyError(key, 'List must specify at least one element.') 491 return 492 493 expected = grammar[value_type] 494 expected_values = None 495 enum_map = None 496 if 'values' in expected: 497 expected_values = expected['values'] 498 elif 'enum_map' in expected: 499 enum_map = expected['enum_map'] 500 expected_values = list(enum_map) 501 502 if is_all: 503 v = copy.deepcopy(expected_values) 504 505 expected_type = None 506 if value_type is list and 'subtype' in expected: 507 expected_type = expected['subtype'] 508 509 cpp_value = None 510 # If this value is a list, iterate over each entry and validate. Otherwise, 511 # validate the single value. 512 if value_type is list: 513 cpp_value = [] 514 for sub_value in v: 515 cpp_sub_value = self._GetCheckedValue(key, expected_type, 516 expected_values, enum_map, 517 sub_value) 518 if cpp_sub_value: 519 cpp_value.append(cpp_sub_value) 520 if cpp_value: 521 cpp_value = '{' + ','.join(cpp_value) + '}' 522 else: 523 cpp_value = self._GetCheckedValue(key, expected_type, expected_values, 524 enum_map, v) 525 526 if 'validators' in expected: 527 validators = expected['validators'] 528 for validator, error in validators: 529 if not validator(v): 530 self._AddKeyError(key, error) 531 532 if cpp_value: 533 if 'shared' in grammar: 534 shared_values[key] = cpp_value 535 else: 536 self.feature_values[key] = cpp_value 537 elif key in self.feature_values: 538 # If the key is empty and this feature inherited a value from its parent, 539 # remove the inherited value. 540 del self.feature_values[key] 541 542 def SetParent(self, parent): 543 """Sets the parent of this feature, and inherits all properties from that 544 parent. 545 """ 546 assert not self.feature_values, 'Parents must be set before parsing' 547 self.feature_values = copy.deepcopy(parent.feature_values) 548 self.has_parent = True 549 550 def SetSharedValues(self, values): 551 self.shared_values = values 552 553 def Parse(self, parsed_json, shared_values): 554 """Parses the feature from the given json value.""" 555 for key in parsed_json.keys(): 556 if key not in FEATURE_GRAMMAR: 557 self._AddKeyError(key, 'Unrecognized key') 558 for key, key_grammar in FEATURE_GRAMMAR.items(): 559 self._ParseKey(key, parsed_json, shared_values, key_grammar) 560 561 def Validate(self, feature_type, shared_values): 562 feature_values = self.feature_values.copy() 563 feature_values.update(shared_values) 564 for validator, error in (VALIDATION[feature_type] + VALIDATION['all']): 565 if not validator(feature_values): 566 self.AddError(error) 567 568 def GetCode(self, feature_type): 569 """Returns the Code object for generating this feature.""" 570 c = Code() 571 cpp_feature_class = SIMPLE_FEATURE_CPP_CLASSES[feature_type] 572 c.Append('%s* feature = new %s();' % (cpp_feature_class, cpp_feature_class)) 573 c.Append('feature->set_name("%s");' % self.name) 574 c.Concat(GetCodeForFeatureValues(self.GetAllFeatureValues())) 575 return c 576 577 def AsParent(self): 578 """ Returns the feature values that should be inherited by children features 579 when this feature is set as parent. 580 """ 581 return self 582 583 def GetValue(self, key): 584 """ Gets feature value for the specified key """ 585 value = self.feature_values.get(key) 586 return value if value else self.shared_values.get(key) 587 588 def GetAllFeatureValues(self): 589 """ Gets all values set for this feature. """ 590 values = self.feature_values.copy() 591 values.update(self.shared_values) 592 return values 593 594 def GetErrors(self): 595 return self.errors; 596 597class ComplexFeature(Feature): 598 """ Complex feature - feature that is comprised of list of features. 599 Overall complex feature is available if any of contained 600 feature is available. 601 """ 602 def __init__(self, name): 603 Feature.__init__(self, name) 604 self.feature_list = [] 605 606 def GetCode(self, feature_type): 607 c = Code() 608 c.Append('std::vector<Feature*> features;') 609 for f in self.feature_list: 610 # Sanity check that components of complex features have no shared values 611 # set. 612 assert not f.shared_values 613 c.Sblock('{') 614 c.Concat(f.GetCode(feature_type)) 615 c.Append('features.push_back(feature);') 616 c.Eblock('}') 617 c.Append('ComplexFeature* feature(new ComplexFeature(&features));') 618 c.Append('feature->set_name("%s");' % self.name) 619 c.Concat(GetCodeForFeatureValues(self.shared_values)) 620 return c 621 622 def AsParent(self): 623 parent = None 624 for p in self.feature_list: 625 if 'default_parent' in p.feature_values: 626 parent = p 627 break 628 assert parent, 'No default parent found for %s' % self.name 629 return parent 630 631 def GetErrors(self): 632 errors = copy.copy(self.errors) 633 for feature in self.feature_list: 634 errors.extend(feature.GetErrors()) 635 return errors 636 637class FeatureCompiler(object): 638 """A compiler to load, parse, and generate C++ code for a number of 639 features.json files.""" 640 def __init__(self, chrome_root, source_files, feature_type, 641 method_name, out_root, out_base_filename): 642 # See __main__'s ArgumentParser for documentation on these properties. 643 self._chrome_root = chrome_root 644 self._source_files = source_files 645 self._feature_type = feature_type 646 self._method_name = method_name 647 self._out_root = out_root 648 self._out_base_filename = out_base_filename 649 650 # The json value for the feature files. 651 self._json = {} 652 # The parsed features. 653 self._features = {} 654 655 def Load(self): 656 """Loads and parses the source from each input file and puts the result in 657 self._json.""" 658 for f in self._source_files: 659 abs_source_file = os.path.join(self._chrome_root, f) 660 try: 661 with open(abs_source_file, 'r') as f: 662 f_json = json_parse.Parse(f.read()) 663 except: 664 print('FAILED: Exception encountered while loading "%s"' % 665 abs_source_file) 666 raise 667 dupes = set(f_json) & set(self._json) 668 assert not dupes, 'Duplicate keys found: %s' % list(dupes) 669 self._json.update(f_json) 670 671 def _FindParent(self, feature_name, feature_value): 672 """Checks to see if a feature has a parent. If it does, returns the 673 parent.""" 674 no_parent = False 675 if type(feature_value) is list: 676 no_parent_values = ['noparent' in v for v in feature_value] 677 no_parent = all(no_parent_values) 678 assert no_parent or not any(no_parent_values), ( 679 '"%s:" All child features must contain the same noparent value' % 680 feature_name) 681 else: 682 no_parent = 'noparent' in feature_value 683 sep = feature_name.rfind('.') 684 if sep == -1 or no_parent: 685 return None 686 687 parent_name = feature_name[:sep] 688 while sep != -1 and parent_name not in self._features: 689 # This recursion allows for a feature to have a parent that isn't a direct 690 # ancestor. For instance, we could have feature 'alpha', and feature 691 # 'alpha.child.child', where 'alpha.child.child' inherits from 'alpha'. 692 # TODO(devlin): Is this useful? Or logical? 693 sep = feature_name.rfind('.', 0, sep) 694 parent_name = feature_name[:sep] 695 696 if sep == -1: 697 # TODO(devlin): It'd be kind of nice to be able to assert that the 698 # deduced parent name is in our features, but some dotted features don't 699 # have parents and also don't have noparent, e.g. system.cpu. We should 700 # probably just noparent them so that we can assert this. 701 # raise KeyError('Could not find parent "%s" for feature "%s".' % 702 # (parent_name, feature_name)) 703 return None 704 return self._features[parent_name].AsParent() 705 706 def _CompileFeature(self, feature_name, feature_value): 707 """Parses a single feature.""" 708 if 'nocompile' in feature_value: 709 assert feature_value['nocompile'], ( 710 'nocompile should only be true; otherwise omit this key.') 711 return 712 713 def parse_and_validate(name, value, parent, shared_values): 714 try: 715 feature = Feature(name) 716 if parent: 717 feature.SetParent(parent) 718 feature.Parse(value, shared_values) 719 feature.Validate(self._feature_type, shared_values) 720 return feature 721 except: 722 print('Failure to parse feature "%s"' % feature_name) 723 raise 724 725 parent = self._FindParent(feature_name, feature_value) 726 shared_values = {} 727 728 # Handle complex features, which are lists of simple features. 729 if type(feature_value) is list: 730 feature = ComplexFeature(feature_name) 731 732 # This doesn't handle nested complex features. I think that's probably for 733 # the best. 734 for v in feature_value: 735 feature.feature_list.append( 736 parse_and_validate(feature_name, v, parent, shared_values)) 737 self._features[feature_name] = feature 738 else: 739 self._features[feature_name] = parse_and_validate( 740 feature_name, feature_value, parent, shared_values) 741 742 # Apply parent shared values at the end to enable child features to 743 # override parent shared value - if parent shared values are added to 744 # shared value set before a child feature is parsed, the child feature 745 # overriding shared values set by its parent would cause an error due to 746 # shared values being set twice. 747 final_shared_values = copy.deepcopy(parent.shared_values) if parent else {} 748 final_shared_values.update(shared_values) 749 self._features[feature_name].SetSharedValues(final_shared_values) 750 751 def _FinalValidation(self): 752 validators = FINAL_VALIDATION['all'] + FINAL_VALIDATION[self._feature_type] 753 for name, feature in self._features.items(): 754 for validator, error in validators: 755 if not validator(feature, self._features): 756 feature.AddError(error) 757 758 def Compile(self): 759 """Parses all features after loading the input files.""" 760 # Iterate over in sorted order so that parents come first. 761 for k in sorted(self._json.keys()): 762 self._CompileFeature(k, self._json[k]) 763 self._FinalValidation() 764 765 def Render(self): 766 """Returns the Code object for the body of the .cc file, which handles the 767 initialization of all features.""" 768 c = Code() 769 c.Sblock() 770 for k in sorted(self._features.keys()): 771 c.Sblock('{') 772 feature = self._features[k] 773 c.Concat(feature.GetCode(self._feature_type)) 774 c.Append('provider->AddFeature("%s", feature);' % k) 775 c.Eblock('}') 776 c.Eblock() 777 return c 778 779 def Write(self): 780 """Writes the output.""" 781 header_file = self._out_base_filename + '.h' 782 cc_file = self._out_base_filename + '.cc' 783 784 include_file_root = self._out_root 785 GEN_DIR_PREFIX = 'gen/' 786 if include_file_root.startswith(GEN_DIR_PREFIX) and len(include_file_root) >= len(GEN_DIR_PREFIX): 787 include_file_root = include_file_root[len(GEN_DIR_PREFIX):] 788 else: 789 include_file_root = '' 790 if include_file_root: 791 header_file_path = '%s/%s' % (include_file_root, header_file) 792 else: 793 header_file_path = header_file 794 cc_file_path = '%s/%s' % (include_file_root, cc_file) 795 796 substitutions = ({ 797 'header_file_path': header_file_path, 798 'header_guard': (header_file_path.replace('/', '_'). 799 replace('.', '_').upper()), 800 'method_name': self._method_name, 801 'source_files': str(self._source_files), 802 'year': str(datetime.now().year) 803 }) 804 if not os.path.exists(self._out_root): 805 os.makedirs(self._out_root) 806 # Write the .h file. 807 with open(os.path.join(self._out_root, header_file), 'w') as f: 808 header_file = Code() 809 header_file.Append(HEADER_FILE_TEMPLATE) 810 header_file.Substitute(substitutions) 811 f.write(header_file.Render().strip()) 812 # Write the .cc file. 813 with open(os.path.join(self._out_root, cc_file), 'w') as f: 814 cc_file = Code() 815 cc_file.Append(CC_FILE_BEGIN) 816 cc_file.Substitute(substitutions) 817 cc_file.Concat(self.Render()) 818 cc_end = Code() 819 cc_end.Append(CC_FILE_END) 820 cc_end.Substitute(substitutions) 821 cc_file.Concat(cc_end) 822 f.write(cc_file.Render().strip()) 823 824if __name__ == '__main__': 825 parser = argparse.ArgumentParser(description='Compile json feature files') 826 parser.add_argument('chrome_root', type=str, 827 help='The root directory of the chrome checkout') 828 parser.add_argument( 829 'feature_type', type=str, 830 help='The name of the class to use in feature generation ' + 831 '(e.g. APIFeature, PermissionFeature)') 832 parser.add_argument('method_name', type=str, 833 help='The name of the method to populate the provider') 834 parser.add_argument('out_root', type=str, 835 help='The root directory to generate the C++ files into') 836 parser.add_argument( 837 'out_base_filename', type=str, 838 help='The base filename for the C++ files (.h and .cc will be appended)') 839 parser.add_argument('source_files', type=str, nargs='+', 840 help='The source features.json files') 841 args = parser.parse_args() 842 if args.feature_type not in FEATURE_TYPES: 843 raise NameError('Unknown feature type: %s' % args.feature_type) 844 c = FeatureCompiler(args.chrome_root, args.source_files, args.feature_type, 845 args.method_name, args.out_root, 846 args.out_base_filename) 847 c.Load() 848 c.Compile() 849 c.Write() 850