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)) 467