Home | History | Annotate | Download | only in json_schema_compiler
      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 """Generator for C++ structs from api json files.
      6 
      7 The purpose of this tool is to remove the need for hand-written code that
      8 converts to and from base::Value types when receiving javascript api calls.
      9 Originally written for generating code for extension apis. Reference schemas
     10 are in chrome/common/extensions/api.
     11 
     12 Usage example:
     13   compiler.py --root /home/Work/src --namespace extensions windows.json
     14     tabs.json
     15   compiler.py --destdir gen --root /home/Work/src
     16     --namespace extensions windows.json tabs.json
     17 """
     18 
     19 import optparse
     20 import os
     21 import shlex
     22 import sys
     23 
     24 from cpp_bundle_generator import CppBundleGenerator
     25 from cpp_generator import CppGenerator
     26 from cpp_type_generator import CppTypeGenerator
     27 from dart_generator import DartGenerator
     28 import json_schema
     29 from cpp_namespace_environment import CppNamespaceEnvironment
     30 from model import Model
     31 from schema_loader import SchemaLoader
     32 
     33 # Names of supported code generators, as specified on the command-line.
     34 # First is default.
     35 GENERATORS = ['cpp', 'cpp-bundle-registration', 'cpp-bundle-schema', 'dart']
     36 
     37 def GenerateSchema(generator_name,
     38                    file_paths,
     39                    root,
     40                    destdir,
     41                    cpp_namespace_pattern,
     42                    dart_overrides_dir,
     43                    impl_dir,
     44                    include_rules):
     45   # Merge the source files into a single list of schemas.
     46   api_defs = []
     47   for file_path in file_paths:
     48     schema = os.path.relpath(file_path, root)
     49     schema_loader = SchemaLoader(
     50         root,
     51         os.path.dirname(schema),
     52         include_rules,
     53         cpp_namespace_pattern)
     54     api_def = schema_loader.LoadSchema(schema)
     55 
     56     # If compiling the C++ model code, delete 'nocompile' nodes.
     57     if generator_name == 'cpp':
     58       api_def = json_schema.DeleteNodes(api_def, 'nocompile')
     59     api_defs.extend(api_def)
     60 
     61   api_model = Model()
     62 
     63   # For single-schema compilation make sure that the first (i.e. only) schema
     64   # is the default one.
     65   default_namespace = None
     66 
     67   # If we have files from multiple source paths, we'll use the common parent
     68   # path as the source directory.
     69   src_path = None
     70 
     71   # Load the actual namespaces into the model.
     72   for target_namespace, file_path in zip(api_defs, file_paths):
     73     relpath = os.path.relpath(os.path.normpath(file_path), root)
     74     namespace = api_model.AddNamespace(target_namespace,
     75                                        relpath,
     76                                        include_compiler_options=True,
     77                                        environment=CppNamespaceEnvironment(
     78                                            cpp_namespace_pattern))
     79 
     80     if default_namespace is None:
     81       default_namespace = namespace
     82 
     83     if src_path is None:
     84       src_path = namespace.source_file_dir
     85     else:
     86       src_path = os.path.commonprefix((src_path, namespace.source_file_dir))
     87 
     88     path, filename = os.path.split(file_path)
     89     filename_base, _ = os.path.splitext(filename)
     90 
     91   # Construct the type generator with all the namespaces in this model.
     92   type_generator = CppTypeGenerator(api_model,
     93                                     schema_loader,
     94                                     default_namespace)
     95   if generator_name in ('cpp-bundle-registration', 'cpp-bundle-schema'):
     96     cpp_bundle_generator = CppBundleGenerator(root,
     97                                               api_model,
     98                                               api_defs,
     99                                               type_generator,
    100                                               cpp_namespace_pattern,
    101                                               src_path,
    102                                               impl_dir)
    103     if generator_name == 'cpp-bundle-registration':
    104       generators = [
    105         ('generated_api_registration.cc',
    106          cpp_bundle_generator.api_cc_generator),
    107         ('generated_api_registration.h', cpp_bundle_generator.api_h_generator),
    108       ]
    109     elif generator_name == 'cpp-bundle-schema':
    110       generators = [
    111         ('generated_schemas.cc', cpp_bundle_generator.schemas_cc_generator),
    112         ('generated_schemas.h', cpp_bundle_generator.schemas_h_generator)
    113       ]
    114   elif generator_name == 'cpp':
    115     cpp_generator = CppGenerator(type_generator)
    116     generators = [
    117       ('%s.h' % filename_base, cpp_generator.h_generator),
    118       ('%s.cc' % filename_base, cpp_generator.cc_generator)
    119     ]
    120   elif generator_name == 'dart':
    121     generators = [
    122       ('%s.dart' % namespace.unix_name, DartGenerator(
    123           dart_overrides_dir))
    124     ]
    125   else:
    126     raise Exception('Unrecognised generator %s' % generator)
    127 
    128   output_code = []
    129   for filename, generator in generators:
    130     code = generator.Generate(namespace).Render()
    131     if destdir:
    132       if generator_name == 'cpp-bundle-registration':
    133         # Function registrations must be output to impl_dir, since they link in
    134         # API implementations.
    135         output_dir = os.path.join(destdir, impl_dir)
    136       else:
    137         output_dir = os.path.join(destdir, src_path)
    138       if not os.path.exists(output_dir):
    139         os.makedirs(output_dir)
    140       with open(os.path.join(output_dir, filename), 'w') as f:
    141         f.write(code)
    142     output_code += [filename, '', code, '']
    143 
    144   return '\n'.join(output_code)
    145 
    146 
    147 if __name__ == '__main__':
    148   parser = optparse.OptionParser(
    149       description='Generates a C++ model of an API from JSON schema',
    150       usage='usage: %prog [option]... schema')
    151   parser.add_option('-r', '--root', default='.',
    152       help='logical include root directory. Path to schema files from specified'
    153       ' dir will be the include path.')
    154   parser.add_option('-d', '--destdir',
    155       help='root directory to output generated files.')
    156   parser.add_option('-n', '--namespace', default='generated_api_schemas',
    157       help='C++ namespace for generated files. e.g extensions::api.')
    158   parser.add_option('-g', '--generator', default=GENERATORS[0],
    159       choices=GENERATORS,
    160       help='The generator to use to build the output code. Supported values are'
    161       ' %s' % GENERATORS)
    162   parser.add_option('-D', '--dart-overrides-dir', dest='dart_overrides_dir',
    163       help='Adds custom dart from files in the given directory (Dart only).')
    164   parser.add_option('-i', '--impl-dir', dest='impl_dir',
    165       help='The root path of all API implementations')
    166   parser.add_option('-I', '--include-rules',
    167       help='A list of paths to include when searching for referenced objects,'
    168       ' with the namespace separated by a \':\'. Example: '
    169       '/foo/bar:Foo::Bar::%(namespace)s')
    170 
    171   (opts, file_paths) = parser.parse_args()
    172 
    173   if not file_paths:
    174     sys.exit(0) # This is OK as a no-op
    175 
    176   # Unless in bundle mode, only one file should be specified.
    177   if (opts.generator not in ('cpp-bundle-registration', 'cpp-bundle-schema') and
    178       len(file_paths) > 1):
    179     # TODO(sashab): Could also just use file_paths[0] here and not complain.
    180     raise Exception(
    181         "Unless in bundle mode, only one file can be specified at a time.")
    182 
    183   def split_path_and_namespace(path_and_namespace):
    184     if ':' not in path_and_namespace:
    185       raise ValueError('Invalid include rule "%s". Rules must be of '
    186                        'the form path:namespace' % path_and_namespace)
    187     return path_and_namespace.split(':', 1)
    188 
    189   include_rules = []
    190   if opts.include_rules:
    191     include_rules = map(split_path_and_namespace,
    192                         shlex.split(opts.include_rules))
    193 
    194   result = GenerateSchema(opts.generator, file_paths, opts.root, opts.destdir,
    195                           opts.namespace, opts.dart_overrides_dir,
    196                           opts.impl_dir, include_rules)
    197   if not opts.destdir:
    198     print result
    199