Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 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 | |
| 6 """ Generator for C++ style thunks """ | |
| 7 | |
| 8 import glob | |
| 9 import os | |
| 10 import re | |
| 11 import sys | |
| 12 | |
| 13 from idl_log import ErrOut, InfoOut, WarnOut | |
| 14 from idl_node import IDLAttribute, IDLNode | |
| 15 from idl_ast import IDLAst | |
| 16 from idl_option import GetOption, Option, ParseOptions | |
| 17 from idl_outfile import IDLOutFile | |
| 18 from idl_parser import ParseFiles | |
| 19 from idl_c_proto import CGen, GetNodeComments, CommentLines, Comment | |
| 20 from idl_generator import Generator, GeneratorByFile | |
| 21 | |
| 22 Option('thunkroot', 'Base directory of output', | |
| 23 default=os.path.join('..', 'thunk')) | |
| 24 | |
| 25 | |
| 26 class TGenError(Exception): | |
| 27 def __init__(self, msg): | |
| 28 self.value = msg | |
| 29 | |
| 30 def __str__(self): | |
| 31 return repr(self.value) | |
| 32 | |
| 33 | |
| 34 def _GetBaseName(filenode): | |
| 35 """Returns the base name for an interface, given the filename.""" | |
|
noelallen1
2012/11/15 22:03:18
FYI: Interface name does not always match source f
teravest
2012/11/16 17:33:36
I changed the method name to _GetBaseFileName to m
dmichael (off chromium)
2012/11/16 18:24:44
Maybe explain what "base name" means? Just an exam
teravest
2012/11/16 18:52:36
Examples added.
| |
| 36 path, name = os.path.split(filenode.GetProperty('NAME')) | |
| 37 name = os.path.splitext(name)[0] | |
| 38 if name.endswith('_dev'): | |
| 39 # Clip off _dev suffix. | |
| 40 name = name[:-len('_dev')] | |
| 41 return name | |
| 42 | |
| 43 | |
| 44 def _GetHeaderFileName(filenode): | |
| 45 """Returns the name for the header for this file.""" | |
| 46 path, name = os.path.split(filenode.GetProperty('NAME')) | |
| 47 name = os.path.splitext(name)[0] | |
| 48 if path: | |
| 49 header = "ppapi/c/%s/%s.h" % (path, name) | |
| 50 else: | |
| 51 header = "ppapi/c/%s.h" % name | |
| 52 return header | |
| 53 | |
| 54 | |
| 55 def _GetThunkFileName(filenode, relpath): | |
| 56 """Returns the thunk file name.""" | |
| 57 path = os.path.split(filenode.GetProperty('NAME'))[0] | |
| 58 name = _GetBaseName(filenode) | |
| 59 # We don't reattach the path for thunk. | |
| 60 if relpath: name = os.path.join(relpath, name) | |
| 61 name = '%s%s' % (name, '_thunk.cc') | |
| 62 return name | |
| 63 | |
| 64 | |
| 65 def _MakeEnterLine(filenode, interface, arg, handle_errors, callback): | |
| 66 """Returns an EnterInstance/EnterResource string for a function.""" | |
| 67 if arg[0] == 'PP_Instance': | |
| 68 if callback is None: | |
| 69 return 'EnterInstance enter(%s);' % arg[1] | |
| 70 else: | |
| 71 return 'EnterInstance enter(%s, %s);' % (arg[1], callback) | |
| 72 elif arg[0] == 'PP_Resource': | |
| 73 api_name = interface.GetName() | |
| 74 if api_name.endswith('_Dev'): | |
| 75 api_name = api_name[:-len('_Dev')] | |
| 76 api_name += '_API' | |
| 77 | |
| 78 enter_type = 'EnterResource<%s>' % api_name | |
| 79 if callback is None: | |
| 80 return '%s enter(%s, %s);' % (enter_type, arg[1], | |
| 81 str(handle_errors).lower()) | |
| 82 else: | |
| 83 return '%s enter(%s, %s, %s);' % (enter_type, arg[1], | |
| 84 callback, | |
| 85 str(handle_errors).lower()) | |
| 86 else: | |
| 87 raise TGenError("Unknown type for _MakeEnterLine: %s" % arg[0]) | |
| 88 | |
| 89 | |
| 90 def _GetShortName(interface, filter_suffixes): | |
| 91 """Return a shorter interface name that matches Is* and Create* functions.""" | |
| 92 parts = interface.GetName().split('_')[1:] | |
| 93 tail = parts[len(parts) - 1] | |
| 94 if tail in filter_suffixes: | |
| 95 parts = parts[:-1] | |
| 96 return ''.join(parts) | |
| 97 | |
| 98 | |
| 99 def _IsTypeCheck(interface, node): | |
| 100 """Returns true if node represents a type-checking function.""" | |
| 101 return node.GetName() == 'Is%s' % _GetShortName(interface, ['Dev', 'Private']) | |
| 102 | |
| 103 | |
| 104 def _GetCreateFuncName(interface): | |
| 105 """Returns the creation function name for an interface.""" | |
| 106 return 'Create%s' % _GetShortName(interface, ['Dev']) | |
| 107 | |
| 108 | |
| 109 def _GetDefaultFailureValue(t): | |
| 110 """Returns the default failure value for a given type. | |
| 111 | |
| 112 Returns None if no default failure value exists for the type. | |
| 113 """ | |
| 114 values = { | |
| 115 'PP_Bool': 'PP_FALSE', | |
| 116 'PP_Resource': '0', | |
| 117 'struct PP_Var': 'PP_MakeUndefined()', | |
| 118 'int32_t': 'enter.retval()', | |
| 119 'uint16_t': '0', | |
| 120 'uint32_t': '0', | |
| 121 'uint64_t': '0', | |
| 122 } | |
| 123 if t in values: | |
| 124 return values[t] | |
| 125 return None | |
| 126 | |
| 127 | |
| 128 def _MakeCreateMemberBody(interface, member, args): | |
| 129 """Returns the body of a Create() function. | |
| 130 | |
| 131 Args: | |
| 132 interface - IDLNode for the interface | |
| 133 member - IDLNode for member function | |
| 134 args - List of arguments for the Create() function | |
| 135 """ | |
| 136 if args[0][0] == 'PP_Resource': | |
| 137 body = ' Resource* object =\n' | |
| 138 body += ' PpapiGlobals::Get()->GetResourceTracker()->' | |
| 139 body += 'GetResource(%s);\n' % args[0][1] | |
| 140 body += ' if (!object)\n' | |
| 141 body += ' return 0;\n' | |
| 142 body += ' EnterResourceCreation enter(object->pp_instance());\n' | |
| 143 elif args[0][0] == 'PP_Instance': | |
| 144 body = ' EnterResourceCreation enter(%s);\n' % args[0][1] | |
| 145 else: | |
| 146 raise TGenError('Unknown arg type for Create(): %s' % args[0][0]) | |
| 147 | |
| 148 body += ' if (enter.failed())\n' | |
| 149 body += ' return 0;\n' | |
| 150 arg_list = ', '.join([a[1] for a in args]) | |
| 151 if member.GetProperty('create_func'): | |
| 152 create_func = member.GetProperty('create_func') | |
| 153 else: | |
| 154 create_func = _GetCreateFuncName(interface) | |
| 155 body += ' return enter.functions()->%s(%s);' % (create_func, | |
| 156 arg_list) | |
| 157 return body | |
| 158 | |
| 159 | |
| 160 def _MakeNormalMemberBody(filenode, node, member, rtype, args): | |
| 161 """Returns the body of a typical function. | |
| 162 | |
| 163 Args: | |
| 164 filenode - IDLNode for the file | |
| 165 node - IDLNode for the interface | |
| 166 member - IDLNode for the member function | |
| 167 rtype - Return type for the member function | |
| 168 args - List of arguments for the member function | |
|
dmichael (off chromium)
2012/11/16 18:24:44
maybe mention that it's a list of 2-element lists,
teravest
2012/11/16 18:52:36
It's actually a list of 4 element tuples, which is
noelallen1
2012/11/16 20:27:28
I've been meaning to make that change for a while.
| |
| 169 """ | |
| 170 is_callback_func = args[len(args) - 1][0] == 'struct PP_CompletionCallback' | |
| 171 | |
| 172 if is_callback_func: | |
| 173 call_args = args[:-1] + [('', 'enter.callback()', '', '')] | |
| 174 else: | |
| 175 call_args = args | |
| 176 | |
| 177 if args[0][0] == 'PP_Instance': | |
| 178 call_arglist = ', '.join(a[1] for a in call_args) | |
| 179 function_container = 'functions' | |
| 180 else: | |
| 181 call_arglist = ', '.join(a[1] for a in call_args[1:]) | |
| 182 function_container = 'object' | |
| 183 | |
| 184 invocation = 'enter.%s()->%s(%s)' % (function_container, | |
| 185 member.GetName(), | |
| 186 call_arglist) | |
| 187 | |
| 188 handle_errors = not (member.GetProperty('report_errors') == 'False') | |
| 189 if is_callback_func: | |
| 190 body = ' %s\n' % _MakeEnterLine(filenode, node, args[0], handle_errors, | |
| 191 args[len(args) - 1][1]) | |
| 192 body += ' if (enter.failed())\n' | |
| 193 value = member.GetProperty('on_failure') | |
| 194 if value is None: | |
| 195 value = 'enter.retval()' | |
| 196 body += ' return %s;\n' % value | |
| 197 body += ' return enter.SetResult(%s);\n' % invocation | |
| 198 elif rtype == 'void': | |
| 199 body = ' %s\n' % _MakeEnterLine(filenode, node, args[0], handle_errors, | |
| 200 None) | |
| 201 body += ' if (enter.succeeded())\n' | |
| 202 body += ' %s;' % invocation | |
| 203 else: | |
| 204 value = member.GetProperty('on_failure') | |
| 205 if value is None: | |
| 206 value = _GetDefaultFailureValue(rtype) | |
| 207 if value is None: | |
| 208 raise TGenError('No default value for rtype %s' % rtype) | |
| 209 | |
| 210 body = ' %s\n' % _MakeEnterLine(filenode, node, args[0], handle_errors, | |
| 211 None) | |
| 212 body += ' if (enter.failed())\n' | |
| 213 body += ' return %s;\n' % value | |
| 214 body += ' return %s;' % invocation | |
| 215 return body | |
| 216 | |
| 217 | |
| 218 def DefineMember(filenode, node, member, release, include_version): | |
| 219 """Returns a definition for a member function of an interface. | |
| 220 | |
| 221 Args: | |
| 222 filenode - IDLNode for the file | |
| 223 node - IDLNode for the interface | |
| 224 member - IDLNode for the member function | |
| 225 release - release to generate | |
| 226 include_version - include the version in emitted function name. | |
| 227 Returns: | |
| 228 A string with the member definition. | |
| 229 """ | |
| 230 cgen = CGen() | |
| 231 rtype, name, arrays, args = cgen.GetComponents(member, release, 'return') | |
| 232 | |
| 233 if _IsTypeCheck(node, member): | |
| 234 body = ' %s\n' % _MakeEnterLine(filenode, node, args[0], False, None) | |
| 235 body += ' return PP_FromBool(enter.succeeded());' | |
| 236 elif member.GetName() == 'Create': | |
| 237 body = _MakeCreateMemberBody(node, member, args) | |
| 238 else: | |
| 239 body = _MakeNormalMemberBody(filenode, node, member, rtype, args) | |
| 240 | |
| 241 signature = cgen.GetSignature(member, release, 'return', func_as_ptr=False, | |
| 242 include_version=include_version) | |
| 243 member_code = '%s {\n%s\n}' % (signature, body) | |
| 244 return cgen.Indent(member_code, tabs=0) | |
| 245 | |
| 246 | |
| 247 class TGen(GeneratorByFile): | |
| 248 def __init__(self): | |
| 249 Generator.__init__(self, 'Thunk', 'tgen', 'Generate the C++ thunk.') | |
| 250 | |
| 251 def GenerateFile(self, filenode, releases, options): | |
| 252 savename = _GetThunkFileName(filenode, GetOption('thunkroot')) | |
| 253 my_min, my_max = filenode.GetMinMax(releases) | |
| 254 if my_min > releases[-1] or my_max < releases[0]: | |
| 255 if os.path.isfile(savename): | |
| 256 print "Removing stale %s for this range." % filenode.GetName() | |
| 257 os.remove(os.path.realpath(savename)) | |
| 258 return False | |
| 259 do_generate = filenode.GetProperty('generate_thunk') | |
| 260 if not do_generate: | |
| 261 return False | |
| 262 | |
| 263 thunk_out = IDLOutFile(savename) | |
| 264 self.GenerateHead(thunk_out, filenode, releases, options) | |
| 265 self.GenerateBody(thunk_out, filenode, releases, options) | |
| 266 self.GenerateTail(thunk_out, filenode, releases, options) | |
| 267 return thunk_out.Close() | |
| 268 | |
| 269 def GenerateHead(self, out, filenode, releases, options): | |
| 270 __pychecker__ = 'unusednames=options' | |
| 271 cgen = CGen() | |
| 272 | |
| 273 cright_node = filenode.GetChildren()[0] | |
| 274 assert(cright_node.IsA('Copyright')) | |
| 275 out.Write('%s\n' % cgen.Copyright(cright_node)) | |
| 276 | |
| 277 # Wrap the From ... modified ... comment if it would be >80 characters. | |
| 278 from_text = 'From %s' % ( | |
| 279 filenode.GetProperty('NAME').replace(os.sep,'/')) | |
| 280 modified_text = 'modified %s.' % ( | |
| 281 filenode.GetProperty('DATETIME')) | |
| 282 if len(from_text) + len(modified_text) < 74: | |
| 283 out.Write('/* %s %s */\n\n' % (from_text, modified_text)) | |
| 284 else: | |
| 285 out.Write('/* %s,\n * %s\n */\n\n' % (from_text, modified_text)) | |
| 286 | |
| 287 | |
| 288 # TODO(teravest): Don't emit includes we don't need. | |
| 289 includes = ['ppapi/c/pp_errors.h', | |
| 290 'ppapi/shared_impl/tracked_callback.h', | |
| 291 'ppapi/thunk/enter.h', | |
| 292 'ppapi/thunk/ppb_instance_api.h', | |
| 293 'ppapi/thunk/resource_creation_api.h', | |
| 294 'ppapi/thunk/thunk.h'] | |
| 295 includes.append(_GetHeaderFileName(filenode)) | |
| 296 includes.append('ppapi/thunk/%s_api.h' % _GetBaseName(filenode)) | |
| 297 for include in sorted(includes): | |
| 298 out.Write('#include "%s"\n' % include) | |
| 299 out.Write('\n') | |
| 300 out.Write('namespace ppapi {\n') | |
| 301 out.Write('namespace thunk {\n') | |
| 302 out.Write('\n') | |
| 303 out.Write('namespace {\n') | |
| 304 out.Write('\n') | |
| 305 | |
| 306 def GenerateBody(self, out, filenode, releases, options): | |
| 307 __pychecker__ = 'unusednames=options' | |
| 308 for node in filenode.GetListOf('Interface'): | |
| 309 # Skip if this node is not in this release | |
| 310 if not node.InReleases(releases): | |
| 311 print "Skipping %s" % node | |
| 312 continue | |
| 313 | |
| 314 # Generate Member functions | |
| 315 if node.IsA('Interface'): | |
| 316 members = [] | |
| 317 for child in node.GetListOf('Member'): | |
| 318 build_list = child.GetUniqueReleases(releases) | |
| 319 # We have to filter out releases this node isn't in. | |
| 320 build_list = filter(lambda r: child.InReleases([r]), build_list) | |
| 321 if len(build_list) == 0: | |
| 322 continue | |
| 323 release = build_list[-1] # Pick the newest release. | |
| 324 member = DefineMember(filenode, node, child, release, False) | |
| 325 if not member: | |
| 326 continue | |
| 327 members.append(member) | |
| 328 for build in build_list[:-1]: | |
| 329 member = DefineMember(filenode, node, child, build, True) | |
| 330 if not member: | |
| 331 continue | |
| 332 members.append(member) | |
| 333 out.Write('\n\n'.join(members)) | |
| 334 | |
| 335 def GenerateTail(self, out, filenode, releases, options): | |
| 336 __pychecker__ = 'unusednames=options' | |
| 337 cgen = CGen() | |
| 338 | |
| 339 version_list = [] | |
| 340 out.Write('\n\n') | |
| 341 for node in filenode.GetListOf('Interface'): | |
| 342 build_list = node.GetUniqueReleases(releases) | |
| 343 for build in build_list: | |
| 344 version = node.GetVersion(build).replace('.', '_') | |
| 345 thunk_name = 'g_' + node.GetName().lower() + '_thunk_' + \ | |
| 346 version | |
| 347 thunk_type = '_'.join((node.GetName(), version)) | |
| 348 version_list.append((thunk_type, thunk_name)) | |
| 349 | |
| 350 out.Write('const %s %s = {\n' % (thunk_type, thunk_name)) | |
| 351 for child in node.GetListOf('Member'): | |
| 352 rtype, name, arrays, args = cgen.GetComponents( | |
| 353 child, build, 'return') | |
| 354 if child.InReleases([build]): # TEST | |
| 355 out.Write(' &%s,\n' % name) | |
| 356 out.Write('};\n\n') | |
| 357 | |
| 358 out.Write('} // namespace\n') | |
| 359 out.Write('\n') | |
| 360 for thunk_type, thunk_name in version_list: | |
| 361 thunk_decl = 'const %s* Get%s_Thunk() {\n' % (thunk_type, thunk_type) | |
| 362 if len(thunk_decl) > 80: | |
| 363 thunk_decl = 'const %s*\n Get%s_Thunk() {\n' % (thunk_type, | |
| 364 thunk_type) | |
| 365 out.Write(thunk_decl) | |
| 366 out.Write(' return &%s;\n' % thunk_name) | |
| 367 out.Write('}\n') | |
| 368 out.Write('\n') | |
| 369 out.Write('} // namespace thunk\n') | |
| 370 out.Write('} // namespace ppapi\n') | |
| 371 | |
| 372 | |
| 373 tgen = TGen() | |
| 374 | |
| 375 | |
| 376 def Main(args): | |
| 377 # Default invocation will verify the golden files are unchanged. | |
| 378 failed = 0 | |
| 379 if not args: | |
| 380 args = ['--wnone', '--diff', '--test', '--thunkroot=.'] | |
| 381 | |
| 382 ParseOptions(args) | |
| 383 | |
| 384 idldir = os.path.split(sys.argv[0])[0] | |
| 385 idldir = os.path.join(idldir, 'test_thunk', '*.idl') | |
| 386 filenames = glob.glob(idldir) | |
| 387 ast = ParseFiles(filenames) | |
| 388 if tgen.GenerateRange(ast, ['M13', 'M14'], {}): | |
| 389 print "Golden file for M13-M14 failed." | |
| 390 failed = 1 | |
| 391 else: | |
| 392 print "Golden file for M13-M14 passed." | |
| 393 | |
| 394 return failed | |
| 395 | |
| 396 | |
| 397 if __name__ == '__main__': | |
| 398 sys.exit(Main(sys.argv[1:])) | |
| OLD | NEW |