blob: b3f59d1d5feeddbaa2c9ee0688b42f3bf3d86a6f [file] [log] [blame]
[email protected]8c311f02012-11-17 16:01:321#!/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
8import glob
9import os
10import re
11import sys
12
13from idl_log import ErrOut, InfoOut, WarnOut
14from idl_node import IDLAttribute, IDLNode
15from idl_ast import IDLAst
16from idl_option import GetOption, Option, ParseOptions
17from idl_outfile import IDLOutFile
18from idl_parser import ParseFiles
19from idl_c_proto import CGen, GetNodeComments, CommentLines, Comment
20from idl_generator import Generator, GeneratorByFile
21
22Option('thunkroot', 'Base directory of output',
23 default=os.path.join('..', 'thunk'))
24
25
26class TGenError(Exception):
27 def __init__(self, msg):
28 self.value = msg
29
30 def __str__(self):
31 return repr(self.value)
32
33
[email protected]cba2c4302012-11-20 18:18:1334class ThunkBodyMetadata(object):
35 """Metadata about thunk body. Used for selecting which headers to emit."""
36 def __init__(self):
37 self._apis = set()
[email protected]8f2a08d52012-12-08 00:10:5038 self._includes = set()
[email protected]cba2c4302012-11-20 18:18:1339
40 def AddApi(self, api):
41 self._apis.add(api)
42
43 def Apis(self):
44 return self._apis
45
[email protected]8f2a08d52012-12-08 00:10:5046 def AddInclude(self, include):
47 self._includes.add(include)
48
49 def Includes(self):
50 return self._includes
51
[email protected]cba2c4302012-11-20 18:18:1352
[email protected]8c311f02012-11-17 16:01:3253def _GetBaseFileName(filenode):
54 """Returns the base name for output files, given the filenode.
55
56 Examples:
[email protected]a5b6ace2013-02-08 21:09:5257 'dev/ppb_find_dev.h' -> 'ppb_find_dev'
[email protected]8c311f02012-11-17 16:01:3258 'trusted/ppb_buffer_trusted.h' -> 'ppb_buffer_trusted'
59 """
60 path, name = os.path.split(filenode.GetProperty('NAME'))
61 name = os.path.splitext(name)[0]
[email protected]8c311f02012-11-17 16:01:3262 return name
63
64
65def _GetHeaderFileName(filenode):
66 """Returns the name for the header for this file."""
67 path, name = os.path.split(filenode.GetProperty('NAME'))
68 name = os.path.splitext(name)[0]
69 if path:
70 header = "ppapi/c/%s/%s.h" % (path, name)
71 else:
72 header = "ppapi/c/%s.h" % name
73 return header
74
75
76def _GetThunkFileName(filenode, relpath):
77 """Returns the thunk file name."""
78 path = os.path.split(filenode.GetProperty('NAME'))[0]
79 name = _GetBaseFileName(filenode)
80 # We don't reattach the path for thunk.
81 if relpath: name = os.path.join(relpath, name)
82 name = '%s%s' % (name, '_thunk.cc')
83 return name
84
85
[email protected]cba2c4302012-11-20 18:18:1386def _MakeEnterLine(filenode, interface, arg, handle_errors, callback, meta):
[email protected]8c311f02012-11-17 16:01:3287 """Returns an EnterInstance/EnterResource string for a function."""
88 if arg[0] == 'PP_Instance':
89 if callback is None:
90 return 'EnterInstance enter(%s);' % arg[1]
91 else:
92 return 'EnterInstance enter(%s, %s);' % (arg[1], callback)
93 elif arg[0] == 'PP_Resource':
94 api_name = interface.GetName()
95 if api_name.endswith('_Dev'):
96 api_name = api_name[:-len('_Dev')]
97 api_name += '_API'
98
99 enter_type = 'EnterResource<%s>' % api_name
[email protected]13cc22f32012-11-21 18:04:12100 # The API header matches the file name, not the interface name.
[email protected]a5b6ace2013-02-08 21:09:52101 api_basename = _GetBaseFileName(filenode)
102 if api_basename.endswith('_dev'):
103 # Clip off _dev suffix.
104 api_basename = api_basename[:-len('_dev')]
105 meta.AddApi(api_basename + '_api')
[email protected]13cc22f32012-11-21 18:04:12106
[email protected]8c311f02012-11-17 16:01:32107 if callback is None:
108 return '%s enter(%s, %s);' % (enter_type, arg[1],
109 str(handle_errors).lower())
110 else:
111 return '%s enter(%s, %s, %s);' % (enter_type, arg[1],
112 callback,
113 str(handle_errors).lower())
114 else:
115 raise TGenError("Unknown type for _MakeEnterLine: %s" % arg[0])
116
117
118def _GetShortName(interface, filter_suffixes):
119 """Return a shorter interface name that matches Is* and Create* functions."""
120 parts = interface.GetName().split('_')[1:]
121 tail = parts[len(parts) - 1]
122 if tail in filter_suffixes:
123 parts = parts[:-1]
124 return ''.join(parts)
125
126
127def _IsTypeCheck(interface, node):
128 """Returns true if node represents a type-checking function."""
129 return node.GetName() == 'Is%s' % _GetShortName(interface, ['Dev', 'Private'])
130
131
132def _GetCreateFuncName(interface):
133 """Returns the creation function name for an interface."""
134 return 'Create%s' % _GetShortName(interface, ['Dev'])
135
136
137def _GetDefaultFailureValue(t):
138 """Returns the default failure value for a given type.
139
140 Returns None if no default failure value exists for the type.
141 """
142 values = {
143 'PP_Bool': 'PP_FALSE',
144 'PP_Resource': '0',
145 'struct PP_Var': 'PP_MakeUndefined()',
146 'int32_t': 'enter.retval()',
147 'uint16_t': '0',
148 'uint32_t': '0',
149 'uint64_t': '0',
150 }
151 if t in values:
152 return values[t]
153 return None
154
155
156def _MakeCreateMemberBody(interface, member, args):
157 """Returns the body of a Create() function.
158
159 Args:
160 interface - IDLNode for the interface
161 member - IDLNode for member function
162 args - List of arguments for the Create() function
163 """
164 if args[0][0] == 'PP_Resource':
165 body = ' Resource* object =\n'
166 body += ' PpapiGlobals::Get()->GetResourceTracker()->'
167 body += 'GetResource(%s);\n' % args[0][1]
168 body += ' if (!object)\n'
169 body += ' return 0;\n'
170 body += ' EnterResourceCreation enter(object->pp_instance());\n'
171 elif args[0][0] == 'PP_Instance':
172 body = ' EnterResourceCreation enter(%s);\n' % args[0][1]
173 else:
174 raise TGenError('Unknown arg type for Create(): %s' % args[0][0])
175
176 body += ' if (enter.failed())\n'
177 body += ' return 0;\n'
178 arg_list = ', '.join([a[1] for a in args])
179 if member.GetProperty('create_func'):
180 create_func = member.GetProperty('create_func')
181 else:
182 create_func = _GetCreateFuncName(interface)
183 body += ' return enter.functions()->%s(%s);' % (create_func,
184 arg_list)
185 return body
186
187
[email protected]cba2c4302012-11-20 18:18:13188def _MakeNormalMemberBody(filenode, node, member, rtype, args, meta):
[email protected]8c311f02012-11-17 16:01:32189 """Returns the body of a typical function.
190
191 Args:
192 filenode - IDLNode for the file
193 node - IDLNode for the interface
194 member - IDLNode for the member function
195 rtype - Return type for the member function
196 args - List of 4-tuple arguments for the member function
[email protected]cba2c4302012-11-20 18:18:13197 meta - ThunkBodyMetadata for header hints
[email protected]8c311f02012-11-17 16:01:32198 """
199 is_callback_func = args[len(args) - 1][0] == 'struct PP_CompletionCallback'
200
201 if is_callback_func:
202 call_args = args[:-1] + [('', 'enter.callback()', '', '')]
[email protected]8f2a08d52012-12-08 00:10:50203 meta.AddInclude('ppapi/c/pp_completion_callback.h')
[email protected]8c311f02012-11-17 16:01:32204 else:
205 call_args = args
206
207 if args[0][0] == 'PP_Instance':
208 call_arglist = ', '.join(a[1] for a in call_args)
209 function_container = 'functions'
210 else:
211 call_arglist = ', '.join(a[1] for a in call_args[1:])
212 function_container = 'object'
213
214 invocation = 'enter.%s()->%s(%s)' % (function_container,
215 member.GetName(),
216 call_arglist)
217
218 handle_errors = not (member.GetProperty('report_errors') == 'False')
219 if is_callback_func:
220 body = ' %s\n' % _MakeEnterLine(filenode, node, args[0], handle_errors,
[email protected]cba2c4302012-11-20 18:18:13221 args[len(args) - 1][1], meta)
[email protected]8c311f02012-11-17 16:01:32222 body += ' if (enter.failed())\n'
223 value = member.GetProperty('on_failure')
224 if value is None:
225 value = 'enter.retval()'
226 body += ' return %s;\n' % value
[email protected]324eec72012-12-07 01:12:52227 body += ' return enter.SetResult(%s);' % invocation
[email protected]8c311f02012-11-17 16:01:32228 elif rtype == 'void':
229 body = ' %s\n' % _MakeEnterLine(filenode, node, args[0], handle_errors,
[email protected]cba2c4302012-11-20 18:18:13230 None, meta)
[email protected]8c311f02012-11-17 16:01:32231 body += ' if (enter.succeeded())\n'
232 body += ' %s;' % invocation
233 else:
234 value = member.GetProperty('on_failure')
235 if value is None:
236 value = _GetDefaultFailureValue(rtype)
237 if value is None:
238 raise TGenError('No default value for rtype %s' % rtype)
239
240 body = ' %s\n' % _MakeEnterLine(filenode, node, args[0], handle_errors,
[email protected]cba2c4302012-11-20 18:18:13241 None, meta)
[email protected]8c311f02012-11-17 16:01:32242 body += ' if (enter.failed())\n'
243 body += ' return %s;\n' % value
244 body += ' return %s;' % invocation
245 return body
246
247
[email protected]cba2c4302012-11-20 18:18:13248def DefineMember(filenode, node, member, release, include_version, meta):
[email protected]8c311f02012-11-17 16:01:32249 """Returns a definition for a member function of an interface.
250
251 Args:
252 filenode - IDLNode for the file
253 node - IDLNode for the interface
254 member - IDLNode for the member function
255 release - release to generate
256 include_version - include the version in emitted function name.
[email protected]cba2c4302012-11-20 18:18:13257 meta - ThunkMetadata for header hints
[email protected]8c311f02012-11-17 16:01:32258 Returns:
259 A string with the member definition.
260 """
261 cgen = CGen()
262 rtype, name, arrays, args = cgen.GetComponents(member, release, 'return')
263
264 if _IsTypeCheck(node, member):
[email protected]cba2c4302012-11-20 18:18:13265 body = ' %s\n' % _MakeEnterLine(filenode, node, args[0], False, None, meta)
[email protected]8c311f02012-11-17 16:01:32266 body += ' return PP_FromBool(enter.succeeded());'
267 elif member.GetName() == 'Create':
268 body = _MakeCreateMemberBody(node, member, args)
269 else:
[email protected]cba2c4302012-11-20 18:18:13270 body = _MakeNormalMemberBody(filenode, node, member, rtype, args, meta)
[email protected]8c311f02012-11-17 16:01:32271
272 signature = cgen.GetSignature(member, release, 'return', func_as_ptr=False,
273 include_version=include_version)
274 member_code = '%s {\n%s\n}' % (signature, body)
275 return cgen.Indent(member_code, tabs=0)
276
277
278class TGen(GeneratorByFile):
279 def __init__(self):
280 Generator.__init__(self, 'Thunk', 'tgen', 'Generate the C++ thunk.')
281
282 def GenerateFile(self, filenode, releases, options):
283 savename = _GetThunkFileName(filenode, GetOption('thunkroot'))
284 my_min, my_max = filenode.GetMinMax(releases)
285 if my_min > releases[-1] or my_max < releases[0]:
286 if os.path.isfile(savename):
287 print "Removing stale %s for this range." % filenode.GetName()
288 os.remove(os.path.realpath(savename))
289 return False
290 do_generate = filenode.GetProperty('generate_thunk')
291 if not do_generate:
292 return False
293
294 thunk_out = IDLOutFile(savename)
[email protected]cba2c4302012-11-20 18:18:13295 body, meta = self.GenerateBody(thunk_out, filenode, releases, options)
296 self.WriteHead(thunk_out, filenode, releases, options, meta)
297 thunk_out.Write('\n\n'.join(body))
298 self.WriteTail(thunk_out, filenode, releases, options)
[email protected]8c311f02012-11-17 16:01:32299 return thunk_out.Close()
300
[email protected]cba2c4302012-11-20 18:18:13301 def WriteHead(self, out, filenode, releases, options, meta):
[email protected]8c311f02012-11-17 16:01:32302 __pychecker__ = 'unusednames=options'
303 cgen = CGen()
304
305 cright_node = filenode.GetChildren()[0]
306 assert(cright_node.IsA('Copyright'))
307 out.Write('%s\n' % cgen.Copyright(cright_node, cpp_style=True))
308
309 # Wrap the From ... modified ... comment if it would be >80 characters.
310 from_text = 'From %s' % (
311 filenode.GetProperty('NAME').replace(os.sep,'/'))
312 modified_text = 'modified %s.' % (
313 filenode.GetProperty('DATETIME'))
314 if len(from_text) + len(modified_text) < 74:
315 out.Write('// %s %s\n\n' % (from_text, modified_text))
316 else:
317 out.Write('// %s,\n// %s\n\n' % (from_text, modified_text))
318
319
320 # TODO(teravest): Don't emit includes we don't need.
321 includes = ['ppapi/c/pp_errors.h',
322 'ppapi/shared_impl/tracked_callback.h',
323 'ppapi/thunk/enter.h',
324 'ppapi/thunk/ppb_instance_api.h',
325 'ppapi/thunk/resource_creation_api.h',
326 'ppapi/thunk/thunk.h']
327 includes.append(_GetHeaderFileName(filenode))
[email protected]cba2c4302012-11-20 18:18:13328 for api in meta.Apis():
329 includes.append('ppapi/thunk/%s.h' % api.lower())
[email protected]8f2a08d52012-12-08 00:10:50330 for i in meta.Includes():
331 includes.append(i)
[email protected]8c311f02012-11-17 16:01:32332 for include in sorted(includes):
333 out.Write('#include "%s"\n' % include)
334 out.Write('\n')
335 out.Write('namespace ppapi {\n')
336 out.Write('namespace thunk {\n')
337 out.Write('\n')
338 out.Write('namespace {\n')
339 out.Write('\n')
340
341 def GenerateBody(self, out, filenode, releases, options):
[email protected]cba2c4302012-11-20 18:18:13342 """Generates a member function lines to be written and metadata.
343
344 Returns a tuple of (body, meta) where:
345 body - a list of lines with member function bodies
346 meta - a ThunkMetadata instance for hinting which headers are needed.
347 """
[email protected]8c311f02012-11-17 16:01:32348 __pychecker__ = 'unusednames=options'
[email protected]cba2c4302012-11-20 18:18:13349 members = []
350 meta = ThunkBodyMetadata()
[email protected]8c311f02012-11-17 16:01:32351 for node in filenode.GetListOf('Interface'):
352 # Skip if this node is not in this release
353 if not node.InReleases(releases):
354 print "Skipping %s" % node
355 continue
356
357 # Generate Member functions
358 if node.IsA('Interface'):
[email protected]8c311f02012-11-17 16:01:32359 for child in node.GetListOf('Member'):
360 build_list = child.GetUniqueReleases(releases)
361 # We have to filter out releases this node isn't in.
362 build_list = filter(lambda r: child.InReleases([r]), build_list)
363 if len(build_list) == 0:
364 continue
365 release = build_list[-1] # Pick the newest release.
[email protected]cba2c4302012-11-20 18:18:13366 member = DefineMember(filenode, node, child, release, False, meta)
[email protected]8c311f02012-11-17 16:01:32367 if not member:
368 continue
369 members.append(member)
370 for build in build_list[:-1]:
[email protected]cba2c4302012-11-20 18:18:13371 member = DefineMember(filenode, node, child, build, True, meta)
[email protected]8c311f02012-11-17 16:01:32372 if not member:
373 continue
374 members.append(member)
[email protected]cba2c4302012-11-20 18:18:13375 return (members, meta)
[email protected]8c311f02012-11-17 16:01:32376
[email protected]cba2c4302012-11-20 18:18:13377 def WriteTail(self, out, filenode, releases, options):
[email protected]8c311f02012-11-17 16:01:32378 __pychecker__ = 'unusednames=options'
379 cgen = CGen()
380
381 version_list = []
382 out.Write('\n\n')
383 for node in filenode.GetListOf('Interface'):
384 build_list = node.GetUniqueReleases(releases)
385 for build in build_list:
386 version = node.GetVersion(build).replace('.', '_')
387 thunk_name = 'g_' + node.GetName().lower() + '_thunk_' + \
388 version
389 thunk_type = '_'.join((node.GetName(), version))
390 version_list.append((thunk_type, thunk_name))
391
392 out.Write('const %s %s = {\n' % (thunk_type, thunk_name))
[email protected]cfc881d2013-01-03 19:07:46393 generated_functions = []
[email protected]8c311f02012-11-17 16:01:32394 for child in node.GetListOf('Member'):
395 rtype, name, arrays, args = cgen.GetComponents(
396 child, build, 'return')
[email protected]cfc881d2013-01-03 19:07:46397 if child.InReleases([build]):
398 generated_functions.append(name)
399 out.Write(',\n'.join([' &%s' % f for f in generated_functions]))
400 out.Write('\n};\n\n')
[email protected]8c311f02012-11-17 16:01:32401
402 out.Write('} // namespace\n')
403 out.Write('\n')
404 for thunk_type, thunk_name in version_list:
405 thunk_decl = 'const %s* Get%s_Thunk() {\n' % (thunk_type, thunk_type)
406 if len(thunk_decl) > 80:
407 thunk_decl = 'const %s*\n Get%s_Thunk() {\n' % (thunk_type,
408 thunk_type)
409 out.Write(thunk_decl)
410 out.Write(' return &%s;\n' % thunk_name)
411 out.Write('}\n')
412 out.Write('\n')
413 out.Write('} // namespace thunk\n')
414 out.Write('} // namespace ppapi\n')
415
416
417tgen = TGen()
418
419
420def Main(args):
421 # Default invocation will verify the golden files are unchanged.
422 failed = 0
423 if not args:
424 args = ['--wnone', '--diff', '--test', '--thunkroot=.']
425
426 ParseOptions(args)
427
428 idldir = os.path.split(sys.argv[0])[0]
429 idldir = os.path.join(idldir, 'test_thunk', '*.idl')
430 filenames = glob.glob(idldir)
431 ast = ParseFiles(filenames)
432 if tgen.GenerateRange(ast, ['M13', 'M14'], {}):
433 print "Golden file for M13-M14 failed."
434 failed = 1
435 else:
436 print "Golden file for M13-M14 passed."
437
438 return failed
439
440
441if __name__ == '__main__':
442 sys.exit(Main(sys.argv[1:]))