Infobar material design refresh: bg color
[chromium-blink-merge.git] / ppapi / generators / idl_thunk.py
blob5df8e6badc6feefcabacf8241ea18502d15f9835
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.
6 """ Generator for C++ style thunks """
8 import glob
9 import os
10 import re
11 import sys
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
22 Option('thunkroot', 'Base directory of output',
23 default=os.path.join('..', 'thunk'))
26 class TGenError(Exception):
27 def __init__(self, msg):
28 self.value = msg
30 def __str__(self):
31 return repr(self.value)
34 class ThunkBodyMetadata(object):
35 """Metadata about thunk body. Used for selecting which headers to emit."""
36 def __init__(self):
37 self._apis = set()
38 self._builtin_includes = set()
39 self._includes = set()
41 def AddApi(self, api):
42 self._apis.add(api)
44 def Apis(self):
45 return self._apis
47 def AddInclude(self, include):
48 self._includes.add(include)
50 def Includes(self):
51 return self._includes
53 def AddBuiltinInclude(self, include):
54 self._builtin_includes.add(include)
56 def BuiltinIncludes(self):
57 return self._builtin_includes
60 def _GetBaseFileName(filenode):
61 """Returns the base name for output files, given the filenode.
63 Examples:
64 'dev/ppb_find_dev.h' -> 'ppb_find_dev'
65 'trusted/ppb_buffer_trusted.h' -> 'ppb_buffer_trusted'
66 """
67 path, name = os.path.split(filenode.GetProperty('NAME'))
68 name = os.path.splitext(name)[0]
69 return name
72 def _GetHeaderFileName(filenode):
73 """Returns the name for the header for this file."""
74 path, name = os.path.split(filenode.GetProperty('NAME'))
75 name = os.path.splitext(name)[0]
76 if path:
77 header = "ppapi/c/%s/%s.h" % (path, name)
78 else:
79 header = "ppapi/c/%s.h" % name
80 return header
83 def _GetThunkFileName(filenode, relpath):
84 """Returns the thunk file name."""
85 path = os.path.split(filenode.GetProperty('NAME'))[0]
86 name = _GetBaseFileName(filenode)
87 # We don't reattach the path for thunk.
88 if relpath: name = os.path.join(relpath, name)
89 name = '%s%s' % (name, '_thunk.cc')
90 return name
93 def _StripFileName(filenode):
94 """Strips path and dev, trusted, and private suffixes from the file name."""
95 api_basename = _GetBaseFileName(filenode)
96 if api_basename.endswith('_dev'):
97 api_basename = api_basename[:-len('_dev')]
98 if api_basename.endswith('_trusted'):
99 api_basename = api_basename[:-len('_trusted')]
100 if api_basename.endswith('_private'):
101 api_basename = api_basename[:-len('_private')]
102 return api_basename
105 def _StripApiName(api_name):
106 """Strips Dev, Private, and Trusted suffixes from the API name."""
107 if api_name.endswith('Trusted'):
108 api_name = api_name[:-len('Trusted')]
109 if api_name.endswith('_Dev'):
110 api_name = api_name[:-len('_Dev')]
111 if api_name.endswith('_Private'):
112 api_name = api_name[:-len('_Private')]
113 return api_name
116 def _MakeEnterLine(filenode, interface, member, arg, handle_errors, callback,
117 meta):
118 """Returns an EnterInstance/EnterResource string for a function."""
119 api_name = _StripApiName(interface.GetName()) + '_API'
120 if member.GetProperty('api'): # Override API name.
121 manually_provided_api = True
122 # TODO(teravest): Automatically guess the API header file.
123 api_name = member.GetProperty('api')
124 else:
125 manually_provided_api = False
127 if arg[0] == 'PP_Instance':
128 if callback is None:
129 arg_string = arg[1]
130 else:
131 arg_string = '%s, %s' % (arg[1], callback)
132 if interface.GetProperty('singleton') or member.GetProperty('singleton'):
133 if not manually_provided_api:
134 meta.AddApi('ppapi/thunk/%s_api.h' % _StripFileName(filenode))
135 return 'EnterInstanceAPI<%s> enter(%s);' % (api_name, arg_string)
136 else:
137 return 'EnterInstance enter(%s);' % arg_string
138 elif arg[0] == 'PP_Resource':
139 enter_type = 'EnterResource<%s>' % api_name
140 if not manually_provided_api:
141 meta.AddApi('ppapi/thunk/%s_api.h' % _StripFileName(filenode))
142 if callback is None:
143 return '%s enter(%s, %s);' % (enter_type, arg[1],
144 str(handle_errors).lower())
145 else:
146 return '%s enter(%s, %s, %s);' % (enter_type, arg[1],
147 callback,
148 str(handle_errors).lower())
149 else:
150 raise TGenError("Unknown type for _MakeEnterLine: %s" % arg[0])
153 def _GetShortName(interface, filter_suffixes):
154 """Return a shorter interface name that matches Is* and Create* functions."""
155 parts = interface.GetName().split('_')[1:]
156 tail = parts[len(parts) - 1]
157 if tail in filter_suffixes:
158 parts = parts[:-1]
159 return ''.join(parts)
162 def _IsTypeCheck(interface, node, args):
163 """Returns true if node represents a type-checking function."""
164 if len(args) == 0 or args[0][0] != 'PP_Resource':
165 return False
166 return node.GetName() == 'Is%s' % _GetShortName(interface, ['Dev', 'Private'])
169 def _GetCreateFuncName(interface):
170 """Returns the creation function name for an interface."""
171 return 'Create%s' % _GetShortName(interface, ['Dev'])
174 def _GetDefaultFailureValue(t):
175 """Returns the default failure value for a given type.
177 Returns None if no default failure value exists for the type.
179 values = {
180 'PP_Bool': 'PP_FALSE',
181 'PP_Resource': '0',
182 'struct PP_Var': 'PP_MakeUndefined()',
183 'float': '0.0f',
184 'int32_t': 'enter.retval()',
185 'uint16_t': '0',
186 'uint32_t': '0',
187 'uint64_t': '0',
188 'void*': 'NULL'
190 if t in values:
191 return values[t]
192 return None
195 def _MakeCreateMemberBody(interface, member, args):
196 """Returns the body of a Create() function.
198 Args:
199 interface - IDLNode for the interface
200 member - IDLNode for member function
201 args - List of arguments for the Create() function
203 if args[0][0] == 'PP_Resource':
204 body = 'Resource* object =\n'
205 body += ' PpapiGlobals::Get()->GetResourceTracker()->'
206 body += 'GetResource(%s);\n' % args[0][1]
207 body += 'if (!object)\n'
208 body += ' return 0;\n'
209 body += 'EnterResourceCreation enter(object->pp_instance());\n'
210 elif args[0][0] == 'PP_Instance':
211 body = 'EnterResourceCreation enter(%s);\n' % args[0][1]
212 else:
213 raise TGenError('Unknown arg type for Create(): %s' % args[0][0])
215 body += 'if (enter.failed())\n'
216 body += ' return 0;\n'
217 arg_list = ', '.join([a[1] for a in args])
218 if member.GetProperty('create_func'):
219 create_func = member.GetProperty('create_func')
220 else:
221 create_func = _GetCreateFuncName(interface)
222 body += 'return enter.functions()->%s(%s);' % (create_func,
223 arg_list)
224 return body
227 def _GetOutputParams(member, release):
228 """Returns output parameters (and their types) for a member function.
230 Args:
231 member - IDLNode for the member function
232 release - Release to get output parameters for
233 Returns:
234 A list of name strings for all output parameters of the member
235 function.
237 out_params = []
238 callnode = member.GetOneOf('Callspec')
239 if callnode:
240 cgen = CGen()
241 for param in callnode.GetListOf('Param'):
242 mode = cgen.GetParamMode(param)
243 if mode == 'out':
244 # We use the 'store' mode when getting the parameter type, since we
245 # need to call sizeof() for memset().
246 _, pname, _, _ = cgen.GetComponents(param, release, 'store')
247 out_params.append(pname)
248 return out_params
251 def _MakeNormalMemberBody(filenode, release, node, member, rtype, args,
252 include_version, meta):
253 """Returns the body of a typical function.
255 Args:
256 filenode - IDLNode for the file
257 release - release to generate body for
258 node - IDLNode for the interface
259 member - IDLNode for the member function
260 rtype - Return type for the member function
261 args - List of 4-tuple arguments for the member function
262 include_version - whether to include the version in the invocation
263 meta - ThunkBodyMetadata for header hints
265 if len(args) == 0:
266 # Calling into the "Shared" code for the interface seems like a reasonable
267 # heuristic when we don't have any arguments; some thunk code follows this
268 # convention today.
269 meta.AddApi('ppapi/shared_impl/%s_shared.h' % _StripFileName(filenode))
270 return 'return %s::%s();' % (_StripApiName(node.GetName()) + '_Shared',
271 member.GetName())
273 is_callback_func = args[len(args) - 1][0] == 'struct PP_CompletionCallback'
275 if is_callback_func:
276 call_args = args[:-1] + [('', 'enter.callback()', '', '')]
277 meta.AddInclude('ppapi/c/pp_completion_callback.h')
278 else:
279 call_args = args
281 if args[0][0] == 'PP_Instance':
282 call_arglist = ', '.join(a[1] for a in call_args)
283 function_container = 'functions'
284 elif args[0][0] == 'PP_Resource':
285 call_arglist = ', '.join(a[1] for a in call_args[1:])
286 function_container = 'object'
287 else:
288 # Calling into the "Shared" code for the interface seems like a reasonable
289 # heuristic when the first argument isn't a PP_Instance or a PP_Resource;
290 # some thunk code follows this convention today.
291 meta.AddApi('ppapi/shared_impl/%s_shared.h' % _StripFileName(filenode))
292 return 'return %s::%s(%s);' % (_StripApiName(node.GetName()) + '_Shared',
293 member.GetName(),
294 ', '.join(a[1] for a in args))
296 function_name = member.GetName()
297 if include_version:
298 version = node.GetVersion(release).replace('.', '_')
299 function_name += version
301 invocation = 'enter.%s()->%s(%s)' % (function_container,
302 function_name,
303 call_arglist)
305 handle_errors = not (member.GetProperty('report_errors') == 'False')
306 out_params = _GetOutputParams(member, release)
307 if is_callback_func:
308 body = '%s\n' % _MakeEnterLine(filenode, node, member, args[0],
309 handle_errors, args[len(args) - 1][1], meta)
310 failure_value = member.GetProperty('on_failure')
311 if failure_value is None:
312 failure_value = 'enter.retval()'
313 failure_return = 'return %s;' % failure_value
314 success_return = 'return enter.SetResult(%s);' % invocation
315 elif rtype == 'void':
316 body = '%s\n' % _MakeEnterLine(filenode, node, member, args[0],
317 handle_errors, None, meta)
318 failure_return = 'return;'
319 success_return = '%s;' % invocation # We don't return anything for void.
320 else:
321 body = '%s\n' % _MakeEnterLine(filenode, node, member, args[0],
322 handle_errors, None, meta)
323 failure_value = member.GetProperty('on_failure')
324 if failure_value is None:
325 failure_value = _GetDefaultFailureValue(rtype)
326 if failure_value is None:
327 raise TGenError('There is no default value for rtype %s. '
328 'Maybe you should provide an on_failure attribute '
329 'in the IDL file.' % rtype)
330 failure_return = 'return %s;' % failure_value
331 success_return = 'return %s;' % invocation
333 if member.GetProperty('always_set_output_parameters'):
334 body += 'if (enter.failed()) {\n'
335 for param in out_params:
336 body += ' memset(%s, 0, sizeof(*%s));\n' % (param, param)
337 body += ' %s\n' % failure_return
338 body += '}\n'
339 body += '%s' % success_return
340 meta.AddBuiltinInclude('string.h')
341 else:
342 body += 'if (enter.failed())\n'
343 body += ' %s\n' % failure_return
344 body += '%s' % success_return
345 return body
348 def DefineMember(filenode, node, member, release, include_version, meta):
349 """Returns a definition for a member function of an interface.
351 Args:
352 filenode - IDLNode for the file
353 node - IDLNode for the interface
354 member - IDLNode for the member function
355 release - release to generate
356 include_version - include the version in emitted function name.
357 meta - ThunkMetadata for header hints
358 Returns:
359 A string with the member definition.
361 cgen = CGen()
362 rtype, name, arrays, args = cgen.GetComponents(member, release, 'return')
363 log_body = '\"%s::%s()\";' % (node.GetName(),
364 cgen.GetStructName(member, release,
365 include_version))
366 if len(log_body) > 69: # Prevent lines over 80 characters.
367 body = 'VLOG(4) <<\n'
368 body += ' %s\n' % log_body
369 else:
370 body = 'VLOG(4) << %s\n' % log_body
372 if _IsTypeCheck(node, member, args):
373 body += '%s\n' % _MakeEnterLine(filenode, node, member, args[0], False,
374 None, meta)
375 body += 'return PP_FromBool(enter.succeeded());'
376 elif member.GetName() == 'Create' or member.GetName() == 'CreateTrusted':
377 body += _MakeCreateMemberBody(node, member, args)
378 else:
379 body += _MakeNormalMemberBody(filenode, release, node, member, rtype, args,
380 include_version, meta)
382 signature = cgen.GetSignature(member, release, 'return', func_as_ptr=False,
383 include_version=include_version)
384 return '%s\n%s\n}' % (cgen.Indent('%s {' % signature, tabs=0),
385 cgen.Indent(body, tabs=1))
388 def _IsNewestMember(member, members, releases):
389 """Returns true if member is the newest node with its name in members.
391 Currently, every node in the AST only has one version. This means that we
392 will have two sibling nodes with the same name to represent different
393 versions.
394 See http://crbug.com/157017 .
396 Special handling is required for nodes which share their name with others,
397 but aren't the newest version in the IDL.
399 Args:
400 member - The member which is checked if it's newest
401 members - The list of members to inspect
402 releases - The set of releases to check for versions in.
404 build_list = member.GetUniqueReleases(releases)
405 release = build_list[0] # Pick the oldest release.
406 same_name_siblings = filter(
407 lambda n: str(n) == str(member) and n != member, members)
409 for s in same_name_siblings:
410 sibling_build_list = s.GetUniqueReleases(releases)
411 sibling_release = sibling_build_list[0]
412 if sibling_release > release:
413 return False
414 return True
417 class TGen(GeneratorByFile):
418 def __init__(self):
419 Generator.__init__(self, 'Thunk', 'tgen', 'Generate the C++ thunk.')
421 def GenerateFile(self, filenode, releases, options):
422 savename = _GetThunkFileName(filenode, GetOption('thunkroot'))
423 my_min, my_max = filenode.GetMinMax(releases)
424 if my_min > releases[-1] or my_max < releases[0]:
425 if os.path.isfile(savename):
426 print "Removing stale %s for this range." % filenode.GetName()
427 os.remove(os.path.realpath(savename))
428 return False
429 do_generate = filenode.GetProperty('generate_thunk')
430 if not do_generate:
431 return False
433 thunk_out = IDLOutFile(savename)
434 body, meta = self.GenerateBody(thunk_out, filenode, releases, options)
435 # TODO(teravest): How do we handle repeated values?
436 if filenode.GetProperty('thunk_include'):
437 meta.AddInclude(filenode.GetProperty('thunk_include'))
438 self.WriteHead(thunk_out, filenode, releases, options, meta)
439 thunk_out.Write('\n\n'.join(body))
440 self.WriteTail(thunk_out, filenode, releases, options)
441 thunk_out.ClangFormat()
442 return thunk_out.Close()
444 def WriteHead(self, out, filenode, releases, options, meta):
445 __pychecker__ = 'unusednames=options'
446 cgen = CGen()
448 cright_node = filenode.GetChildren()[0]
449 assert(cright_node.IsA('Copyright'))
450 out.Write('%s\n' % cgen.Copyright(cright_node, cpp_style=True))
452 from_text = 'From %s' % (
453 filenode.GetProperty('NAME').replace(os.sep,'/'))
454 modified_text = 'modified %s.' % (
455 filenode.GetProperty('DATETIME'))
456 out.Write('// %s %s\n\n' % (from_text, modified_text))
458 if meta.BuiltinIncludes():
459 for include in sorted(meta.BuiltinIncludes()):
460 out.Write('#include <%s>\n' % include)
461 out.Write('\n')
463 # TODO(teravest): Don't emit includes we don't need.
464 includes = ['ppapi/c/pp_errors.h',
465 'ppapi/shared_impl/tracked_callback.h',
466 'ppapi/thunk/enter.h',
467 'ppapi/thunk/ppapi_thunk_export.h']
468 includes.append(_GetHeaderFileName(filenode))
469 for api in meta.Apis():
470 includes.append('%s' % api.lower())
471 for i in meta.Includes():
472 includes.append(i)
473 for include in sorted(includes):
474 out.Write('#include "%s"\n' % include)
475 out.Write('\n')
476 out.Write('namespace ppapi {\n')
477 out.Write('namespace thunk {\n')
478 out.Write('\n')
479 out.Write('namespace {\n')
480 out.Write('\n')
482 def GenerateBody(self, out, filenode, releases, options):
483 """Generates a member function lines to be written and metadata.
485 Returns a tuple of (body, meta) where:
486 body - a list of lines with member function bodies
487 meta - a ThunkMetadata instance for hinting which headers are needed.
489 __pychecker__ = 'unusednames=options'
490 out_members = []
491 meta = ThunkBodyMetadata()
492 for node in filenode.GetListOf('Interface'):
493 # Skip if this node is not in this release
494 if not node.InReleases(releases):
495 print "Skipping %s" % node
496 continue
498 # Generate Member functions
499 if node.IsA('Interface'):
500 members = node.GetListOf('Member')
501 for child in members:
502 build_list = child.GetUniqueReleases(releases)
503 # We have to filter out releases this node isn't in.
504 build_list = filter(lambda r: child.InReleases([r]), build_list)
505 if len(build_list) == 0:
506 continue
507 release = build_list[-1]
508 include_version = not _IsNewestMember(child, members, releases)
509 member = DefineMember(filenode, node, child, release, include_version,
510 meta)
511 if not member:
512 continue
513 out_members.append(member)
514 return (out_members, meta)
516 def WriteTail(self, out, filenode, releases, options):
517 __pychecker__ = 'unusednames=options'
518 cgen = CGen()
520 version_list = []
521 out.Write('\n\n')
522 for node in filenode.GetListOf('Interface'):
523 build_list = node.GetUniqueReleases(releases)
524 for build in build_list:
525 version = node.GetVersion(build).replace('.', '_')
526 thunk_name = 'g_' + node.GetName().lower() + '_thunk_' + \
527 version
528 thunk_type = '_'.join((node.GetName(), version))
529 version_list.append((thunk_type, thunk_name))
531 out.Write('const %s %s = {\n' % (thunk_type, thunk_name))
532 generated_functions = []
533 members = node.GetListOf('Member')
534 for child in members:
535 rtype, name, arrays, args = cgen.GetComponents(
536 child, build, 'return')
537 if child.InReleases([build]):
538 if not _IsNewestMember(child, members, releases):
539 version = child.GetVersion(
540 child.first_release[build]).replace('.', '_')
541 name += '_' + version
542 generated_functions.append(name)
543 out.Write(',\n'.join([' &%s' % f for f in generated_functions]))
544 out.Write('\n};\n\n')
546 out.Write('} // namespace\n')
547 out.Write('\n')
548 for thunk_type, thunk_name in version_list:
549 out.Write('PPAPI_THUNK_EXPORT const %s* Get%s_Thunk() {\n' %
550 (thunk_type, thunk_type))
551 out.Write(' return &%s;\n' % thunk_name)
552 out.Write('}\n')
553 out.Write('\n')
554 out.Write('} // namespace thunk\n')
555 out.Write('} // namespace ppapi\n')
558 tgen = TGen()
561 def Main(args):
562 # Default invocation will verify the golden files are unchanged.
563 failed = 0
564 if not args:
565 args = ['--wnone', '--diff', '--test', '--thunkroot=.']
567 ParseOptions(args)
569 idldir = os.path.split(sys.argv[0])[0]
570 idldir = os.path.join(idldir, 'test_thunk', '*.idl')
571 filenames = glob.glob(idldir)
572 ast = ParseFiles(filenames)
573 if tgen.GenerateRange(ast, ['M13', 'M14', 'M15'], {}):
574 print "Golden file for M13-M15 failed."
575 failed = 1
576 else:
577 print "Golden file for M13-M15 passed."
579 return failed
582 if __name__ == '__main__':
583 sys.exit(Main(sys.argv[1:]))