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.
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.
13 compiler.py --root /home/Work/src --namespace extensions windows.json
15 compiler.py --destdir gen --root /home/Work/src
16 --namespace extensions windows.json tabs.json
24 from cpp_bundle_generator
import CppBundleGenerator
25 from cpp_generator
import CppGenerator
26 from cpp_type_generator
import CppTypeGenerator
27 from js_externs_generator
import JsExternsGenerator
29 from cpp_namespace_environment
import CppNamespaceEnvironment
30 from model
import Model
31 from schema_loader
import SchemaLoader
33 # Names of supported code generators, as specified on the command-line.
35 GENERATORS
= ['cpp', 'cpp-bundle-registration', 'cpp-bundle-schema', 'externs']
37 def GenerateSchema(generator_name
,
41 cpp_namespace_pattern
,
45 # Merge the source files into a single list of schemas.
47 for file_path
in file_paths
:
48 schema
= os
.path
.relpath(file_path
, root
)
49 schema_loader
= SchemaLoader(
51 os
.path
.dirname(schema
),
53 cpp_namespace_pattern
)
54 api_def
= schema_loader
.LoadSchema(schema
)
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
)
61 api_model
= Model(allow_inline_enums
=False)
63 # For single-schema compilation make sure that the first (i.e. only) schema
65 default_namespace
= None
67 # If we have files from multiple source paths, we'll use the common parent
68 # path as the source directory.
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
,
76 include_compiler_options
=True,
77 environment
=CppNamespaceEnvironment(
78 cpp_namespace_pattern
))
80 if default_namespace
is None:
81 default_namespace
= namespace
84 src_path
= namespace
.source_file_dir
86 src_path
= os
.path
.commonprefix((src_path
, namespace
.source_file_dir
))
88 _
, filename
= os
.path
.split(file_path
)
89 filename_base
, _
= os
.path
.splitext(filename
)
91 # Construct the type generator with all the namespaces in this model.
92 type_generator
= CppTypeGenerator(api_model
,
95 if generator_name
in ('cpp-bundle-registration', 'cpp-bundle-schema'):
96 cpp_bundle_generator
= CppBundleGenerator(root
,
100 cpp_namespace_pattern
,
104 if generator_name
== 'cpp-bundle-registration':
106 ('generated_api_registration.cc',
107 cpp_bundle_generator
.api_cc_generator
),
108 ('generated_api_registration.h', cpp_bundle_generator
.api_h_generator
),
110 elif generator_name
== 'cpp-bundle-schema':
112 ('generated_schemas.cc', cpp_bundle_generator
.schemas_cc_generator
),
113 ('generated_schemas.h', cpp_bundle_generator
.schemas_h_generator
)
115 elif generator_name
== 'cpp':
116 cpp_generator
= CppGenerator(type_generator
)
118 ('%s.h' % filename_base
, cpp_generator
.h_generator
),
119 ('%s.cc' % filename_base
, cpp_generator
.cc_generator
)
121 elif generator_name
== 'externs':
123 ('%s_externs.js' % namespace
.unix_name
, JsExternsGenerator())
126 raise Exception('Unrecognised generator %s' % generator_name
)
129 for filename
, generator
in generators
:
130 code
= generator
.Generate(namespace
).Render()
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
)
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
:
142 output_code
+= [filename
, '', code
, '']
144 return '\n'.join(output_code
)
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('-b', '--bundle-name', default
='',
159 help='A string to prepend to generated bundle class names, so that '
160 'multiple bundle rules can be used without conflicting. '
161 'Only used with one of the cpp-bundle generators.')
162 parser
.add_option('-g', '--generator', default
=GENERATORS
[0],
164 help='The generator to use to build the output code. Supported values are'
166 parser
.add_option('-i', '--impl-dir', dest
='impl_dir',
167 help='The root path of all API implementations')
168 parser
.add_option('-I', '--include-rules',
169 help='A list of paths to include when searching for referenced objects,'
170 ' with the namespace separated by a \':\'. Example: '
171 '/foo/bar:Foo::Bar::%(namespace)s')
173 (opts
, file_paths
) = parser
.parse_args()
176 sys
.exit(0) # This is OK as a no-op
178 # Unless in bundle mode, only one file should be specified.
179 if (opts
.generator
not in ('cpp-bundle-registration', 'cpp-bundle-schema') and
180 len(file_paths
) > 1):
181 # TODO(sashab): Could also just use file_paths[0] here and not complain.
183 "Unless in bundle mode, only one file can be specified at a time.")
185 def split_path_and_namespace(path_and_namespace
):
186 if ':' not in path_and_namespace
:
187 raise ValueError('Invalid include rule "%s". Rules must be of '
188 'the form path:namespace' % path_and_namespace
)
189 return path_and_namespace
.split(':', 1)
192 if opts
.include_rules
:
193 include_rules
= map(split_path_and_namespace
,
194 shlex
.split(opts
.include_rules
))
196 result
= GenerateSchema(opts
.generator
, file_paths
, opts
.root
, opts
.destdir
,
197 opts
.namespace
, opts
.bundle_name
, opts
.impl_dir
,