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