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