OLD | NEW |
(Empty) | |
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. |
| 4 |
| 5 import argparse |
| 6 import os.path |
| 7 import sys |
| 8 import re |
| 9 try: |
| 10 import json |
| 11 except ImportError: |
| 12 import simplejson as json |
| 13 |
| 14 # Path handling for libraries and templates |
| 15 # Paths have to be normalized because Jinja uses the exact template path to |
| 16 # determine the hash used in the cache filename, and we need a pre-caching step |
| 17 # to be concurrency-safe. Use absolute path because __file__ is absolute if |
| 18 # module is imported, and relative if executed directly. |
| 19 # If paths differ between pre-caching and individual file compilation, the cache |
| 20 # is regenerated, which causes a race condition and breaks concurrent build, |
| 21 # since some compile processes will try to read the partially written cache. |
| 22 module_path, module_filename = os.path.split(os.path.realpath(__file__)) |
| 23 third_party_dir = os.path.normpath(os.path.join( |
| 24 module_path, os.pardir, os.pardir, os.pardir, 'third_party')) |
| 25 templates_dir = module_path |
| 26 |
| 27 # jinja2 is in chromium's third_party directory. |
| 28 # Insert at 1 so at front to override system libraries, and |
| 29 # after path[0] == invoking script dir |
| 30 sys.path.insert(1, third_party_dir) |
| 31 import jinja2 |
| 32 |
| 33 |
| 34 def ParseArguments(args): |
| 35 """Parses command line arguments and returns a (json_api, output_dir) tuple. |
| 36 """ |
| 37 cmdline_parser = argparse.ArgumentParser() |
| 38 cmdline_parser.add_argument('--protocol', required=True) |
| 39 cmdline_parser.add_argument('--output_dir', required=True) |
| 40 |
| 41 args = cmdline_parser.parse_args(args) |
| 42 with open(args.protocol, 'r') as f: |
| 43 return json.load(f), args.output_dir |
| 44 |
| 45 |
| 46 def ToTitleCase(name): |
| 47 return name[:1].upper() + name[1:] |
| 48 |
| 49 |
| 50 def DashToCamelCase(word): |
| 51 return ''.join(ToTitleCase(x) for x in word.split('-')) |
| 52 |
| 53 |
| 54 def CamelCaseToHackerStyle(name): |
| 55 # Do two passes to insert '_' chars to deal with overlapping matches (e.g., |
| 56 # 'LoLoLoL'). |
| 57 name = re.sub(r'([^_])([A-Z][a-z]+?)', r'\1_\2', name) |
| 58 name = re.sub(r'([^_])([A-Z][a-z]+?)', r'\1_\2', name) |
| 59 return name.lower() |
| 60 |
| 61 |
| 62 def MangleEnum(value): |
| 63 # Rename NULL enumeration values to avoid a clash with the actual NULL. |
| 64 return 'NONE' if value == 'NULL' else value |
| 65 |
| 66 |
| 67 def InitializeJinjaEnv(cache_dir): |
| 68 jinja_env = jinja2.Environment( |
| 69 loader=jinja2.FileSystemLoader(templates_dir), |
| 70 # Bytecode cache is not concurrency-safe unless pre-cached: |
| 71 # if pre-cached this is read-only, but writing creates a race condition. |
| 72 bytecode_cache=jinja2.FileSystemBytecodeCache(cache_dir), |
| 73 keep_trailing_newline=True, # Newline-terminate generated files. |
| 74 lstrip_blocks=True, # So we can indent control flow tags. |
| 75 trim_blocks=True) |
| 76 jinja_env.filters.update({ |
| 77 'to_title_case': ToTitleCase, |
| 78 'dash_to_camelcase': DashToCamelCase, |
| 79 'camelcase_to_hacker_style': CamelCaseToHackerStyle, |
| 80 'mangle_enum': MangleEnum, |
| 81 }) |
| 82 jinja_env.add_extension('jinja2.ext.loopcontrols') |
| 83 return jinja_env |
| 84 |
| 85 |
| 86 def PatchFullQualifiedRefs(json_api): |
| 87 def PatchFullQualifiedRefsInDomain(json, domain_name): |
| 88 if isinstance(json, list): |
| 89 for item in json: |
| 90 PatchFullQualifiedRefsInDomain(item, domain_name) |
| 91 |
| 92 if not isinstance(json, dict): |
| 93 return |
| 94 for key in json: |
| 95 if key != '$ref': |
| 96 PatchFullQualifiedRefsInDomain(json[key], domain_name) |
| 97 continue |
| 98 if not '.' in json['$ref']: |
| 99 json['$ref'] = domain_name + '.' + json['$ref'] |
| 100 |
| 101 for domain in json_api['domains']: |
| 102 PatchFullQualifiedRefsInDomain(domain, domain['domain']) |
| 103 |
| 104 |
| 105 def CreateUserTypeDefinition(domain, type): |
| 106 namespace = CamelCaseToHackerStyle(domain['domain']) |
| 107 return { |
| 108 'return_type': 'std::unique_ptr<headless::%s::%s>' % ( |
| 109 namespace, type['id']), |
| 110 'pass_type': 'std::unique_ptr<headless::%s::%s>' % (namespace, type['id']), |
| 111 'to_raw_type': '*%s', |
| 112 'to_raw_return_type': '%s.get()', |
| 113 'to_pass_type': 'std::move(%s)', |
| 114 'type': 'std::unique_ptr<headless::%s::%s>' % (namespace, type['id']), |
| 115 'raw_type': 'headless::%s::%s' % (namespace, type['id']), |
| 116 'raw_pass_type': 'headless::%s::%s*' % (namespace, type['id']), |
| 117 'raw_return_type': 'headless::%s::%s*' % (namespace, type['id']), |
| 118 } |
| 119 |
| 120 |
| 121 def CreateEnumTypeDefinition(domain_name, type): |
| 122 namespace = CamelCaseToHackerStyle(domain_name) |
| 123 return { |
| 124 'return_type': 'headless::%s::%s' % (namespace, type['id']), |
| 125 'pass_type': 'headless::%s::%s' % (namespace, type['id']), |
| 126 'to_raw_type': '%s', |
| 127 'to_raw_return_type': '%s', |
| 128 'to_pass_type': '%s', |
| 129 'type': 'headless::%s::%s' % (namespace, type['id']), |
| 130 'raw_type': 'headless::%s::%s' % (namespace, type['id']), |
| 131 'raw_pass_type': 'headless::%s::%s' % (namespace, type['id']), |
| 132 'raw_return_type': 'headless::%s::%s' % (namespace, type['id']), |
| 133 } |
| 134 |
| 135 |
| 136 def CreateObjectTypeDefinition(): |
| 137 return { |
| 138 'return_type': 'std::unique_ptr<base::DictionaryValue>', |
| 139 'pass_type': 'std::unique_ptr<base::DictionaryValue>', |
| 140 'to_raw_type': '*%s', |
| 141 'to_raw_return_type': '%s.get()', |
| 142 'to_pass_type': 'std::move(%s)', |
| 143 'type': 'std::unique_ptr<base::DictionaryValue>', |
| 144 'raw_type': 'base::DictionaryValue', |
| 145 'raw_pass_type': 'base::DictionaryValue*', |
| 146 'raw_return_type': 'base::DictionaryValue*', |
| 147 } |
| 148 |
| 149 |
| 150 def WrapObjectTypeDefinition(type): |
| 151 id = type.get('id', 'base::Value') |
| 152 return { |
| 153 'return_type': 'std::unique_ptr<%s>' % id, |
| 154 'pass_type': 'std::unique_ptr<%s>' % id, |
| 155 'to_raw_type': '*%s', |
| 156 'to_raw_return_type': '%s.get()', |
| 157 'to_pass_type': 'std::move(%s)', |
| 158 'type': 'std::unique_ptr<%s>' % id, |
| 159 'raw_type': id, |
| 160 'raw_pass_type': '%s*' % id, |
| 161 'raw_return_type': '%s*' % id, |
| 162 } |
| 163 |
| 164 |
| 165 def CreateAnyTypeDefinition(): |
| 166 return { |
| 167 'return_type': 'std::unique_ptr<base::Value>', |
| 168 'pass_type': 'std::unique_ptr<base::Value>', |
| 169 'to_raw_type': '*%s', |
| 170 'to_raw_return_type': '%s.get()', |
| 171 'to_pass_type': 'std::move(%s)', |
| 172 'type': 'std::unique_ptr<base::Value>', |
| 173 'raw_type': 'base::Value', |
| 174 'raw_pass_type': 'base::Value*', |
| 175 'raw_return_type': 'base::Value*', |
| 176 } |
| 177 |
| 178 |
| 179 def CreateStringTypeDefinition(domain): |
| 180 return { |
| 181 'return_type': 'std::string', |
| 182 'pass_type': 'const std::string&', |
| 183 'to_pass_type': '%s', |
| 184 'to_raw_type': '%s', |
| 185 'to_raw_return_type': '%s', |
| 186 'type': 'std::string', |
| 187 'raw_type': 'std::string', |
| 188 'raw_pass_type': 'const std::string&', |
| 189 'raw_return_type': 'std::string', |
| 190 } |
| 191 |
| 192 |
| 193 def CreatePrimitiveTypeDefinition(type): |
| 194 typedefs = { |
| 195 'number': 'double', |
| 196 'integer': 'int', |
| 197 'boolean': 'bool', |
| 198 'string': 'std::string', |
| 199 } |
| 200 return { |
| 201 'return_type': typedefs[type], |
| 202 'pass_type': typedefs[type], |
| 203 'to_pass_type': '%s', |
| 204 'to_raw_type': '%s', |
| 205 'to_raw_return_type': '%s', |
| 206 'type': typedefs[type], |
| 207 'raw_type': typedefs[type], |
| 208 'raw_pass_type': typedefs[type], |
| 209 'raw_return_type': typedefs[type], |
| 210 } |
| 211 |
| 212 |
| 213 type_definitions = {} |
| 214 type_definitions['number'] = CreatePrimitiveTypeDefinition('number') |
| 215 type_definitions['integer'] = CreatePrimitiveTypeDefinition('integer') |
| 216 type_definitions['boolean'] = CreatePrimitiveTypeDefinition('boolean') |
| 217 type_definitions['string'] = CreatePrimitiveTypeDefinition('string') |
| 218 type_definitions['object'] = CreateObjectTypeDefinition() |
| 219 type_definitions['any'] = CreateAnyTypeDefinition() |
| 220 |
| 221 |
| 222 def WrapArrayDefinition(type): |
| 223 return { |
| 224 'return_type': 'std::vector<%s>' % type['type'], |
| 225 'pass_type': 'std::vector<%s>' % type['type'], |
| 226 'to_raw_type': '%s', |
| 227 'to_raw_return_type': '&%s', |
| 228 'to_pass_type': 'std::move(%s)', |
| 229 'type': 'std::vector<%s>' % type['type'], |
| 230 'raw_type': 'std::vector<%s>' % type['type'], |
| 231 'raw_pass_type': 'std::vector<%s>*' % type['type'], |
| 232 'raw_return_type': 'std::vector<%s>*' % type['type'], |
| 233 } |
| 234 |
| 235 |
| 236 def CreateTypeDefinitions(json_api): |
| 237 for domain in json_api['domains']: |
| 238 if not ('types' in domain): |
| 239 continue |
| 240 for type in domain['types']: |
| 241 if type['type'] == 'object': |
| 242 if 'properties' in type: |
| 243 type_definitions[domain['domain'] + '.' + type['id']] = ( |
| 244 CreateUserTypeDefinition(domain, type)) |
| 245 else: |
| 246 type_definitions[domain['domain'] + '.' + type['id']] = ( |
| 247 CreateObjectTypeDefinition()) |
| 248 elif type['type'] == 'array': |
| 249 items_type = type['items']['type'] |
| 250 type_definitions[domain['domain'] + '.' + type['id']] = ( |
| 251 WrapArrayDefinition(type_definitions[items_type])) |
| 252 elif 'enum' in type: |
| 253 type_definitions[domain['domain'] + '.' + type['id']] = ( |
| 254 CreateEnumTypeDefinition(domain['domain'], type)) |
| 255 type['$ref'] = domain['domain'] + '.' + type['id'] |
| 256 elif type['type'] == 'any': |
| 257 type_definitions[domain['domain'] + '.' + type['id']] = ( |
| 258 CreateAnyTypeDefinition()) |
| 259 else: |
| 260 type_definitions[domain['domain'] + '.' + type['id']] = ( |
| 261 CreatePrimitiveTypeDefinition(type['type'])) |
| 262 |
| 263 |
| 264 def TypeDefinition(name): |
| 265 return type_definitions[name] |
| 266 |
| 267 |
| 268 def ResolveType(property): |
| 269 if '$ref' in property: |
| 270 return type_definitions[property['$ref']] |
| 271 elif property['type'] == 'object': |
| 272 return WrapObjectTypeDefinition(property) |
| 273 elif property['type'] == 'array': |
| 274 return WrapArrayDefinition(ResolveType(property['items'])) |
| 275 return type_definitions[property['type']] |
| 276 |
| 277 |
| 278 def JoinArrays(dict, keys): |
| 279 result = [] |
| 280 for key in keys: |
| 281 if key in dict: |
| 282 result += dict[key] |
| 283 return result |
| 284 |
| 285 |
| 286 def SynthesizeEnumType(domain, owner, type): |
| 287 type['id'] = ToTitleCase(owner) + ToTitleCase(type['name']) |
| 288 type_definitions[domain['domain'] + '.' + type['id']] = ( |
| 289 CreateEnumTypeDefinition(domain['domain'], type)) |
| 290 type['$ref'] = domain['domain'] + '.' + type['id'] |
| 291 domain['types'].append(type) |
| 292 |
| 293 |
| 294 def SynthesizeCommandTypes(json_api): |
| 295 """Generate types for command parameters, return values and enum |
| 296 properties.""" |
| 297 for domain in json_api['domains']: |
| 298 if not 'types' in domain: |
| 299 domain['types'] = [] |
| 300 for type in domain['types']: |
| 301 if type['type'] == 'object': |
| 302 for property in type.get('properties', []): |
| 303 if 'enum' in property and not '$ref' in property: |
| 304 SynthesizeEnumType(domain, type['id'], property) |
| 305 |
| 306 for command in domain.get('commands', []): |
| 307 if 'parameters' in command: |
| 308 for parameter in command['parameters']: |
| 309 if 'enum' in parameter and not '$ref' in parameter: |
| 310 SynthesizeEnumType(domain, command['name'], parameter) |
| 311 parameters_type = { |
| 312 'id': ToTitleCase(command['name']) + 'Params', |
| 313 'type': 'object', |
| 314 'description': 'Parameters for the %s command.' % ToTitleCase( |
| 315 command['name']), |
| 316 'properties': command['parameters'] |
| 317 } |
| 318 domain['types'].append(parameters_type) |
| 319 if 'returns' in command: |
| 320 for parameter in command['returns']: |
| 321 if 'enum' in parameter and not '$ref' in parameter: |
| 322 SynthesizeEnumType(domain, command['name'], parameter) |
| 323 result_type = { |
| 324 'id': ToTitleCase(command['name']) + 'Result', |
| 325 'type': 'object', |
| 326 'description': 'Result for the %s command.' % ToTitleCase( |
| 327 command['name']), |
| 328 'properties': command['returns'] |
| 329 } |
| 330 domain['types'].append(result_type) |
| 331 |
| 332 |
| 333 def Generate(jinja_env, output_dirname, json_api, class_name, file_types): |
| 334 template_context = { |
| 335 'api': json_api, |
| 336 'join_arrays': JoinArrays, |
| 337 'resolve_type': ResolveType, |
| 338 'type_definition': TypeDefinition, |
| 339 } |
| 340 for file_type in file_types: |
| 341 template = jinja_env.get_template('/%s_%s.template' % ( |
| 342 class_name, file_type)) |
| 343 output_file = '%s/%s.%s' % (output_dirname, class_name, file_type) |
| 344 with open(output_file, 'w') as f: |
| 345 f.write(template.render(template_context)) |
| 346 |
| 347 |
| 348 def GenerateDomains(jinja_env, output_dirname, json_api, class_name, |
| 349 file_types): |
| 350 for file_type in file_types: |
| 351 template = jinja_env.get_template('/%s_%s.template' % ( |
| 352 class_name, file_type)) |
| 353 for domain in json_api['domains']: |
| 354 template_context = { |
| 355 'domain': domain, |
| 356 'resolve_type': ResolveType, |
| 357 } |
| 358 domain_name = CamelCaseToHackerStyle(domain['domain']) |
| 359 output_file = '%s/%s.%s' % (output_dirname, domain_name, file_type) |
| 360 with open(output_file, 'w') as f: |
| 361 f.write(template.render(template_context)) |
| 362 |
| 363 |
| 364 if __name__ == '__main__': |
| 365 json_api, output_dirname = ParseArguments(sys.argv[1:]) |
| 366 jinja_env = InitializeJinjaEnv(output_dirname) |
| 367 SynthesizeCommandTypes(json_api) |
| 368 PatchFullQualifiedRefs(json_api) |
| 369 CreateTypeDefinitions(json_api) |
| 370 Generate(jinja_env, output_dirname, json_api, 'types', ['cc', 'h']) |
| 371 Generate(jinja_env, output_dirname, json_api, 'type_conversions', ['h']) |
| 372 GenerateDomains(jinja_env, output_dirname, json_api, 'domain', ['cc', 'h']) |
OLD | NEW |