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 sys
     22 
     23 from cpp_bundle_generator import CppBundleGenerator
     24 from cpp_generator import CppGenerator
     25 from cpp_type_generator import CppTypeGenerator
     26 from dart_generator import DartGenerator
     27 import json_schema
     28 from model import Model, UnixName
     29 from schema_loader import SchemaLoader
     30 
     31 # Names of supported code generators, as specified on the command-line.
     32 # First is default.
     33 GENERATORS = ['cpp', 'cpp-bundle', 'dart']
     34 
     35 def GenerateSchema(generator,
     36                    filenames,
     37                    root,
     38                    destdir,
     39                    root_namespace,
     40                    dart_overrides_dir):
     41   schema_loader = SchemaLoader(os.path.dirname(os.path.relpath(
     42       os.path.normpath(filenames[0]), root)))
     43   # Merge the source files into a single list of schemas.
     44   api_defs = []
     45   for filename in filenames:
     46     schema = os.path.normpath(filename)
     47     schema_filename, schema_extension = os.path.splitext(schema)
     48     path, short_filename = os.path.split(schema_filename)
     49     api_def = schema_loader.LoadSchema(schema)
     50 
     51     # If compiling the C++ model code, delete 'nocompile' nodes.
     52     if generator == 'cpp':
     53       api_def = json_schema.DeleteNodes(api_def, 'nocompile')
     54     api_defs.extend(api_def)
     55 
     56   api_model = Model()
     57 
     58   # For single-schema compilation make sure that the first (i.e. only) schema
     59   # is the default one.
     60   default_namespace = None
     61 
     62   # Load the actual namespaces into the model.
     63   for target_namespace, schema_filename in zip(api_defs, filenames):
     64     relpath = os.path.relpath(os.path.normpath(schema_filename), root)
     65     namespace = api_model.AddNamespace(target_namespace,
     66                                        relpath,
     67                                        include_compiler_options=True)
     68     if default_namespace is None:
     69       default_namespace = namespace
     70 
     71     path, filename = os.path.split(schema_filename)
     72     short_filename, extension = os.path.splitext(filename)
     73 
     74     # Filenames are checked against the unix_names of the namespaces they
     75     # generate because the gyp uses the names of the JSON files to generate
     76     # the names of the .cc and .h files. We want these to be using unix_names.
     77     if namespace.unix_name != short_filename:
     78       sys.exit("Filename %s is illegal. Name files using unix_hacker style." %
     79                schema_filename)
     80 
     81     # The output filename must match the input filename for gyp to deal with it
     82     # properly.
     83     out_file = namespace.unix_name
     84 
     85   # Construct the type generator with all the namespaces in this model.
     86   type_generator = CppTypeGenerator(api_model,
     87                                     schema_loader,
     88                                     default_namespace=default_namespace)
     89 
     90   if generator == 'cpp-bundle':
     91     cpp_bundle_generator = CppBundleGenerator(root,
     92                                               api_model,
     93                                               api_defs,
     94                                               type_generator,
     95                                               root_namespace)
     96     generators = [
     97       ('generated_api.cc', cpp_bundle_generator.api_cc_generator),
     98       ('generated_api.h', cpp_bundle_generator.api_h_generator),
     99       ('generated_schemas.cc', cpp_bundle_generator.schemas_cc_generator),
    100       ('generated_schemas.h', cpp_bundle_generator.schemas_h_generator)
    101     ]
    102   elif generator == 'cpp':
    103     cpp_generator = CppGenerator(type_generator, root_namespace)
    104     generators = [
    105       ('%s.h' % namespace.unix_name, cpp_generator.h_generator),
    106       ('%s.cc' % namespace.unix_name, cpp_generator.cc_generator)
    107     ]
    108   elif generator == 'dart':
    109     generators = [
    110       ('%s.dart' % namespace.unix_name, DartGenerator(
    111           dart_overrides_dir))
    112     ]
    113   else:
    114     raise Exception('Unrecognised generator %s' % generator)
    115 
    116   output_code = []
    117   for filename, generator in generators:
    118     code = generator.Generate(namespace).Render()
    119     if destdir:
    120       with open(os.path.join(destdir, namespace.source_file_dir,
    121           filename), 'w') as f:
    122         f.write(code)
    123     output_code += [filename, '', code, '']
    124 
    125   return '\n'.join(output_code)
    126 
    127 if __name__ == '__main__':
    128   parser = optparse.OptionParser(
    129       description='Generates a C++ model of an API from JSON schema',
    130       usage='usage: %prog [option]... schema')
    131   parser.add_option('-r', '--root', default='.',
    132       help='logical include root directory. Path to schema files from specified'
    133       'dir will be the include path.')
    134   parser.add_option('-d', '--destdir',
    135       help='root directory to output generated files.')
    136   parser.add_option('-n', '--namespace', default='generated_api_schemas',
    137       help='C++ namespace for generated files. e.g extensions::api.')
    138   parser.add_option('-g', '--generator', default=GENERATORS[0],
    139       choices=GENERATORS,
    140       help='The generator to use to build the output code. Supported values are'
    141       ' %s' % GENERATORS)
    142   parser.add_option('-D', '--dart-overrides-dir', dest='dart_overrides_dir',
    143       help='Adds custom dart from files in the given directory (Dart only).')
    144 
    145   (opts, filenames) = parser.parse_args()
    146 
    147   if not filenames:
    148     sys.exit(0) # This is OK as a no-op
    149 
    150   # Unless in bundle mode, only one file should be specified.
    151   if opts.generator != 'cpp-bundle' and len(filenames) > 1:
    152     # TODO(sashab): Could also just use filenames[0] here and not complain.
    153     raise Exception(
    154         "Unless in bundle mode, only one file can be specified at a time.")
    155 
    156   result = GenerateSchema(opts.generator, filenames, opts.root, opts.destdir,
    157                           opts.namespace, opts.dart_overrides_dir)
    158   if not opts.destdir:
    159     print result
    160