1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
|
#!/usr/bin/env python3
# Copyright 2012 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Generator for C++ structs from api json files.
The purpose of this tool is to remove the need for hand-written code that
converts to and from base::Value types when receiving javascript api calls.
Originally written for generating code for extension apis. Reference schemas
are in chrome/common/extensions/api.
Usage example:
compiler.py --root /home/Work/src --namespace extensions windows.json
tabs.json
compiler.py --destdir gen --root /home/Work/src
--namespace extensions windows.json tabs.json
"""
import io
import optparse
import os
import shlex
import sys
from cpp_bundle_generator import CppBundleGenerator
from cpp_generator import CppGenerator
from cpp_namespace_environment import CppNamespaceEnvironment
from cpp_type_generator import CppTypeGenerator
from js_externs_generator import JsExternsGenerator
from js_interface_generator import JsInterfaceGenerator
import json_schema
from model import Model
from namespace_resolver import NamespaceResolver
from schema_loader import SchemaLoader
from ts_definition_generator import TsDefinitionGenerator
# Names of supported code generators, as specified on the command-line.
# First is default.
GENERATORS = [
'cpp',
'cpp-bundle-registration',
'cpp-bundle-schema',
'externs',
'ts_definitions',
'interface',
]
def GenerateSchema(
generator_name,
file_paths,
root,
destdir,
cpp_namespace_pattern,
bundle_name,
impl_dir,
include_rules,
):
# Merge the source files into a single list of schemas.
api_defs = []
for file_path in file_paths:
schema = os.path.relpath(file_path, root)
api_def = SchemaLoader(root).LoadSchema(schema)
# If compiling the C++ model code, delete 'nocompile' nodes.
if generator_name == 'cpp':
api_def = json_schema.DeleteNodes(api_def, 'nocompile')
api_defs.extend(api_def)
api_model = Model(allow_inline_enums=False)
# For single-schema compilation make sure that the first (i.e. only) schema
# is the default one.
default_namespace = None
# If we have files from multiple source paths, we'll use the common parent
# path as the source directory.
src_path = None
# Load the actual namespaces into the model.
for target_namespace, file_path in zip(api_defs, file_paths):
relpath = os.path.relpath(os.path.normpath(file_path), root)
namespace = api_model.AddNamespace(
target_namespace,
relpath,
include_compiler_options=True,
environment=CppNamespaceEnvironment(cpp_namespace_pattern),
)
if default_namespace is None:
default_namespace = namespace
if src_path is None:
src_path = namespace.source_file_dir
else:
src_path = os.path.commonprefix((src_path, namespace.source_file_dir))
_, filename = os.path.split(file_path)
filename_base, _ = os.path.splitext(filename)
# Construct the type generator with all the namespaces in this model.
schema_dir = os.path.dirname(os.path.relpath(file_paths[0], root))
namespace_resolver = NamespaceResolver(
root, schema_dir, include_rules, cpp_namespace_pattern
)
type_generator = CppTypeGenerator(
api_model, namespace_resolver, default_namespace
)
if generator_name in ('cpp-bundle-registration', 'cpp-bundle-schema'):
cpp_bundle_generator = CppBundleGenerator(
root,
api_model,
api_defs,
type_generator,
cpp_namespace_pattern,
bundle_name,
src_path,
impl_dir,
)
if generator_name == 'cpp-bundle-registration':
generators = [
(
'generated_api_registration.cc',
cpp_bundle_generator.api_cc_generator,
),
(
'generated_api_registration.h',
cpp_bundle_generator.api_h_generator,
),
]
elif generator_name == 'cpp-bundle-schema':
generators = [
('generated_schemas.cc', cpp_bundle_generator.schemas_cc_generator),
('generated_schemas.h', cpp_bundle_generator.schemas_h_generator),
]
elif generator_name == 'cpp':
cpp_generator = CppGenerator(type_generator)
generators = [
('%s.h' % filename_base, cpp_generator.h_generator),
('%s.cc' % filename_base, cpp_generator.cc_generator),
]
elif generator_name == 'externs':
generators = [('%s_externs.js' % namespace.unix_name, JsExternsGenerator())]
elif generator_name == 'ts_definitions':
generators = [('%s.d.ts' % namespace.unix_name, TsDefinitionGenerator())]
elif generator_name == 'interface':
generators = [(
'%s_interface.js' % namespace.unix_name,
JsInterfaceGenerator(),
)]
else:
raise Exception('Unrecognised generator %s' % generator_name)
output_code = []
for filename, generator in generators:
code = generator.Generate(namespace).Render()
if destdir:
if generator_name == 'cpp-bundle-registration':
# Function registrations must be output to impl_dir, since they link in
# API implementations.
output_dir = os.path.join(destdir, impl_dir)
else:
output_dir = os.path.join(destdir, src_path)
if not os.path.exists(output_dir):
os.makedirs(output_dir)
generator_filepath = os.path.join(output_dir, filename)
with io.open(generator_filepath, 'w', encoding='utf-8') as f:
f.write(code)
# If multiple files are being output, add the filename for each file.
if len(generators) > 1:
output_code += [filename, '', code, '']
else:
output_code += [code]
return '\n'.join(output_code)
if __name__ == '__main__':
parser = optparse.OptionParser(
description='Generates a C++ model of an API from JSON schema',
usage='usage: %prog [option]... schema',
)
parser.add_option(
'-r',
'--root',
default='.',
help=(
'logical include root directory. Path to schema files from specified'
' dir will be the include path.'
),
)
parser.add_option(
'-d', '--destdir', help='root directory to output generated files.'
)
parser.add_option(
'-n',
'--namespace',
default='generated_api_schemas',
help='C++ namespace for generated files. e.g extensions::api.',
)
parser.add_option(
'-b',
'--bundle-name',
default='',
help=(
'A string to prepend to generated bundle class names, so that '
'multiple bundle rules can be used without conflicting. '
'Only used with one of the cpp-bundle generators.'
),
)
parser.add_option(
'-g',
'--generator',
default=GENERATORS[0],
choices=GENERATORS,
help=(
'The generator to use to build the output code. Supported values are'
' %s'
)
% GENERATORS,
)
parser.add_option(
'-i',
'--impl-dir',
dest='impl_dir',
help='The root path of all API implementations',
)
parser.add_option(
'-I',
'--include-rules',
help=(
'A list of paths to include when searching for referenced objects,'
" with the namespace separated by a ':'. Example: "
'/foo/bar:Foo::Bar::%(namespace)s'
),
)
(opts, file_paths) = parser.parse_args()
if not file_paths:
sys.exit(0) # This is OK as a no-op
# Unless in bundle mode, only one file should be specified.
if (
opts.generator not in ('cpp-bundle-registration', 'cpp-bundle-schema')
and len(file_paths) > 1
):
# TODO(sashab): Could also just use file_paths[0] here and not complain.
raise Exception(
'Unless in bundle mode, only one file can be specified at a time.'
)
def split_path_and_namespace(path_and_namespace):
if ':' not in path_and_namespace:
raise ValueError(
'Invalid include rule "%s". Rules must be of the form path:namespace'
% path_and_namespace
)
return path_and_namespace.split(':', 1)
include_rules = []
if opts.include_rules:
include_rules = list(
map(split_path_and_namespace, shlex.split(opts.include_rules))
)
result = GenerateSchema(
opts.generator,
file_paths,
opts.root,
opts.destdir,
opts.namespace,
opts.bundle_name,
opts.impl_dir,
include_rules,
)
if not opts.destdir:
print(result)
|