OLD | NEW |
| (Empty) |
1 #!/usr/bin/env python | |
2 # Copyright (c) 2012 The Native Client 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 """Build "SRPC" interfaces from specifications. | |
7 | |
8 SRPC interfaces consist of one or more interface classes, typically defined | |
9 in a set of .srpc files. The specifications are Python dictionaries, with a | |
10 top level 'name' element and an 'rpcs' element. The rpcs element is a list | |
11 containing a number of rpc methods, each of which has a 'name', an 'inputs', | |
12 and an 'outputs' element. These elements are lists of input or output | |
13 parameters, which are lists pairs containing a name and type. The set of | |
14 types includes all the SRPC basic types. | |
15 | |
16 These SRPC specifications are used to generate a header file and either a | |
17 server or client stub file, as determined by the command line flag -s or -c. | |
18 """ | |
19 | |
20 import getopt | |
21 import sys | |
22 import os | |
23 | |
24 COPYRIGHT_AND_AUTOGEN_COMMENT = """\ | |
25 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
26 // Use of this source code is governed by a BSD-style license that can be | |
27 // found in the LICENSE file. | |
28 // | |
29 // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING | |
30 // | |
31 // Automatically generated code. See srpcgen.py | |
32 // | |
33 // NaCl Simple Remote Procedure Call interface abstractions. | |
34 """ | |
35 | |
36 HEADER_INCLUDE_GUARD_START = """\ | |
37 #ifndef %(include_guard)s | |
38 #define %(include_guard)s | |
39 """ | |
40 | |
41 HEADER_INCLUDE_GUARD_END = """\ | |
42 \n\n#endif // %(include_guard)s | |
43 """ | |
44 | |
45 HEADER_FILE_INCLUDES = """\ | |
46 #ifndef __native_client__ | |
47 #include "native_client/src/include/portability.h" | |
48 #endif // __native_client__ | |
49 %(EXTRA_INCLUDES)s | |
50 """ | |
51 | |
52 SOURCE_FILE_INCLUDES = """\ | |
53 #include "%(srpcgen_h)s" | |
54 #ifdef __native_client__ | |
55 #ifndef UNREFERENCED_PARAMETER | |
56 #define UNREFERENCED_PARAMETER(P) do { (void) P; } while (0) | |
57 #endif // UNREFERENCED_PARAMETER | |
58 #else | |
59 #include "native_client/src/include/portability.h" | |
60 #endif // __native_client__ | |
61 %(EXTRA_INCLUDES)s | |
62 """ | |
63 | |
64 # For both .cc and .h files. | |
65 EXTRA_INCLUDES = [ | |
66 '#include "native_client/src/shared/srpc/nacl_srpc.h"', | |
67 ] | |
68 | |
69 types = {'bool': ['b', 'bool', 'u.bval', ''], | |
70 'char[]': ['C', 'char*', 'arrays.carr', 'u.count'], | |
71 'double': ['d', 'double', 'u.dval', ''], | |
72 'double[]': ['D', 'double*', 'arrays.darr', 'u.count'], | |
73 'handle': ['h', 'NaClSrpcImcDescType', 'u.hval', ''], | |
74 'int32_t': ['i', 'int32_t', 'u.ival', ''], | |
75 'int32_t[]': ['I', 'int32_t*', 'arrays.iarr', 'u.count'], | |
76 'int64_t': ['l', 'int64_t', 'u.lval', ''], | |
77 'int64_t[]': ['L', 'int64_t', 'arrays.larr', 'u.count'], | |
78 'PP_Instance': ['i', 'PP_Instance', 'u.ival', ''], | |
79 'PP_Module': ['i', 'PP_Module', 'u.ival', ''], | |
80 'PP_Resource': ['i', 'PP_Resource', 'u.ival', ''], | |
81 'string': ['s', 'const char*', 'arrays.str', ''], | |
82 } | |
83 | |
84 def AddInclude(name): | |
85 """Adds an include to the include section of both .cc and .h files.""" | |
86 EXTRA_INCLUDES.append('#include "%s"' % name) | |
87 | |
88 | |
89 def HeaderFileIncludes(): | |
90 """Includes are sorted alphabetically.""" | |
91 EXTRA_INCLUDES.sort() | |
92 return HEADER_FILE_INCLUDES % { | |
93 'EXTRA_INCLUDES': '\n'.join(EXTRA_INCLUDES), | |
94 } | |
95 | |
96 | |
97 def SourceFileIncludes(srpcgen_h_file): | |
98 """Includes are sorted alphabetically.""" | |
99 EXTRA_INCLUDES.sort() | |
100 return SOURCE_FILE_INCLUDES % { | |
101 'EXTRA_INCLUDES': '\n'.join(EXTRA_INCLUDES), | |
102 'srpcgen_h': srpcgen_h_file | |
103 } | |
104 | |
105 | |
106 def PrintHeaderFileTop(output, include_guard): | |
107 """Prints the header of the .h file including copyright, | |
108 header comment, include guard and includes.""" | |
109 print >>output, COPYRIGHT_AND_AUTOGEN_COMMENT | |
110 print >>output, HEADER_INCLUDE_GUARD_START % {'include_guard': include_guard} | |
111 print >>output, HeaderFileIncludes() | |
112 | |
113 | |
114 def PrintHeaderFileBottom(output, include_guard): | |
115 """Prints the footer of the .h file including copyright, | |
116 header comment, include guard and includes.""" | |
117 print >>output, HEADER_INCLUDE_GUARD_END % {'include_guard': include_guard} | |
118 | |
119 | |
120 def PrintSourceFileTop(output, srpcgen_h_file): | |
121 """Prints the header of the .cc file including copyright, | |
122 header comment and includes.""" | |
123 print >>output, COPYRIGHT_AND_AUTOGEN_COMMENT | |
124 print >>output, SourceFileIncludes(srpcgen_h_file) | |
125 | |
126 | |
127 def CountName(name): | |
128 """Returns the name of the auxiliary count member used for array typed.""" | |
129 return '%s_bytes' % name | |
130 | |
131 | |
132 def FormatRpcPrototype(is_server, class_name, indent, rpc): | |
133 """Returns a string for the prototype of an individual RPC.""" | |
134 | |
135 def FormatArgs(is_output, args): | |
136 """Returns a string containing the formatted arguments for an RPC.""" | |
137 | |
138 def FormatArg(is_output, arg): | |
139 """Returns a string containing a formatted argument to an RPC.""" | |
140 if is_output: | |
141 suffix = '* ' | |
142 else: | |
143 suffix = ' ' | |
144 s = '' | |
145 type_info = types[arg[1]] | |
146 if type_info[3]: | |
147 s += 'nacl_abi_size_t%s%s, %s %s' % (suffix, | |
148 CountName(arg[0]), | |
149 type_info[1], | |
150 arg[0]) | |
151 else: | |
152 s += '%s%s%s' % (type_info[1], suffix, arg[0]) | |
153 return s | |
154 s = '' | |
155 for arg in args: | |
156 s += ',\n %s%s' % (indent, FormatArg(is_output, arg)) | |
157 return s | |
158 if is_server: | |
159 ret_type = 'void' | |
160 else: | |
161 ret_type = 'NaClSrpcError' | |
162 s = '%s %s%s(\n' % (ret_type, class_name, rpc['name']) | |
163 # Until SRPC uses RPC/Closure on the client side, these must be different. | |
164 if is_server: | |
165 s += ' %sNaClSrpcRpc* rpc,\n' % indent | |
166 s += ' %sNaClSrpcClosure* done' % indent | |
167 else: | |
168 s += ' %sNaClSrpcChannel* channel' % indent | |
169 s += '%s' % FormatArgs(False, rpc['inputs']) | |
170 s += '%s' % FormatArgs(True, rpc['outputs']) | |
171 s += ')' | |
172 return s | |
173 | |
174 | |
175 def PrintHeaderFile(output, is_server, guard_name, interface_name, specs): | |
176 """Prints out the header file containing the prototypes for the RPCs.""" | |
177 PrintHeaderFileTop(output, guard_name) | |
178 s = '' | |
179 # iterate over all the specified interfaces | |
180 if is_server: | |
181 suffix = 'Server' | |
182 else: | |
183 suffix = 'Client' | |
184 for spec in specs: | |
185 class_name = spec['name'] + suffix | |
186 rpcs = spec['rpcs'] | |
187 s += 'class %s {\n public:\n' % class_name | |
188 for rpc in rpcs: | |
189 s += ' static %s;\n' % FormatRpcPrototype(is_server, '', ' ', rpc) | |
190 s += '\n private:\n %s();\n' % class_name | |
191 s += ' %s(const %s&);\n' % (class_name, class_name) | |
192 s += ' void operator=(const %s);\n' % class_name | |
193 s += '}; // class %s\n\n' % class_name | |
194 if is_server: | |
195 s += 'class %s {\n' % interface_name | |
196 s += ' public:\n' | |
197 s += ' static NaClSrpcHandlerDesc srpc_methods[];\n' | |
198 s += '}; // class %s' % interface_name | |
199 print >>output, s | |
200 PrintHeaderFileBottom(output, guard_name) | |
201 | |
202 | |
203 def PrintServerFile(output, header_name, interface_name, specs): | |
204 """Print the server (stub) .cc file.""" | |
205 | |
206 def FormatDispatchPrototype(indent, rpc): | |
207 """Format the prototype of a dispatcher method.""" | |
208 s = '%sstatic void %sDispatcher(\n' % (indent, rpc['name']) | |
209 s += '%s NaClSrpcRpc* rpc,\n' % indent | |
210 s += '%s NaClSrpcArg** inputs,\n' % indent | |
211 s += '%s NaClSrpcArg** outputs,\n' % indent | |
212 s += '%s NaClSrpcClosure* done\n' % indent | |
213 s += '%s)' % indent | |
214 return s | |
215 | |
216 def FormatMethodString(rpc): | |
217 """Format the SRPC text string for a single rpc method.""" | |
218 | |
219 def FormatTypes(args): | |
220 s = '' | |
221 for arg in args: | |
222 s += types[arg[1]][0] | |
223 return s | |
224 s = ' { "%s:%s:%s", %sDispatcher },\n' % (rpc['name'], | |
225 FormatTypes(rpc['inputs']), | |
226 FormatTypes(rpc['outputs']), | |
227 rpc['name']) | |
228 return s | |
229 | |
230 def FormatCall(class_name, indent, rpc): | |
231 """Format a call from a dispatcher method to its stub.""" | |
232 | |
233 def FormatArgs(is_output, args): | |
234 """Format the arguments passed to the stub.""" | |
235 | |
236 def FormatArg(is_output, num, arg): | |
237 """Format an argument passed to a stub.""" | |
238 if is_output: | |
239 prefix = 'outputs[' + str(num) + ']->' | |
240 addr_prefix = '&(' | |
241 addr_suffix = ')' | |
242 else: | |
243 prefix = 'inputs[' + str(num) + ']->' | |
244 addr_prefix = '' | |
245 addr_suffix = '' | |
246 type_info = types[arg[1]] | |
247 if type_info[3]: | |
248 s = '%s%s%s%s, %s%s' % (addr_prefix, | |
249 prefix, | |
250 type_info[3], | |
251 addr_suffix, | |
252 prefix, | |
253 type_info[2]) | |
254 else: | |
255 s = '%s%s%s%s' % (addr_prefix, prefix, type_info[2], addr_suffix) | |
256 return s | |
257 # end FormatArg | |
258 s = '' | |
259 num = 0 | |
260 for arg in args: | |
261 s += ',\n%s %s' % (indent, FormatArg(is_output, num, arg)) | |
262 num += 1 | |
263 return s | |
264 # end FormatArgs | |
265 s = '%s::%s(\n%s rpc,\n' % (class_name, rpc['name'], indent) | |
266 s += '%s done' % indent | |
267 s += FormatArgs(False, rpc['inputs']) | |
268 s += FormatArgs(True, rpc['outputs']) | |
269 s += '\n%s)' % indent | |
270 return s | |
271 # end FormatCall | |
272 | |
273 PrintSourceFileTop(output, header_name) | |
274 s = 'namespace {\n\n' | |
275 for spec in specs: | |
276 class_name = spec['name'] + 'Server' | |
277 rpcs = spec['rpcs'] | |
278 for rpc in rpcs: | |
279 s += '%s {\n' % FormatDispatchPrototype('', rpc) | |
280 if rpc['inputs'] == []: | |
281 s += ' UNREFERENCED_PARAMETER(inputs);\n' | |
282 if rpc['outputs'] == []: | |
283 s += ' UNREFERENCED_PARAMETER(outputs);\n' | |
284 s += ' %s;\n' % FormatCall(class_name, ' ', rpc) | |
285 s += '}\n\n' | |
286 s += '} // namespace\n\n' | |
287 s += 'NaClSrpcHandlerDesc %s::srpc_methods[] = {\n' % interface_name | |
288 for spec in specs: | |
289 class_name = spec['name'] + 'Server' | |
290 rpcs = spec['rpcs'] | |
291 for rpc in rpcs: | |
292 s += FormatMethodString(rpc) | |
293 s += ' { NULL, NULL }\n};\n' | |
294 print >>output, s | |
295 | |
296 | |
297 def PrintClientFile(output, header_name, specs, thread_check): | |
298 """Prints the client (proxy) .cc file.""" | |
299 | |
300 def InstanceInputArg(rpc): | |
301 """Returns the name of the PP_Instance arg or None if there is none.""" | |
302 for arg in rpc['inputs']: | |
303 if arg[1] == 'PP_Instance': | |
304 return arg[0] | |
305 return None | |
306 | |
307 def DeadNexeHandling(rpc, retval): | |
308 """Generates the code necessary to handle death of a nexe during the rpc | |
309 call. This is only possible if PP_Instance arg is present, otherwise""" | |
310 instance = InstanceInputArg(rpc); | |
311 if instance is not None: | |
312 check = (' if (%s == NACL_SRPC_RESULT_INTERNAL)\n' | |
313 ' ppapi_proxy::CleanUpAfterDeadNexe(%s);\n') | |
314 return check % (retval, instance) | |
315 return '' # No handling | |
316 | |
317 | |
318 def FormatCall(rpc): | |
319 """Format a call to the generic dispatcher, NaClSrpcInvokeBySignature.""" | |
320 | |
321 def FormatTypes(args): | |
322 """Format a the type signature string for either inputs or outputs.""" | |
323 s = '' | |
324 for arg in args: | |
325 s += types[arg[1]][0] | |
326 return s | |
327 def FormatArgs(args): | |
328 """Format the arguments for the call to the generic dispatcher.""" | |
329 | |
330 def FormatArg(arg): | |
331 """Format a single argument for the call to the generic dispatcher.""" | |
332 s = '' | |
333 type_info = types[arg[1]] | |
334 if type_info[3]: | |
335 s += '%s, ' % CountName(arg[0]) | |
336 s += arg[0] | |
337 return s | |
338 # end FormatArg | |
339 s = '' | |
340 for arg in args: | |
341 s += ',\n %s' % FormatArg(arg) | |
342 return s | |
343 #end FormatArgs | |
344 s = '(\n channel,\n "%s:%s:%s"' % (rpc['name'], | |
345 FormatTypes(rpc['inputs']), | |
346 FormatTypes(rpc['outputs'])) | |
347 s += FormatArgs(rpc['inputs']) | |
348 s += FormatArgs(rpc['outputs']) + '\n )' | |
349 return s | |
350 # end FormatCall | |
351 | |
352 # We need this to handle dead nexes. | |
353 if header_name.startswith('trusted'): | |
354 AddInclude('native_client/src/shared/ppapi_proxy/browser_globals.h') | |
355 if thread_check: | |
356 AddInclude('native_client/src/shared/ppapi_proxy/plugin_globals.h') | |
357 AddInclude('ppapi/c/ppb_core.h') | |
358 AddInclude('native_client/src/shared/platform/nacl_check.h') | |
359 PrintSourceFileTop(output, header_name) | |
360 s = '' | |
361 | |
362 for spec in specs: | |
363 class_name = spec['name'] + 'Client' | |
364 rpcs = spec['rpcs'] | |
365 for rpc in rpcs: | |
366 s += '%s {\n' % FormatRpcPrototype('', class_name + '::', '', rpc) | |
367 if thread_check and rpc['name'] not in ['PPB_GetInterface', | |
368 'PPB_Core_CallOnMainThread']: | |
369 error = '"%s: PPAPI calls are not supported off the main thread\\n"' | |
370 s += ' VCHECK(ppapi_proxy::PPBCoreInterface()->IsMainThread(),\n' | |
371 s += ' (%s,\n' % error | |
372 s += ' __FUNCTION__));\n' | |
373 s += ' NaClSrpcError retval;\n' | |
374 s += ' retval = NaClSrpcInvokeBySignature%s;\n' % FormatCall(rpc) | |
375 if header_name.startswith('trusted'): | |
376 s += DeadNexeHandling(rpc, 'retval') | |
377 s += ' return retval;\n' | |
378 s += '}\n\n' | |
379 print >>output, s | |
380 | |
381 def MakePath(name): | |
382 paths = name.split(os.sep) | |
383 path = os.sep.join(paths[:-1]) | |
384 try: | |
385 os.makedirs(path) | |
386 except OSError: | |
387 return | |
388 | |
389 | |
390 def main(argv): | |
391 usage = 'Usage: srpcgen.py <-c | -s> [--include=<name>] [--ppapi]' | |
392 usage = usage + ' <iname> <gname> <.h> <.cc> <specs>' | |
393 | |
394 mode = None | |
395 ppapi = False | |
396 thread_check = False | |
397 try: | |
398 long_opts = ['include=', 'ppapi', 'thread-check'] | |
399 opts, pargs = getopt.getopt(argv[1:], 'cs', long_opts) | |
400 except getopt.error, e: | |
401 print >>sys.stderr, 'Illegal option:', str(e) | |
402 print >>sys.stderr, usage | |
403 return 1 | |
404 | |
405 # Get the class name for the interface. | |
406 interface_name = pargs[0] | |
407 # Get the name for the token used as a multiple inclusion guard in the header. | |
408 include_guard_name = pargs[1] | |
409 # Get the name of the header file to be generated. | |
410 h_file_name = pargs[2] | |
411 MakePath(h_file_name) | |
412 # Note we open output files in binary mode so that on Windows the files | |
413 # will always get LF line-endings rather than CRLF. | |
414 h_file = open(h_file_name, 'wb') | |
415 # Get the name of the source file to be generated. Depending upon whether | |
416 # -c or -s is generated, this file contains either client or server methods. | |
417 cc_file_name = pargs[3] | |
418 MakePath(cc_file_name) | |
419 cc_file = open(cc_file_name, 'wb') | |
420 # The remaining arguments are the spec files to be compiled. | |
421 spec_files = pargs[4:] | |
422 | |
423 for opt, val in opts: | |
424 if opt == '-c': | |
425 mode = 'client' | |
426 elif opt == '-s': | |
427 mode = 'server' | |
428 elif opt == '--include': | |
429 h_file_name = val | |
430 elif opt == '--ppapi': | |
431 ppapi = True | |
432 elif opt == '--thread-check': | |
433 thread_check = True | |
434 | |
435 if ppapi: | |
436 AddInclude("ppapi/c/pp_instance.h") | |
437 AddInclude("ppapi/c/pp_module.h") | |
438 AddInclude("ppapi/c/pp_resource.h") | |
439 | |
440 # Convert to forward slash paths if needed | |
441 h_file_name = "/".join(h_file_name.split("\\")) | |
442 | |
443 # Verify we picked server or client mode | |
444 if not mode: | |
445 print >>sys.stderr, 'Neither -c nor -s specified' | |
446 usage() | |
447 return 1 | |
448 | |
449 # Combine the rpc specs from spec_files into rpcs. | |
450 specs = [] | |
451 for spec_file in spec_files: | |
452 code_obj = compile(open(spec_file, 'r').read(), 'file', 'eval') | |
453 specs.append(eval(code_obj)) | |
454 # Print out the requested files. | |
455 if mode == 'client': | |
456 PrintHeaderFile(h_file, False, include_guard_name, interface_name, specs) | |
457 PrintClientFile(cc_file, h_file_name, specs, thread_check) | |
458 elif mode == 'server': | |
459 PrintHeaderFile(h_file, True, include_guard_name, interface_name, specs) | |
460 PrintServerFile(cc_file, h_file_name, interface_name, specs) | |
461 | |
462 return 0 | |
463 | |
464 | |
465 if __name__ == '__main__': | |
466 sys.exit(main(sys.argv)) | |
OLD | NEW |