Home | History | Annotate | Download | only in tools
      1 #!/usr/bin/env python
      2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 '''python %prog [options] platform chromium_os_flag template
      7 
      8 platform specifies which platform source is being generated for
      9   and can be one of (win, mac, linux)
     10 chromium_os_flag should be 1 if this is a Chromium OS build
     11 template is the path to a .json policy template file.'''
     12 
     13 from __future__ import with_statement
     14 from functools import partial
     15 import json
     16 from optparse import OptionParser
     17 import re
     18 import sys
     19 import textwrap
     20 import types
     21 
     22 
     23 CHROME_POLICY_KEY = 'SOFTWARE\\\\Policies\\\\Google\\\\Chrome'
     24 CHROMIUM_POLICY_KEY = 'SOFTWARE\\\\Policies\\\\Chromium'
     25 
     26 
     27 class PolicyDetails:
     28   """Parses a policy template and caches all its details."""
     29 
     30   # Maps policy types to a tuple with 3 other types:
     31   # - the equivalent base::Value::Type or 'TYPE_EXTERNAL' if the policy
     32   #   references external data
     33   # - the equivalent Protobuf field type
     34   # - the name of one of the protobufs for shared policy types
     35   # TODO(joaodasilva): refactor the 'dict' type into a more generic 'json' type
     36   # that can also be used to represent lists of other JSON objects.
     37   TYPE_MAP = {
     38     'dict':         ('TYPE_DICTIONARY',   'string',       'String'),
     39     'external':     ('TYPE_EXTERNAL',     'string',       'String'),
     40     'int':          ('TYPE_INTEGER',      'int64',        'Integer'),
     41     'int-enum':     ('TYPE_INTEGER',      'int64',        'Integer'),
     42     'list':         ('TYPE_LIST',         'StringList',   'StringList'),
     43     'main':         ('TYPE_BOOLEAN',      'bool',         'Boolean'),
     44     'string':       ('TYPE_STRING',       'string',       'String'),
     45     'string-enum':  ('TYPE_STRING',       'string',       'String'),
     46   }
     47 
     48   class EnumItem:
     49     def __init__(self, item):
     50       self.caption = PolicyDetails._RemovePlaceholders(item['caption'])
     51       self.value = item['value']
     52 
     53   def __init__(self, policy, os, is_chromium_os):
     54     self.id = policy['id']
     55     self.name = policy['name']
     56     self.is_deprecated = policy.get('deprecated', False)
     57     self.is_device_only = policy.get('device_only', False)
     58     self.schema = policy.get('schema', {})
     59 
     60     expected_platform = 'chrome_os' if is_chromium_os else os.lower()
     61     self.platforms = []
     62     for platform, version in [ p.split(':') for p in policy['supported_on'] ]:
     63       if not version.endswith('-'):
     64         continue
     65 
     66       if platform.startswith('chrome.'):
     67         platform_sub = platform[7:]
     68         if platform_sub == '*':
     69           self.platforms.extend(['win', 'mac', 'linux'])
     70         else:
     71           self.platforms.append(platform_sub)
     72       else:
     73         self.platforms.append(platform)
     74 
     75     self.platforms.sort()
     76     self.is_supported = expected_platform in self.platforms
     77 
     78     if not PolicyDetails.TYPE_MAP.has_key(policy['type']):
     79       raise NotImplementedError('Unknown policy type for %s: %s' %
     80                                 (policy['name'], policy['type']))
     81     self.policy_type, self.protobuf_type, self.policy_protobuf_type = \
     82         PolicyDetails.TYPE_MAP[policy['type']]
     83     self.schema = policy['schema']
     84 
     85     self.desc = '\n'.join(
     86         map(str.strip,
     87             PolicyDetails._RemovePlaceholders(policy['desc']).splitlines()))
     88     self.caption = PolicyDetails._RemovePlaceholders(policy['caption'])
     89     self.max_size = policy.get('max_size', 0)
     90 
     91     items = policy.get('items')
     92     if items is None:
     93       self.items = None
     94     else:
     95       self.items = [ PolicyDetails.EnumItem(entry) for entry in items ]
     96 
     97   PH_PATTERN = re.compile('<ph[^>]*>([^<]*|[^<]*<ex>([^<]*)</ex>[^<]*)</ph>')
     98 
     99   # Simplistic grit placeholder stripper.
    100   @staticmethod
    101   def _RemovePlaceholders(text):
    102     result = ''
    103     pos = 0
    104     for m in PolicyDetails.PH_PATTERN.finditer(text):
    105       result += text[pos:m.start(0)]
    106       result += m.group(2) or m.group(1)
    107       pos = m.end(0)
    108     result += text[pos:]
    109     return result
    110 
    111 
    112 def main():
    113   parser = OptionParser(usage=__doc__)
    114   parser.add_option('--pch', '--policy-constants-header', dest='header_path',
    115                     help='generate header file of policy constants',
    116                     metavar='FILE')
    117   parser.add_option('--pcc', '--policy-constants-source', dest='source_path',
    118                     help='generate source file of policy constants',
    119                     metavar='FILE')
    120   parser.add_option('--cpp', '--cloud-policy-protobuf',
    121                     dest='cloud_policy_proto_path',
    122                     help='generate cloud policy protobuf file',
    123                     metavar='FILE')
    124   parser.add_option('--csp', '--chrome-settings-protobuf',
    125                     dest='chrome_settings_proto_path',
    126                     help='generate chrome settings protobuf file',
    127                     metavar='FILE')
    128   parser.add_option('--cpd', '--cloud-policy-decoder',
    129                     dest='cloud_policy_decoder_path',
    130                     help='generate C++ code decoding the cloud policy protobuf',
    131                     metavar='FILE')
    132 
    133   (opts, args) = parser.parse_args()
    134 
    135   if len(args) != 3:
    136     print 'exactly platform, chromium_os flag and input file must be specified.'
    137     parser.print_help()
    138     return 2
    139 
    140   os = args[0]
    141   is_chromium_os = args[1] == '1'
    142   template_file_name = args[2]
    143 
    144   template_file_contents = _LoadJSONFile(template_file_name)
    145   policy_details = [ PolicyDetails(policy, os, is_chromium_os)
    146                      for policy in _Flatten(template_file_contents) ]
    147   sorted_policy_details = sorted(policy_details, key=lambda policy: policy.name)
    148 
    149   def GenerateFile(path, writer, sorted=False):
    150     if path:
    151       with open(path, 'w') as f:
    152         _OutputGeneratedWarningHeader(f, template_file_name)
    153         writer(sorted and sorted_policy_details or policy_details, os, f)
    154 
    155   GenerateFile(opts.header_path, _WritePolicyConstantHeader, sorted=True)
    156   GenerateFile(opts.source_path, _WritePolicyConstantSource, sorted=True)
    157   GenerateFile(opts.cloud_policy_proto_path, _WriteCloudPolicyProtobuf)
    158   GenerateFile(opts.chrome_settings_proto_path, _WriteChromeSettingsProtobuf)
    159   GenerateFile(opts.cloud_policy_decoder_path, _WriteCloudPolicyDecoder)
    160 
    161   return 0
    162 
    163 
    164 #------------------ shared helpers ---------------------------------#
    165 
    166 def _OutputGeneratedWarningHeader(f, template_file_path):
    167   f.write('//\n'
    168           '// DO NOT MODIFY THIS FILE DIRECTLY!\n'
    169           '// IT IS GENERATED BY generate_policy_source.py\n'
    170           '// FROM ' + template_file_path + '\n'
    171           '//\n\n')
    172 
    173 
    174 COMMENT_WRAPPER = textwrap.TextWrapper()
    175 COMMENT_WRAPPER.width = 80
    176 COMMENT_WRAPPER.initial_indent = '// '
    177 COMMENT_WRAPPER.subsequent_indent = '// '
    178 COMMENT_WRAPPER.replace_whitespace = False
    179 
    180 
    181 # Writes a comment, each line prefixed by // and wrapped to 80 spaces.
    182 def _OutputComment(f, comment):
    183   for line in comment.splitlines():
    184     if len(line) == 0:
    185       f.write('//')
    186     else:
    187       f.write(COMMENT_WRAPPER.fill(line))
    188     f.write('\n')
    189 
    190 
    191 # Returns an iterator over all the policies in |template_file_contents|.
    192 def _Flatten(template_file_contents):
    193   for policy in template_file_contents['policy_definitions']:
    194     if policy['type'] == 'group':
    195       for sub_policy in policy['policies']:
    196         yield sub_policy
    197     else:
    198       yield policy
    199 
    200 
    201 def _LoadJSONFile(json_file):
    202   with open(json_file, 'r') as f:
    203     text = f.read()
    204   return eval(text)
    205 
    206 
    207 #------------------ policy constants header ------------------------#
    208 
    209 def _WritePolicyConstantHeader(policies, os, f):
    210   f.write('#ifndef CHROME_COMMON_POLICY_CONSTANTS_H_\n'
    211           '#define CHROME_COMMON_POLICY_CONSTANTS_H_\n'
    212           '\n'
    213           '#include <string>\n'
    214           '\n'
    215           '#include "base/basictypes.h"\n'
    216           '#include "base/values.h"\n'
    217           '#include "components/policy/core/common/policy_details.h"\n'
    218           '\n'
    219           'namespace policy {\n'
    220           '\n'
    221           'namespace internal {\n'
    222           'struct SchemaData;\n'
    223           '}\n\n')
    224 
    225   if os == 'win':
    226     f.write('// The windows registry path where Chrome policy '
    227             'configuration resides.\n'
    228             'extern const wchar_t kRegistryChromePolicyKey[];\n')
    229 
    230   f.write('// Returns the PolicyDetails for |policy| if |policy| is a known\n'
    231           '// Chrome policy, otherwise returns NULL.\n'
    232           'const PolicyDetails* GetChromePolicyDetails('
    233               'const std::string& policy);\n'
    234           '\n'
    235           '// Returns the schema data of the Chrome policy schema.\n'
    236           'const internal::SchemaData* GetChromeSchemaData();\n'
    237           '\n')
    238   f.write('// Key names for the policy settings.\n'
    239           'namespace key {\n\n')
    240   for policy in policies:
    241     # TODO(joaodasilva): Include only supported policies in
    242     # configuration_policy_handler.cc and configuration_policy_handler_list.cc
    243     # so that these names can be conditional on 'policy.is_supported'.
    244     # http://crbug.com/223616
    245     f.write('extern const char k' + policy.name + '[];\n')
    246   f.write('\n}  // namespace key\n\n'
    247           '}  // namespace policy\n\n'
    248           '#endif  // CHROME_COMMON_POLICY_CONSTANTS_H_\n')
    249 
    250 
    251 #------------------ policy constants source ------------------------#
    252 
    253 # A mapping of the simple schema types to base::Value::Types.
    254 SIMPLE_SCHEMA_NAME_MAP = {
    255   'boolean': 'TYPE_BOOLEAN',
    256   'integer': 'TYPE_INTEGER',
    257   'null'   : 'TYPE_NULL',
    258   'number' : 'TYPE_DOUBLE',
    259   'string' : 'TYPE_STRING',
    260 }
    261 
    262 class SchemaNodesGenerator:
    263   """Builds the internal structs to represent a JSON schema."""
    264 
    265   def __init__(self, shared_strings):
    266     """Creates a new generator.
    267 
    268     |shared_strings| is a map of strings to a C expression that evaluates to
    269     that string at runtime. This mapping can be used to reuse existing string
    270     constants."""
    271     self.shared_strings = shared_strings
    272     self.schema_nodes = []
    273     self.property_nodes = []
    274     self.properties_nodes = []
    275     self.restriction_nodes = []
    276     self.int_enums = []
    277     self.string_enums = []
    278     self.simple_types = {
    279       'boolean': None,
    280       'integer': None,
    281       'null': None,
    282       'number': None,
    283       'string': None,
    284     }
    285     self.stringlist_type = None
    286     self.ranges = {}
    287     self.id_map = {}
    288 
    289   def GetString(self, s):
    290     if s in self.shared_strings:
    291       return self.shared_strings[s]
    292     # Generate JSON escaped string, which is slightly different from desired
    293     # C/C++ escaped string. Known differences includes unicode escaping format.
    294     return json.dumps(s)
    295 
    296   def AppendSchema(self, type, extra, comment=''):
    297     index = len(self.schema_nodes)
    298     self.schema_nodes.append((type, extra, comment))
    299     return index
    300 
    301   def AppendRestriction(self, first, second):
    302     r = (str(first), str(second))
    303     if not r in self.ranges:
    304       self.ranges[r] = len(self.restriction_nodes)
    305       self.restriction_nodes.append(r)
    306     return self.ranges[r]
    307 
    308   def GetSimpleType(self, name):
    309     if self.simple_types[name] == None:
    310       self.simple_types[name] = self.AppendSchema(
    311           SIMPLE_SCHEMA_NAME_MAP[name],
    312           -1,
    313           'simple type: ' + name)
    314     return self.simple_types[name]
    315 
    316   def GetStringList(self):
    317     if self.stringlist_type == None:
    318       self.stringlist_type = self.AppendSchema(
    319           'TYPE_LIST',
    320           self.GetSimpleType('string'),
    321           'simple type: stringlist')
    322     return self.stringlist_type
    323 
    324   def SchemaHaveRestriction(self, schema):
    325     return any(keyword in schema for keyword in
    326         ['minimum', 'maximum', 'enum', 'pattern'])
    327 
    328   def IsConsecutiveInterval(self, seq):
    329     sortedSeq = sorted(seq)
    330     return all(sortedSeq[i] + 1 == sortedSeq[i + 1]
    331                for i in xrange(len(sortedSeq) - 1))
    332 
    333   def GetEnumIntegerType(self, schema, name):
    334     assert all(type(x) == int for x in schema['enum'])
    335     possible_values = schema['enum']
    336     if self.IsConsecutiveInterval(possible_values):
    337       index = self.AppendRestriction(max(possible_values), min(possible_values))
    338       return self.AppendSchema('TYPE_INTEGER', index,
    339           'integer with enumeration restriction (use range instead): %s' % name)
    340     offset_begin = len(self.int_enums)
    341     self.int_enums += possible_values
    342     offset_end = len(self.int_enums)
    343     return self.AppendSchema('TYPE_INTEGER',
    344         self.AppendRestriction(offset_begin, offset_end),
    345         'integer with enumeration restriction: %s' % name)
    346 
    347   def GetEnumStringType(self, schema, name):
    348     assert all(type(x) == str for x in schema['enum'])
    349     offset_begin = len(self.string_enums)
    350     self.string_enums += schema['enum']
    351     offset_end = len(self.string_enums)
    352     return self.AppendSchema('TYPE_STRING',
    353         self.AppendRestriction(offset_begin, offset_end),
    354         'string with enumeration restriction: %s' % name)
    355 
    356   def GetEnumType(self, schema, name):
    357     if len(schema['enum']) == 0:
    358       raise RuntimeError('Empty enumeration in %s' % name)
    359     elif schema['type'] == 'integer':
    360       return self.GetEnumIntegerType(schema, name)
    361     elif schema['type'] == 'string':
    362       return self.GetEnumStringType(schema, name)
    363     else:
    364       raise RuntimeError('Unknown enumeration type in %s' % name)
    365 
    366   def GetPatternType(self, schema, name):
    367     if schema['type'] != 'string':
    368       raise RuntimeError('Unknown pattern type in %s' % name)
    369     pattern = schema['pattern']
    370     # Try to compile the pattern to validate it, note that the syntax used
    371     # here might be slightly different from re2.
    372     # TODO(binjin): Add a python wrapper of re2 and use it here.
    373     re.compile(pattern)
    374     index = len(self.string_enums);
    375     self.string_enums.append(pattern);
    376     return self.AppendSchema('TYPE_STRING',
    377         self.AppendRestriction(index, index),
    378         'string with pattern restriction: %s' % name);
    379 
    380   def GetRangedType(self, schema, name):
    381     if schema['type'] != 'integer':
    382       raise RuntimeError('Unknown ranged type in %s' % name)
    383     min_value_set, max_value_set = False, False
    384     if 'minimum' in schema:
    385       min_value = int(schema['minimum'])
    386       min_value_set = True
    387     if 'maximum' in schema:
    388       max_value = int(schema['minimum'])
    389       max_value_set = True
    390     if min_value_set and max_value_set and min_value > max_value:
    391       raise RuntimeError('Invalid ranged type in %s' % name)
    392     index = self.AppendRestriction(
    393         str(max_value) if max_value_set else 'INT_MAX',
    394         str(min_value) if min_value_set else 'INT_MIN')
    395     return self.AppendSchema('TYPE_INTEGER',
    396         index,
    397         'integer with ranged restriction: %s' % name)
    398 
    399   def Generate(self, schema, name):
    400     """Generates the structs for the given schema.
    401 
    402     |schema|: a valid JSON schema in a dictionary.
    403     |name|: the name of the current node, for the generated comments."""
    404     if schema.has_key('$ref'):
    405       if schema.has_key('id'):
    406         raise RuntimeError("Schemas with a $ref can't have an id")
    407       if not isinstance(schema['$ref'], types.StringTypes):
    408         raise RuntimeError("$ref attribute must be a string")
    409       return schema['$ref']
    410     if schema['type'] in self.simple_types:
    411       if not self.SchemaHaveRestriction(schema):
    412         # Simple types use shared nodes.
    413         return self.GetSimpleType(schema['type'])
    414       elif 'enum' in schema:
    415         return self.GetEnumType(schema, name)
    416       elif 'pattern' in schema:
    417         return self.GetPatternType(schema, name)
    418       else:
    419         return self.GetRangedType(schema, name)
    420 
    421     if schema['type'] == 'array':
    422       # Special case for lists of strings, which is a common policy type.
    423       # The 'type' may be missing if the schema has a '$ref' attribute.
    424       if schema['items'].get('type', '') == 'string':
    425         return self.GetStringList()
    426       return self.AppendSchema('TYPE_LIST',
    427           self.GenerateAndCollectID(schema['items'], 'items of ' + name))
    428     elif schema['type'] == 'object':
    429       # Reserve an index first, so that dictionaries come before their
    430       # properties. This makes sure that the root node is the first in the
    431       # SchemaNodes array.
    432       index = self.AppendSchema('TYPE_DICTIONARY', -1)
    433 
    434       if 'additionalProperties' in schema:
    435         additionalProperties = self.GenerateAndCollectID(
    436             schema['additionalProperties'],
    437             'additionalProperties of ' + name)
    438       else:
    439         additionalProperties = -1
    440 
    441       # Properties must be sorted by name, for the binary search lookup.
    442       # Note that |properties| must be evaluated immediately, so that all the
    443       # recursive calls to Generate() append the necessary child nodes; if
    444       # |properties| were a generator then this wouldn't work.
    445       sorted_properties = sorted(schema.get('properties', {}).items())
    446       properties = [
    447           (self.GetString(key), self.GenerateAndCollectID(subschema, key))
    448           for key, subschema in sorted_properties ]
    449 
    450       pattern_properties = []
    451       for pattern, subschema in schema.get('patternProperties', {}).items():
    452         pattern_properties.append((self.GetString(pattern),
    453             self.GenerateAndCollectID(subschema, pattern)));
    454 
    455       begin = len(self.property_nodes)
    456       self.property_nodes += properties
    457       end = len(self.property_nodes)
    458       self.property_nodes += pattern_properties
    459       pattern_end = len(self.property_nodes)
    460 
    461       if index == 0:
    462         self.root_properties_begin = begin
    463         self.root_properties_end = end
    464 
    465       extra = len(self.properties_nodes)
    466       self.properties_nodes.append((begin, end, pattern_end,
    467           additionalProperties, name))
    468 
    469       # Set the right data at |index| now.
    470       self.schema_nodes[index] = ('TYPE_DICTIONARY', extra, name)
    471       return index
    472     else:
    473       assert False
    474 
    475   def GenerateAndCollectID(self, schema, name):
    476     """A wrapper of Generate(), will take the return value, check and add 'id'
    477     attribute to self.id_map. The wrapper needs to be used for every call to
    478     Generate().
    479     """
    480     index = self.Generate(schema, name)
    481     if not schema.has_key('id'):
    482       return index
    483     id_str = schema['id']
    484     if self.id_map.has_key(id_str):
    485       raise RuntimeError('Duplicated id: ' + id_str)
    486     self.id_map[id_str] = index
    487     return index
    488 
    489   def Write(self, f):
    490     """Writes the generated structs to the given file.
    491 
    492     |f| an open file to write to."""
    493     f.write('const internal::SchemaNode kSchemas[] = {\n'
    494             '//  Type                          Extra\n')
    495     for type, extra, comment in self.schema_nodes:
    496       type += ','
    497       f.write('  { base::Value::%-18s %3d },  // %s\n' % (type, extra, comment))
    498     f.write('};\n\n')
    499 
    500     if self.property_nodes:
    501       f.write('const internal::PropertyNode kPropertyNodes[] = {\n'
    502               '//  Property                                          #Schema\n')
    503       for key, schema in self.property_nodes:
    504         key += ','
    505         f.write('  { %-50s %6d },\n' % (key, schema))
    506       f.write('};\n\n')
    507 
    508     if self.properties_nodes:
    509       f.write('const internal::PropertiesNode kProperties[] = {\n'
    510               '//  Begin    End  PatternEnd Additional Properties\n')
    511       for node in self.properties_nodes:
    512         f.write('  { %5d, %5d, %10d, %5d },  // %s\n' % node)
    513       f.write('};\n\n')
    514 
    515     if self.restriction_nodes:
    516       f.write('const internal::RestrictionNode kRestrictionNodes[] = {\n')
    517       f.write('//   FIRST, SECOND\n')
    518       for first, second in self.restriction_nodes:
    519         f.write('  {{ %-8s %4s}},\n' % (first + ',', second))
    520       f.write('};\n\n')
    521 
    522     if self.int_enums:
    523       f.write('const int kIntegerEnumerations[] = {\n')
    524       for possible_values in self.int_enums:
    525         f.write('  %d,\n' % possible_values)
    526       f.write('};\n\n')
    527 
    528     if self.string_enums:
    529       f.write('const char* kStringEnumerations[] = {\n')
    530       for possible_values in self.string_enums:
    531         f.write('  %s,\n' % self.GetString(possible_values))
    532       f.write('};\n\n')
    533 
    534     f.write('const internal::SchemaData kChromeSchemaData = {\n'
    535             '  kSchemas,\n')
    536     f.write('  kPropertyNodes,\n' if self.property_nodes else '  NULL,\n')
    537     f.write('  kProperties,\n' if self.properties_nodes else '  NULL,\n')
    538     f.write('  kRestrictionNodes,\n' if self.restriction_nodes else '  NULL,\n')
    539     f.write('  kIntegerEnumerations,\n' if self.int_enums else '  NULL,\n')
    540     f.write('  kStringEnumerations,\n' if self.string_enums else '  NULL,\n')
    541     f.write('};\n\n')
    542 
    543   def GetByID(self, id_str):
    544     if not isinstance(id_str, types.StringTypes):
    545       return id_str
    546     if not self.id_map.has_key(id_str):
    547       raise RuntimeError('Invalid $ref: ' + id_str)
    548     return self.id_map[id_str]
    549 
    550   def ResolveID(self, index, params):
    551     return params[:index] + (self.GetByID(params[index]),) + params[index+1:]
    552 
    553   def ResolveReferences(self):
    554     """Resolve reference mapping, required to be called after Generate()
    555 
    556     After calling Generate(), the type of indices used in schema structures
    557     might be either int or string. An int type suggests that it's a resolved
    558     index, but for string type it's unresolved. Resolving a reference is as
    559     simple as looking up for corresponding ID in self.id_map, and replace the
    560     old index with the mapped index.
    561     """
    562     self.schema_nodes = map(partial(self.ResolveID, 1), self.schema_nodes)
    563     self.property_nodes = map(partial(self.ResolveID, 1), self.property_nodes)
    564     self.properties_nodes = map(partial(self.ResolveID, 3),
    565         self.properties_nodes)
    566 
    567 def _WritePolicyConstantSource(policies, os, f):
    568   f.write('#include "policy/policy_constants.h"\n'
    569           '\n'
    570           '#include <algorithm>\n'
    571           '#include <climits>\n'
    572           '\n'
    573           '#include "base/logging.h"\n'
    574           '#include "components/policy/core/common/schema_internal.h"\n'
    575           '\n'
    576           'namespace policy {\n'
    577           '\n'
    578           'namespace {\n'
    579           '\n')
    580 
    581   # Generate the Chrome schema.
    582   chrome_schema = {
    583     'type': 'object',
    584     'properties': {},
    585   }
    586   shared_strings = {}
    587   for policy in policies:
    588     shared_strings[policy.name] = "key::k%s" % policy.name
    589     if policy.is_supported:
    590       chrome_schema['properties'][policy.name] = policy.schema
    591 
    592   # Note: this list must be kept in sync with the known property list of the
    593   # Chrome schema, so that binary seaching in the PropertyNode array gets the
    594   # right index on this array as well. See the implementation of
    595   # GetChromePolicyDetails() below.
    596   f.write('const PolicyDetails kChromePolicyDetails[] = {\n'
    597           '//  is_deprecated  is_device_policy  id    max_external_data_size\n')
    598   for policy in policies:
    599     if policy.is_supported:
    600       f.write('  { %-14s %-16s %3s, %24s },\n' % (
    601                   'true,' if policy.is_deprecated else 'false,',
    602                   'true,' if policy.is_device_only else 'false,',
    603                   policy.id,
    604                   policy.max_size))
    605   f.write('};\n\n')
    606 
    607   schema_generator = SchemaNodesGenerator(shared_strings)
    608   schema_generator.GenerateAndCollectID(chrome_schema, 'root node')
    609   schema_generator.ResolveReferences()
    610   schema_generator.Write(f)
    611 
    612   f.write('bool CompareKeys(const internal::PropertyNode& node,\n'
    613           '                 const std::string& key) {\n'
    614           '  return node.key < key;\n'
    615           '}\n\n')
    616 
    617   f.write('}  // namespace\n\n')
    618 
    619   if os == 'win':
    620     f.write('#if defined(GOOGLE_CHROME_BUILD)\n'
    621             'const wchar_t kRegistryChromePolicyKey[] = '
    622             'L"' + CHROME_POLICY_KEY + '";\n'
    623             '#else\n'
    624             'const wchar_t kRegistryChromePolicyKey[] = '
    625             'L"' + CHROMIUM_POLICY_KEY + '";\n'
    626             '#endif\n\n')
    627 
    628   f.write('const internal::SchemaData* GetChromeSchemaData() {\n'
    629           '  return &kChromeSchemaData;\n'
    630           '}\n\n')
    631 
    632   f.write('const PolicyDetails* GetChromePolicyDetails('
    633               'const std::string& policy) {\n'
    634           '  // First index in kPropertyNodes of the Chrome policies.\n'
    635           '  static const int begin_index = %s;\n'
    636           '  // One-past-the-end of the Chrome policies in kPropertyNodes.\n'
    637           '  static const int end_index = %s;\n' %
    638           (schema_generator.root_properties_begin,
    639            schema_generator.root_properties_end))
    640   f.write('  const internal::PropertyNode* begin =\n'
    641           '      kPropertyNodes + begin_index;\n'
    642           '  const internal::PropertyNode* end = kPropertyNodes + end_index;\n'
    643           '  const internal::PropertyNode* it =\n'
    644           '      std::lower_bound(begin, end, policy, CompareKeys);\n'
    645           '  if (it == end || it->key != policy)\n'
    646           '    return NULL;\n'
    647           '  // This relies on kPropertyNodes from begin_index to end_index\n'
    648           '  // having exactly the same policies (and in the same order) as\n'
    649           '  // kChromePolicyDetails, so that binary searching on the first\n'
    650           '  // gets the same results as a binary search on the second would.\n'
    651           '  // However, kPropertyNodes has the policy names and\n'
    652           '  // kChromePolicyDetails doesn\'t, so we obtain the index into\n'
    653           '  // the second array by searching the first to avoid duplicating\n'
    654           '  // the policy name pointers.\n'
    655           '  // Offsetting |it| from |begin| here obtains the index we\'re\n'
    656           '  // looking for.\n'
    657           '  size_t index = it - begin;\n'
    658           '  CHECK_LT(index, arraysize(kChromePolicyDetails));\n'
    659           '  return kChromePolicyDetails + index;\n'
    660           '}\n\n')
    661 
    662   f.write('namespace key {\n\n')
    663   for policy in policies:
    664     # TODO(joaodasilva): Include only supported policies in
    665     # configuration_policy_handler.cc and configuration_policy_handler_list.cc
    666     # so that these names can be conditional on 'policy.is_supported'.
    667     # http://crbug.com/223616
    668     f.write('const char k{name}[] = "{name}";\n'.format(name=policy.name))
    669   f.write('\n}  // namespace key\n\n'
    670           '}  // namespace policy\n')
    671 
    672 
    673 #------------------ policy protobufs --------------------------------#
    674 
    675 CHROME_SETTINGS_PROTO_HEAD = '''
    676 syntax = "proto2";
    677 
    678 option optimize_for = LITE_RUNTIME;
    679 
    680 package enterprise_management;
    681 
    682 // For StringList and PolicyOptions.
    683 import "cloud_policy.proto";
    684 
    685 '''
    686 
    687 
    688 CLOUD_POLICY_PROTO_HEAD = '''
    689 syntax = "proto2";
    690 
    691 option optimize_for = LITE_RUNTIME;
    692 
    693 package enterprise_management;
    694 
    695 message StringList {
    696   repeated string entries = 1;
    697 }
    698 
    699 message PolicyOptions {
    700   enum PolicyMode {
    701     // The given settings are applied regardless of user choice.
    702     MANDATORY = 0;
    703     // The user may choose to override the given settings.
    704     RECOMMENDED = 1;
    705     // No policy value is present and the policy should be ignored.
    706     UNSET = 2;
    707   }
    708   optional PolicyMode mode = 1 [default = MANDATORY];
    709 }
    710 
    711 message BooleanPolicyProto {
    712   optional PolicyOptions policy_options = 1;
    713   optional bool value = 2;
    714 }
    715 
    716 message IntegerPolicyProto {
    717   optional PolicyOptions policy_options = 1;
    718   optional int64 value = 2;
    719 }
    720 
    721 message StringPolicyProto {
    722   optional PolicyOptions policy_options = 1;
    723   optional string value = 2;
    724 }
    725 
    726 message StringListPolicyProto {
    727   optional PolicyOptions policy_options = 1;
    728   optional StringList value = 2;
    729 }
    730 
    731 '''
    732 
    733 
    734 # Field IDs [1..RESERVED_IDS] will not be used in the wrapping protobuf.
    735 RESERVED_IDS = 2
    736 
    737 
    738 def _WritePolicyProto(f, policy, fields):
    739   _OutputComment(f, policy.caption + '\n\n' + policy.desc)
    740   if policy.items is not None:
    741     _OutputComment(f, '\nValid values:')
    742     for item in policy.items:
    743       _OutputComment(f, '  %s: %s' % (str(item.value), item.caption))
    744   if policy.policy_type == 'TYPE_DICTIONARY':
    745     _OutputComment(f, '\nValue schema:\n%s' %
    746                    json.dumps(policy.schema, sort_keys=True, indent=4,
    747                               separators=(',', ': ')))
    748   _OutputComment(f, '\nSupported on: %s' % ', '.join(policy.platforms))
    749   f.write('message %sProto {\n' % policy.name)
    750   f.write('  optional PolicyOptions policy_options = 1;\n')
    751   f.write('  optional %s %s = 2;\n' % (policy.protobuf_type, policy.name))
    752   f.write('}\n\n')
    753   fields += [ '  optional %sProto %s = %s;\n' %
    754               (policy.name, policy.name, policy.id + RESERVED_IDS) ]
    755 
    756 
    757 def _WriteChromeSettingsProtobuf(policies, os, f):
    758   f.write(CHROME_SETTINGS_PROTO_HEAD)
    759 
    760   fields = []
    761   f.write('// PBs for individual settings.\n\n')
    762   for policy in policies:
    763     # Note: this protobuf also gets the unsupported policies, since it's an
    764     # exaustive list of all the supported user policies on any platform.
    765     if not policy.is_device_only:
    766       _WritePolicyProto(f, policy, fields)
    767 
    768   f.write('// --------------------------------------------------\n'
    769           '// Big wrapper PB containing the above groups.\n\n'
    770           'message ChromeSettingsProto {\n')
    771   f.write(''.join(fields))
    772   f.write('}\n\n')
    773 
    774 
    775 def _WriteCloudPolicyProtobuf(policies, os, f):
    776   f.write(CLOUD_POLICY_PROTO_HEAD)
    777   f.write('message CloudPolicySettings {\n')
    778   for policy in policies:
    779     if policy.is_supported and not policy.is_device_only:
    780       f.write('  optional %sPolicyProto %s = %s;\n' %
    781               (policy.policy_protobuf_type, policy.name,
    782                policy.id + RESERVED_IDS))
    783   f.write('}\n\n')
    784 
    785 
    786 #------------------ protobuf decoder -------------------------------#
    787 
    788 CPP_HEAD = '''
    789 #include <limits>
    790 #include <string>
    791 
    792 #include "base/basictypes.h"
    793 #include "base/callback.h"
    794 #include "base/json/json_reader.h"
    795 #include "base/logging.h"
    796 #include "base/memory/scoped_ptr.h"
    797 #include "base/memory/weak_ptr.h"
    798 #include "base/values.h"
    799 #include "components/policy/core/common/cloud/cloud_external_data_manager.h"
    800 #include "components/policy/core/common/external_data_fetcher.h"
    801 #include "components/policy/core/common/policy_map.h"
    802 #include "policy/policy_constants.h"
    803 #include "policy/proto/cloud_policy.pb.h"
    804 
    805 using google::protobuf::RepeatedPtrField;
    806 
    807 namespace policy {
    808 
    809 namespace em = enterprise_management;
    810 
    811 base::Value* DecodeIntegerValue(google::protobuf::int64 value) {
    812   if (value < std::numeric_limits<int>::min() ||
    813       value > std::numeric_limits<int>::max()) {
    814     LOG(WARNING) << "Integer value " << value
    815                  << " out of numeric limits, ignoring.";
    816     return NULL;
    817   }
    818 
    819   return base::Value::CreateIntegerValue(static_cast<int>(value));
    820 }
    821 
    822 base::ListValue* DecodeStringList(const em::StringList& string_list) {
    823   base::ListValue* list_value = new base::ListValue;
    824   RepeatedPtrField<std::string>::const_iterator entry;
    825   for (entry = string_list.entries().begin();
    826        entry != string_list.entries().end(); ++entry) {
    827     list_value->Append(base::Value::CreateStringValue(*entry));
    828   }
    829   return list_value;
    830 }
    831 
    832 base::Value* DecodeJson(const std::string& json) {
    833   scoped_ptr<base::Value> root(
    834       base::JSONReader::Read(json, base::JSON_ALLOW_TRAILING_COMMAS));
    835 
    836   if (!root)
    837     LOG(WARNING) << "Invalid JSON string, ignoring: " << json;
    838 
    839   // Accept any Value type that parsed as JSON, and leave it to the handler to
    840   // convert and check the concrete type.
    841   return root.release();
    842 }
    843 
    844 void DecodePolicy(const em::CloudPolicySettings& policy,
    845                   base::WeakPtr<CloudExternalDataManager> external_data_manager,
    846                   PolicyMap* map) {
    847 '''
    848 
    849 
    850 CPP_FOOT = '''}
    851 
    852 }  // namespace policy
    853 '''
    854 
    855 
    856 def _CreateValue(type, arg):
    857   if type == 'TYPE_BOOLEAN':
    858     return 'base::Value::CreateBooleanValue(%s)' % arg
    859   elif type == 'TYPE_INTEGER':
    860     return 'DecodeIntegerValue(%s)' % arg
    861   elif type == 'TYPE_STRING':
    862     return 'base::Value::CreateStringValue(%s)' % arg
    863   elif type == 'TYPE_LIST':
    864     return 'DecodeStringList(%s)' % arg
    865   elif type == 'TYPE_DICTIONARY' or type == 'TYPE_EXTERNAL':
    866     return 'DecodeJson(%s)' % arg
    867   else:
    868     raise NotImplementedError('Unknown type %s' % type)
    869 
    870 
    871 def _CreateExternalDataFetcher(type, name):
    872   if type == 'TYPE_EXTERNAL':
    873     return 'new ExternalDataFetcher(external_data_manager, key::k%s)' % name
    874   return 'NULL'
    875 
    876 
    877 def _WritePolicyCode(f, policy):
    878   membername = policy.name.lower()
    879   proto_type = '%sPolicyProto' % policy.policy_protobuf_type
    880   f.write('  if (policy.has_%s()) {\n' % membername)
    881   f.write('    const em::%s& policy_proto = policy.%s();\n' %
    882           (proto_type, membername))
    883   f.write('    if (policy_proto.has_value()) {\n')
    884   f.write('      PolicyLevel level = POLICY_LEVEL_MANDATORY;\n'
    885           '      bool do_set = true;\n'
    886           '      if (policy_proto.has_policy_options()) {\n'
    887           '        do_set = false;\n'
    888           '        switch(policy_proto.policy_options().mode()) {\n'
    889           '          case em::PolicyOptions::MANDATORY:\n'
    890           '            do_set = true;\n'
    891           '            level = POLICY_LEVEL_MANDATORY;\n'
    892           '            break;\n'
    893           '          case em::PolicyOptions::RECOMMENDED:\n'
    894           '            do_set = true;\n'
    895           '            level = POLICY_LEVEL_RECOMMENDED;\n'
    896           '            break;\n'
    897           '          case em::PolicyOptions::UNSET:\n'
    898           '            break;\n'
    899           '        }\n'
    900           '      }\n'
    901           '      if (do_set) {\n')
    902   f.write('        base::Value* value = %s;\n' %
    903           (_CreateValue(policy.policy_type, 'policy_proto.value()')))
    904   # TODO(bartfab): |value| == NULL indicates that the policy value could not be
    905   # parsed successfully. Surface such errors in the UI.
    906   f.write('        if (value) {\n')
    907   f.write('          ExternalDataFetcher* external_data_fetcher = %s;\n' %
    908           _CreateExternalDataFetcher(policy.policy_type, policy.name))
    909   f.write('          map->Set(key::k%s, level, POLICY_SCOPE_USER,\n' %
    910           policy.name)
    911   f.write('                   value, external_data_fetcher);\n'
    912           '        }\n'
    913           '      }\n'
    914           '    }\n'
    915           '  }\n')
    916 
    917 
    918 def _WriteCloudPolicyDecoder(policies, os, f):
    919   f.write(CPP_HEAD)
    920   for policy in policies:
    921     if policy.is_supported and not policy.is_device_only:
    922       _WritePolicyCode(f, policy)
    923   f.write(CPP_FOOT)
    924 
    925 
    926 if __name__ == '__main__':
    927   sys.exit(main())
    928