| # Copyright 2016 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import argparse |
| import copy |
| from datetime import datetime |
| from functools import partial |
| import os |
| import re |
| |
| from code import Code |
| import json_parse |
| |
| # The template for the header file of the generated FeatureProvider. |
| HEADER_FILE_TEMPLATE = """ |
| // Copyright %(year)s The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // GENERATED FROM THE FEATURES FILE: |
| // %(source_files)s |
| // DO NOT EDIT. |
| |
| #ifndef %(header_guard)s |
| #define %(header_guard)s |
| |
| namespace extensions { |
| class FeatureProvider; |
| |
| void %(method_name)s(FeatureProvider* provider); |
| |
| } // namespace extensions |
| |
| #endif // %(header_guard)s |
| """ |
| |
| # The beginning of the .cc file for the generated FeatureProvider. |
| CC_FILE_BEGIN = """ |
| // Copyright %(year)s The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // GENERATED FROM THE FEATURES FILE: |
| // %(source_files)s |
| // DO NOT EDIT. |
| |
| #include "%(header_file_path)s" |
| |
| #include "extensions/common/features/complex_feature.h" |
| #include "extensions/common/features/feature_provider.h" |
| #include "extensions/common/features/manifest_feature.h" |
| #include "extensions/common/features/permission_feature.h" |
| |
| namespace extensions { |
| |
| void %(method_name)s(FeatureProvider* provider) { |
| """ |
| |
| # The end of the .cc file for the generated FeatureProvider. |
| CC_FILE_END = """ |
| } |
| |
| } // namespace extensions |
| """ |
| |
| # Returns true if the list 'l' does not contain any strings that look like |
| # extension ids. |
| def ListDoesNotContainPlainExtensionIds(l): |
| # For now, let's just say anything 32 characters in length is an id. |
| return len(filter(lambda s: len(s) == 32, l)) == 0 |
| |
| # A "grammar" for what is and isn't allowed in the features.json files. This |
| # grammar has to list all possible keys and the requirements for each. The |
| # format of each entry is: |
| # 'key': { |
| # allowed_type_1: optional_properties, |
| # allowed_type_2: optional_properties, |
| # } |
| # |allowed_types| are the types of values that can be used for a given key. The |
| # possible values are list, unicode, bool, and int. |
| # |optional_properties| provide more restrictions on the given type. The options |
| # are: |
| # 'subtype': Only applicable for lists. If provided, this enforces that each |
| # entry in the list is of the specified type. |
| # 'enum_map': A map of strings to C++ enums. When the compiler sees the given |
| # enum string, it will replace it with the C++ version in the |
| # compiled code. For instance, if a feature specifies |
| # 'channel': 'stable', the generated C++ will assign |
| # version_info::Channel::STABLE to channel. The keys in this map |
| # also serve as a list all of possible values. |
| # 'allow_all': Only applicable for lists. If present, this will check for |
| # a value of "all" for a list value, and will replace it with |
| # the collection of all possible values. For instance, if a |
| # feature specifies 'contexts': 'all', the generated C++ will |
| # assign the list of Feature::BLESSED_EXTENSION_CONTEXT, |
| # Feature::BLESSED_WEB_PAGE_CONTEXT et al for contexts. If not |
| # specified, defaults to false. |
| # 'validators': A list of (function, str) pairs with a function to run on the |
| # value for a feature. Validators allow for more flexible or |
| # one-off style validation than just what's in the grammar (such |
| # as validating the content of a string). The validator function |
| # should return True if the value is valid, and False otherwise. |
| # If the value is invalid, the specified error will be added for |
| # that key. |
| # 'values': A list of all possible allowed values for a given key. |
| # 'shared': Boolean that, if set, ensures that only one of the associated |
| # features has the feature property set. Used primarily for complex |
| # features - for simple features, there is always at most one feature |
| # setting an option. |
| # If a type definition does not have any restrictions (beyond the type itself), |
| # an empty definition ({}) is used. |
| FEATURE_GRAMMAR = ( |
| { |
| 'alias': { |
| unicode: {}, |
| 'shared': True |
| }, |
| 'blacklist': { |
| list: { |
| 'subtype': unicode, |
| 'validators': [ |
| (ListDoesNotContainPlainExtensionIds, |
| 'list should only have hex-encoded SHA1 hashes of extension ids') |
| ] |
| } |
| }, |
| 'channel': { |
| unicode: { |
| 'enum_map': { |
| 'trunk': 'version_info::Channel::UNKNOWN', |
| 'canary': 'version_info::Channel::CANARY', |
| 'dev': 'version_info::Channel::DEV', |
| 'beta': 'version_info::Channel::BETA', |
| 'stable': 'version_info::Channel::STABLE', |
| } |
| } |
| }, |
| 'command_line_switch': { |
| unicode: {} |
| }, |
| 'component_extensions_auto_granted': { |
| bool: {} |
| }, |
| 'contexts': { |
| list: { |
| 'enum_map': { |
| 'blessed_extension': 'Feature::BLESSED_EXTENSION_CONTEXT', |
| 'blessed_web_page': 'Feature::BLESSED_WEB_PAGE_CONTEXT', |
| 'content_script': 'Feature::CONTENT_SCRIPT_CONTEXT', |
| 'extension_service_worker': 'Feature::SERVICE_WORKER_CONTEXT', |
| 'lock_screen_extension': 'Feature::LOCK_SCREEN_EXTENSION_CONTEXT', |
| 'web_page': 'Feature::WEB_PAGE_CONTEXT', |
| 'webui': 'Feature::WEBUI_CONTEXT', |
| 'unblessed_extension': 'Feature::UNBLESSED_EXTENSION_CONTEXT', |
| }, |
| 'allow_all': True |
| }, |
| }, |
| 'default_parent': { |
| bool: {'values': [True]} |
| }, |
| 'dependencies': { |
| list: {'subtype': unicode} |
| }, |
| 'extension_types': { |
| list: { |
| 'enum_map': { |
| 'extension': 'Manifest::TYPE_EXTENSION', |
| 'hosted_app': 'Manifest::TYPE_HOSTED_APP', |
| 'legacy_packaged_app': 'Manifest::TYPE_LEGACY_PACKAGED_APP', |
| 'platform_app': 'Manifest::TYPE_PLATFORM_APP', |
| 'shared_module': 'Manifest::TYPE_SHARED_MODULE', |
| 'theme': 'Manifest::TYPE_THEME', |
| }, |
| 'allow_all': True |
| }, |
| }, |
| 'location': { |
| unicode: { |
| 'enum_map': { |
| 'component': 'SimpleFeature::COMPONENT_LOCATION', |
| 'external_component': 'SimpleFeature::EXTERNAL_COMPONENT_LOCATION', |
| 'policy': 'SimpleFeature::POLICY_LOCATION', |
| } |
| } |
| }, |
| 'internal': { |
| bool: {'values': [True]} |
| }, |
| 'matches': { |
| list: {'subtype': unicode} |
| }, |
| 'max_manifest_version': { |
| int: {'values': [1]} |
| }, |
| 'min_manifest_version': { |
| int: {'values': [2]} |
| }, |
| 'noparent': { |
| bool: {'values': [True]} |
| }, |
| 'platforms': { |
| list: { |
| 'enum_map': { |
| 'chromeos': 'Feature::CHROMEOS_PLATFORM', |
| 'linux': 'Feature::LINUX_PLATFORM', |
| 'mac': 'Feature::MACOSX_PLATFORM', |
| 'win': 'Feature::WIN_PLATFORM', |
| } |
| } |
| }, |
| 'session_types': { |
| list: { |
| 'enum_map': { |
| 'regular': 'FeatureSessionType::REGULAR', |
| 'kiosk': 'FeatureSessionType::KIOSK', |
| 'kiosk.autolaunched': 'FeatureSessionType::AUTOLAUNCHED_KIOSK', |
| } |
| } |
| }, |
| 'source': { |
| unicode: {}, |
| 'shared': True |
| }, |
| 'whitelist': { |
| list: { |
| 'subtype': unicode, |
| 'validators': [ |
| (ListDoesNotContainPlainExtensionIds, |
| 'list should only have hex-encoded SHA1 hashes of extension ids') |
| ] |
| } |
| }, |
| }) |
| |
| FEATURE_TYPES = ['APIFeature', 'BehaviorFeature', |
| 'ManifestFeature', 'PermissionFeature'] |
| |
| def HasProperty(property_name, value): |
| return property_name in value |
| |
| def HasAtLeastOneProperty(property_names, value): |
| return any([HasProperty(name, value) for name in property_names]) |
| |
| def DoesNotHaveAllProperties(property_names, value): |
| return not all([HasProperty(name, value) for name in property_names]) |
| |
| def DoesNotHaveProperty(property_name, value): |
| return property_name not in value |
| |
| def IsFeatureCrossReference(property_name, reverse_property_name, feature, |
| all_features): |
| """ Verifies that |property_name| on |feature| references a feature that |
| references |feature| back using |reverse_property_name| property. |
| |property_name| and |reverse_property_name| are expected to have string |
| values. |
| """ |
| value = feature.GetValue(property_name) |
| if not value: |
| return True |
| # String property values will be wrapped in "", strip those. |
| value_regex = re.compile('^"(.+)"$') |
| parsed_value = value_regex.match(value) |
| assert parsed_value, ( |
| 'IsFeatureCrossReference should only be used on unicode properties') |
| |
| referenced_feature = all_features.get(parsed_value.group(1)) |
| if not referenced_feature: |
| return False |
| reverse_reference_value = referenced_feature.GetValue(reverse_property_name) |
| if not reverse_reference_value: |
| return False |
| # Don't validate reverse reference value for child features - chances are that |
| # the value was inherited from a feature parent, in which case it won't match |
| # current feature name. |
| if feature.has_parent: |
| return True |
| return reverse_reference_value == ('"%s"' % feature.name) |
| |
| SIMPLE_FEATURE_CPP_CLASSES = ({ |
| 'APIFeature': 'SimpleFeature', |
| 'ManifestFeature': 'ManifestFeature', |
| 'PermissionFeature': 'PermissionFeature', |
| 'BehaviorFeature': 'SimpleFeature', |
| }) |
| |
| VALIDATION = ({ |
| 'all': [ |
| (partial(HasAtLeastOneProperty, ['channel', 'dependencies']), |
| 'Features must specify either a channel or dependencies'), |
| ], |
| 'APIFeature': [ |
| (partial(HasProperty, 'contexts'), |
| 'APIFeatures must specify at least one context'), |
| (partial(DoesNotHaveAllProperties, ['alias', 'source']), |
| 'Features cannot specify both alias and source.') |
| ], |
| 'ManifestFeature': [ |
| (partial(HasProperty, 'extension_types'), |
| 'ManifestFeatures must specify at least one extension type'), |
| (partial(DoesNotHaveProperty, 'contexts'), |
| 'ManifestFeatures do not support contexts.'), |
| (partial(DoesNotHaveProperty, 'alias'), |
| 'ManifestFeatures do not support alias.'), |
| (partial(DoesNotHaveProperty, 'source'), |
| 'ManifestFeatures do not support source.'), |
| ], |
| 'BehaviorFeature': [ |
| (partial(DoesNotHaveProperty, 'alias'), |
| 'BehaviorFeatures do not support alias.'), |
| (partial(DoesNotHaveProperty, 'source'), |
| 'BehaviorFeatures do not support source.'), |
| ], |
| 'PermissionFeature': [ |
| (partial(HasProperty, 'extension_types'), |
| 'PermissionFeatures must specify at least one extension type'), |
| (partial(DoesNotHaveProperty, 'contexts'), |
| 'PermissionFeatures do not support contexts.'), |
| (partial(DoesNotHaveProperty, 'alias'), |
| 'PermissionFeatures do not support alias.'), |
| (partial(DoesNotHaveProperty, 'source'), |
| 'PermissionFeatures do not support source.'), |
| ], |
| }) |
| |
| FINAL_VALIDATION = ({ |
| 'all': [], |
| 'APIFeature': [ |
| (partial(IsFeatureCrossReference, 'alias', 'source'), |
| 'A feature alias property should reference a feature whose source ' |
| 'property references it back.'), |
| (partial(IsFeatureCrossReference, 'source', 'alias'), |
| 'A feature source property should reference a feature whose alias ' |
| 'property references it back.') |
| |
| ], |
| 'ManifestFeature': [], |
| 'BehaviorFeature': [], |
| 'PermissionFeature': [] |
| }) |
| |
| # These keys are used to find the parents of different features, but are not |
| # compiled into the features themselves. |
| IGNORED_KEYS = ['default_parent'] |
| |
| # By default, if an error is encountered, assert to stop the compilation. This |
| # can be disabled for testing. |
| ENABLE_ASSERTIONS = True |
| |
| # JSON parsing returns all strings of characters as unicode types. For testing, |
| # we can enable converting all string types to unicode to avoid writing u'' |
| # everywhere. |
| STRINGS_TO_UNICODE = False |
| |
| def GetCodeForFeatureValues(feature_values): |
| """ Gets the Code object for setting feature values for this object. """ |
| c = Code() |
| for key in sorted(feature_values.keys()): |
| if key in IGNORED_KEYS: |
| continue; |
| |
| # TODO(devlin): Remove this hack as part of 842387. |
| set_key = key |
| if key == "whitelist": |
| set_key = "allowlist" |
| elif key == "blacklist": |
| set_key = "blocklist" |
| |
| c.Append('feature->set_%s(%s);' % (set_key, feature_values[key])) |
| return c |
| |
| class Feature(object): |
| """A representation of a single simple feature that can handle all parsing, |
| validation, and code generation. |
| """ |
| def __init__(self, name): |
| self.name = name |
| self.has_parent = False |
| self.errors = [] |
| self.feature_values = {} |
| self.shared_values = {} |
| |
| def _GetType(self, value): |
| """Returns the type of the given value. This can be different than type() if |
| STRINGS_TO_UNICODE is enabled. |
| """ |
| t = type(value) |
| if not STRINGS_TO_UNICODE: |
| return t |
| if t is str: |
| return unicode |
| return t |
| |
| def AddError(self, error): |
| """Adds an error to the feature. If ENABLE_ASSERTIONS is active, this will |
| also assert to stop the compilation process (since errors should never be |
| found in production). |
| """ |
| self.errors.append(error) |
| if ENABLE_ASSERTIONS: |
| assert False, error |
| |
| def _AddKeyError(self, key, error): |
| """Adds an error relating to a particular key in the feature. |
| """ |
| self.AddError('Error parsing feature "%s" at key "%s": %s' % |
| (self.name, key, error)) |
| |
| def _GetCheckedValue(self, key, expected_type, expected_values, |
| enum_map, value): |
| """Returns a string to be used in the generated C++ code for a given key's |
| python value, or None if the value is invalid. For example, if the python |
| value is True, this returns 'true', for a string foo, this returns "foo", |
| and for an enum, this looks up the C++ definition in the enum map. |
| key: The key being parsed. |
| expected_type: The expected type for this value, or None if any type is |
| allowed. |
| expected_values: The list of allowed values for this value, or None if any |
| value is allowed. |
| enum_map: The map from python value -> cpp value for all allowed values, |
| or None if no special mapping should be made. |
| value: The value to check. |
| """ |
| valid = True |
| if expected_values and value not in expected_values: |
| self._AddKeyError(key, 'Illegal value: "%s"' % value) |
| valid = False |
| |
| t = self._GetType(value) |
| if expected_type and t is not expected_type: |
| self._AddKeyError(key, 'Illegal value: "%s"' % value) |
| valid = False |
| |
| if not valid: |
| return None |
| |
| if enum_map: |
| return enum_map[value] |
| |
| if t in [str, unicode]: |
| return '"%s"' % str(value) |
| if t is int: |
| return str(value) |
| if t is bool: |
| return 'true' if value else 'false' |
| assert False, 'Unsupported type: %s' % value |
| |
| def _ParseKey(self, key, value, shared_values, grammar): |
| """Parses the specific key according to the grammar rule for that key if it |
| is present in the json value. |
| key: The key to parse. |
| value: The full value for this feature. |
| shared_values: Set of shared vfalues associated with this feature. |
| grammar: The rule for the specific key. |
| """ |
| if key not in value: |
| return |
| v = value[key] |
| |
| is_all = False |
| if v == 'all' and list in grammar and 'allow_all' in grammar[list]: |
| v = [] |
| is_all = True |
| |
| if 'shared' in grammar and key in shared_values: |
| self._AddKeyError(key, 'Key can be set at most once per feature.') |
| return |
| |
| value_type = self._GetType(v) |
| if value_type not in grammar: |
| self._AddKeyError(key, 'Illegal value: "%s"' % v) |
| return |
| |
| expected = grammar[value_type] |
| expected_values = None |
| enum_map = None |
| if 'values' in expected: |
| expected_values = expected['values'] |
| elif 'enum_map' in expected: |
| enum_map = expected['enum_map'] |
| expected_values = enum_map.keys() |
| |
| if is_all: |
| v = copy.deepcopy(expected_values) |
| |
| expected_type = None |
| if value_type is list and 'subtype' in expected: |
| expected_type = expected['subtype'] |
| |
| cpp_value = None |
| # If this value is a list, iterate over each entry and validate. Otherwise, |
| # validate the single value. |
| if value_type is list: |
| cpp_value = [] |
| for sub_value in v: |
| cpp_sub_value = self._GetCheckedValue(key, expected_type, |
| expected_values, enum_map, |
| sub_value) |
| if cpp_sub_value: |
| cpp_value.append(cpp_sub_value) |
| if cpp_value: |
| cpp_value = '{' + ','.join(cpp_value) + '}' |
| else: |
| cpp_value = self._GetCheckedValue(key, expected_type, expected_values, |
| enum_map, v) |
| |
| if 'validators' in expected: |
| validators = expected['validators'] |
| for validator, error in validators: |
| if not validator(v): |
| self._AddKeyError(key, error) |
| |
| if cpp_value: |
| if 'shared' in grammar: |
| shared_values[key] = cpp_value |
| else: |
| self.feature_values[key] = cpp_value |
| elif key in self.feature_values: |
| # If the key is empty and this feature inherited a value from its parent, |
| # remove the inherited value. |
| del self.feature_values[key] |
| |
| def SetParent(self, parent): |
| """Sets the parent of this feature, and inherits all properties from that |
| parent. |
| """ |
| assert not self.feature_values, 'Parents must be set before parsing' |
| self.feature_values = copy.deepcopy(parent.feature_values) |
| self.has_parent = True |
| |
| def SetSharedValues(self, values): |
| self.shared_values = values |
| |
| def Parse(self, parsed_json, shared_values): |
| """Parses the feature from the given json value.""" |
| for key in parsed_json.keys(): |
| if key not in FEATURE_GRAMMAR: |
| self._AddKeyError(key, 'Unrecognized key') |
| for key, key_grammar in FEATURE_GRAMMAR.iteritems(): |
| self._ParseKey(key, parsed_json, shared_values, key_grammar) |
| |
| def Validate(self, feature_type, shared_values): |
| feature_values = self.feature_values.copy() |
| feature_values.update(shared_values) |
| for validator, error in (VALIDATION[feature_type] + VALIDATION['all']): |
| if not validator(feature_values): |
| self.AddError(error) |
| |
| def GetCode(self, feature_type): |
| """Returns the Code object for generating this feature.""" |
| c = Code() |
| cpp_feature_class = SIMPLE_FEATURE_CPP_CLASSES[feature_type] |
| c.Append('%s* feature = new %s();' % (cpp_feature_class, cpp_feature_class)) |
| c.Append('feature->set_name("%s");' % self.name) |
| c.Concat(GetCodeForFeatureValues(self.GetAllFeatureValues())) |
| return c |
| |
| def AsParent(self): |
| """ Returns the feature values that should be inherited by children features |
| when this feature is set as parent. |
| """ |
| return self |
| |
| def GetValue(self, key): |
| """ Gets feature value for the specified key """ |
| value = self.feature_values.get(key) |
| return value if value else self.shared_values.get(key) |
| |
| def GetAllFeatureValues(self): |
| """ Gets all values set for this feature. """ |
| values = self.feature_values.copy() |
| values.update(self.shared_values) |
| return values |
| |
| def GetErrors(self): |
| return self.errors; |
| |
| class ComplexFeature(Feature): |
| """ Complex feature - feature that is comprised of list of features. |
| Overall complex feature is available if any of contained |
| feature is available. |
| """ |
| def __init__(self, name): |
| Feature.__init__(self, name) |
| self.feature_list = [] |
| |
| def GetCode(self, feature_type): |
| c = Code() |
| c.Append('std::vector<Feature*> features;') |
| for f in self.feature_list: |
| # Sanity check that components of complex features have no shared values |
| # set. |
| assert not f.shared_values |
| c.Sblock('{') |
| c.Concat(f.GetCode(feature_type)) |
| c.Append('features.push_back(feature);') |
| c.Eblock('}') |
| c.Append('ComplexFeature* feature(new ComplexFeature(&features));') |
| c.Append('feature->set_name("%s");' % self.name) |
| c.Concat(GetCodeForFeatureValues(self.shared_values)) |
| return c |
| |
| def AsParent(self): |
| parent = None |
| for p in self.feature_list: |
| if 'default_parent' in p.feature_values: |
| parent = p |
| break |
| assert parent, 'No default parent found for %s' % self.name |
| return parent |
| |
| def GetErrors(self): |
| errors = copy.copy(self.errors) |
| for feature in self.feature_list: |
| errors.extend(feature.GetErrors()) |
| return errors |
| |
| class FeatureCompiler(object): |
| """A compiler to load, parse, and generate C++ code for a number of |
| features.json files.""" |
| def __init__(self, chrome_root, source_files, feature_type, |
| method_name, out_root, out_base_filename): |
| # See __main__'s ArgumentParser for documentation on these properties. |
| self._chrome_root = chrome_root |
| self._source_files = source_files |
| self._feature_type = feature_type |
| self._method_name = method_name |
| self._out_root = out_root |
| self._out_base_filename = out_base_filename |
| |
| # The json value for the feature files. |
| self._json = {} |
| # The parsed features. |
| self._features = {} |
| |
| def Load(self): |
| """Loads and parses the source from each input file and puts the result in |
| self._json.""" |
| for f in self._source_files: |
| abs_source_file = os.path.join(self._chrome_root, f) |
| try: |
| with open(abs_source_file, 'r') as f: |
| f_json = json_parse.Parse(f.read()) |
| except: |
| print('FAILED: Exception encountered while loading "%s"' % |
| abs_source_file) |
| raise |
| dupes = set(f_json) & set(self._json) |
| assert not dupes, 'Duplicate keys found: %s' % list(dupes) |
| self._json.update(f_json) |
| |
| def _FindParent(self, feature_name, feature_value): |
| """Checks to see if a feature has a parent. If it does, returns the |
| parent.""" |
| no_parent = False |
| if type(feature_value) is list: |
| no_parent_values = ['noparent' in v for v in feature_value] |
| no_parent = all(no_parent_values) |
| assert no_parent or not any(no_parent_values), ( |
| '"%s:" All child features must contain the same noparent value' % |
| feature_name) |
| else: |
| no_parent = 'noparent' in feature_value |
| sep = feature_name.rfind('.') |
| if sep is -1 or no_parent: |
| return None |
| |
| parent_name = feature_name[:sep] |
| while sep != -1 and parent_name not in self._features: |
| # This recursion allows for a feature to have a parent that isn't a direct |
| # ancestor. For instance, we could have feature 'alpha', and feature |
| # 'alpha.child.child', where 'alpha.child.child' inherits from 'alpha'. |
| # TODO(devlin): Is this useful? Or logical? |
| sep = feature_name.rfind('.', 0, sep) |
| parent_name = feature_name[:sep] |
| |
| if sep == -1: |
| # TODO(devlin): It'd be kind of nice to be able to assert that the |
| # deduced parent name is in our features, but some dotted features don't |
| # have parents and also don't have noparent, e.g. system.cpu. We should |
| # probably just noparent them so that we can assert this. |
| # raise KeyError('Could not find parent "%s" for feature "%s".' % |
| # (parent_name, feature_name)) |
| return None |
| return self._features[parent_name].AsParent() |
| |
| def _CompileFeature(self, feature_name, feature_value): |
| """Parses a single feature.""" |
| if 'nocompile' in feature_value: |
| assert feature_value['nocompile'], ( |
| 'nocompile should only be true; otherwise omit this key.') |
| return |
| |
| def parse_and_validate(name, value, parent, shared_values): |
| try: |
| feature = Feature(name) |
| if parent: |
| feature.SetParent(parent) |
| feature.Parse(value, shared_values) |
| feature.Validate(self._feature_type, shared_values) |
| return feature |
| except: |
| print('Failure to parse feature "%s"' % feature_name) |
| raise |
| |
| parent = self._FindParent(feature_name, feature_value) |
| shared_values = {} |
| |
| # Handle complex features, which are lists of simple features. |
| if type(feature_value) is list: |
| feature = ComplexFeature(feature_name) |
| |
| # This doesn't handle nested complex features. I think that's probably for |
| # the best. |
| for v in feature_value: |
| feature.feature_list.append( |
| parse_and_validate(feature_name, v, parent, shared_values)) |
| self._features[feature_name] = feature |
| else: |
| self._features[feature_name] = parse_and_validate( |
| feature_name, feature_value, parent, shared_values) |
| |
| # Apply parent shared values at the end to enable child features to |
| # override parent shared value - if parent shared values are added to |
| # shared value set before a child feature is parsed, the child feature |
| # overriding shared values set by its parent would cause an error due to |
| # shared values being set twice. |
| final_shared_values = copy.deepcopy(parent.shared_values) if parent else {} |
| final_shared_values.update(shared_values) |
| self._features[feature_name].SetSharedValues(final_shared_values) |
| |
| def _FinalValidation(self): |
| validators = FINAL_VALIDATION['all'] + FINAL_VALIDATION[self._feature_type] |
| for name, feature in self._features.items(): |
| for validator, error in validators: |
| if not validator(feature, self._features): |
| feature.AddError(error) |
| |
| def Compile(self): |
| """Parses all features after loading the input files.""" |
| # Iterate over in sorted order so that parents come first. |
| for k in sorted(self._json.keys()): |
| self._CompileFeature(k, self._json[k]) |
| self._FinalValidation() |
| |
| def Render(self): |
| """Returns the Code object for the body of the .cc file, which handles the |
| initialization of all features.""" |
| c = Code() |
| c.Sblock() |
| for k in sorted(self._features.keys()): |
| c.Sblock('{') |
| feature = self._features[k] |
| c.Concat(feature.GetCode(self._feature_type)) |
| c.Append('provider->AddFeature("%s", feature);' % k) |
| c.Eblock('}') |
| c.Eblock() |
| return c |
| |
| def Write(self): |
| """Writes the output.""" |
| header_file = self._out_base_filename + '.h' |
| cc_file = self._out_base_filename + '.cc' |
| |
| include_file_root = self._out_root |
| GEN_DIR_PREFIX = 'gen/' |
| if include_file_root.startswith(GEN_DIR_PREFIX): |
| include_file_root = include_file_root[len(GEN_DIR_PREFIX):] |
| header_file_path = '%s/%s' % (include_file_root, header_file) |
| cc_file_path = '%s/%s' % (include_file_root, cc_file) |
| substitutions = ({ |
| 'header_file_path': header_file_path, |
| 'header_guard': (header_file_path.replace('/', '_'). |
| replace('.', '_').upper()), |
| 'method_name': self._method_name, |
| 'source_files': str(self._source_files), |
| 'year': str(datetime.now().year) |
| }) |
| if not os.path.exists(self._out_root): |
| os.makedirs(self._out_root) |
| # Write the .h file. |
| with open(os.path.join(self._out_root, header_file), 'w') as f: |
| header_file = Code() |
| header_file.Append(HEADER_FILE_TEMPLATE) |
| header_file.Substitute(substitutions) |
| f.write(header_file.Render().strip()) |
| # Write the .cc file. |
| with open(os.path.join(self._out_root, cc_file), 'w') as f: |
| cc_file = Code() |
| cc_file.Append(CC_FILE_BEGIN) |
| cc_file.Substitute(substitutions) |
| cc_file.Concat(self.Render()) |
| cc_end = Code() |
| cc_end.Append(CC_FILE_END) |
| cc_end.Substitute(substitutions) |
| cc_file.Concat(cc_end) |
| f.write(cc_file.Render().strip()) |
| |
| if __name__ == '__main__': |
| parser = argparse.ArgumentParser(description='Compile json feature files') |
| parser.add_argument('chrome_root', type=str, |
| help='The root directory of the chrome checkout') |
| parser.add_argument( |
| 'feature_type', type=str, |
| help='The name of the class to use in feature generation ' + |
| '(e.g. APIFeature, PermissionFeature)') |
| parser.add_argument('method_name', type=str, |
| help='The name of the method to populate the provider') |
| parser.add_argument('out_root', type=str, |
| help='The root directory to generate the C++ files into') |
| parser.add_argument( |
| 'out_base_filename', type=str, |
| help='The base filename for the C++ files (.h and .cc will be appended)') |
| parser.add_argument('source_files', type=str, nargs='+', |
| help='The source features.json files') |
| args = parser.parse_args() |
| if args.feature_type not in FEATURE_TYPES: |
| raise NameError('Unknown feature type: %s' % args.feature_type) |
| c = FeatureCompiler(args.chrome_root, args.source_files, args.feature_type, |
| args.method_name, args.out_root, |
| args.out_base_filename) |
| c.Load() |
| c.Compile() |
| c.Write() |