#!/usr/bin/env python # Copyright (c) 2012 The Native Client Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Build "SRPC" interfaces from specifications. SRPC interfaces consist of one or more interface classes, typically defined in a set of .srpc files. The specifications are Python dictionaries, with a top level 'name' element and an 'rpcs' element. The rpcs element is a list containing a number of rpc methods, each of which has a 'name', an 'inputs', and an 'outputs' element. These elements are lists of input or output parameters, which are lists pairs containing a name and type. The set of types includes all the SRPC basic types. These SRPC specifications are used to generate a header file and either a server or client stub file, as determined by the command line flag -s or -c. """ import getopt import sys import os COPYRIGHT_AND_AUTOGEN_COMMENT = """\ // Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING // // Automatically generated code. See srpcgen.py // // NaCl Simple Remote Procedure Call interface abstractions. """ HEADER_INCLUDE_GUARD_START = """\ #ifndef %(include_guard)s #define %(include_guard)s """ HEADER_INCLUDE_GUARD_END = """\ \n\n#endif // %(include_guard)s """ HEADER_FILE_INCLUDES = """\ #ifndef __native_client__ #include "native_client/src/include/portability.h" #endif // __native_client__ %(EXTRA_INCLUDES)s """ SOURCE_FILE_INCLUDES = """\ #include "%(srpcgen_h)s" #ifdef __native_client__ #ifndef UNREFERENCED_PARAMETER #define UNREFERENCED_PARAMETER(P) do { (void) P; } while (0) #endif // UNREFERENCED_PARAMETER #else #include "native_client/src/include/portability.h" #endif // __native_client__ %(EXTRA_INCLUDES)s """ # For both .cc and .h files. EXTRA_INCLUDES = [ '#include "native_client/src/shared/srpc/nacl_srpc.h"', ] types = {'bool': ['b', 'bool', 'u.bval', ''], 'char[]': ['C', 'char*', 'arrays.carr', 'u.count'], 'double': ['d', 'double', 'u.dval', ''], 'double[]': ['D', 'double*', 'arrays.darr', 'u.count'], 'handle': ['h', 'NaClSrpcImcDescType', 'u.hval', ''], 'int32_t': ['i', 'int32_t', 'u.ival', ''], 'int32_t[]': ['I', 'int32_t*', 'arrays.iarr', 'u.count'], 'int64_t': ['l', 'int64_t', 'u.lval', ''], 'int64_t[]': ['L', 'int64_t', 'arrays.larr', 'u.count'], 'PP_Instance': ['i', 'PP_Instance', 'u.ival', ''], 'PP_Module': ['i', 'PP_Module', 'u.ival', ''], 'PP_Resource': ['i', 'PP_Resource', 'u.ival', ''], 'string': ['s', 'const char*', 'arrays.str', ''], } def AddInclude(name): """Adds an include to the include section of both .cc and .h files.""" EXTRA_INCLUDES.append('#include "%s"' % name) def HeaderFileIncludes(): """Includes are sorted alphabetically.""" EXTRA_INCLUDES.sort() return HEADER_FILE_INCLUDES % { 'EXTRA_INCLUDES': '\n'.join(EXTRA_INCLUDES), } def SourceFileIncludes(srpcgen_h_file): """Includes are sorted alphabetically.""" EXTRA_INCLUDES.sort() return SOURCE_FILE_INCLUDES % { 'EXTRA_INCLUDES': '\n'.join(EXTRA_INCLUDES), 'srpcgen_h': srpcgen_h_file } def PrintHeaderFileTop(output, include_guard): """Prints the header of the .h file including copyright, header comment, include guard and includes.""" print >>output, COPYRIGHT_AND_AUTOGEN_COMMENT print >>output, HEADER_INCLUDE_GUARD_START % {'include_guard': include_guard} print >>output, HeaderFileIncludes() def PrintHeaderFileBottom(output, include_guard): """Prints the footer of the .h file including copyright, header comment, include guard and includes.""" print >>output, HEADER_INCLUDE_GUARD_END % {'include_guard': include_guard} def PrintSourceFileTop(output, srpcgen_h_file): """Prints the header of the .cc file including copyright, header comment and includes.""" print >>output, COPYRIGHT_AND_AUTOGEN_COMMENT print >>output, SourceFileIncludes(srpcgen_h_file) def CountName(name): """Returns the name of the auxiliary count member used for array typed.""" return '%s_bytes' % name def FormatRpcPrototype(is_server, class_name, indent, rpc): """Returns a string for the prototype of an individual RPC.""" def FormatArgs(is_output, args): """Returns a string containing the formatted arguments for an RPC.""" def FormatArg(is_output, arg): """Returns a string containing a formatted argument to an RPC.""" if is_output: suffix = '* ' else: suffix = ' ' s = '' type_info = types[arg[1]] if type_info[3]: s += 'nacl_abi_size_t%s%s, %s %s' % (suffix, CountName(arg[0]), type_info[1], arg[0]) else: s += '%s%s%s' % (type_info[1], suffix, arg[0]) return s s = '' for arg in args: s += ',\n %s%s' % (indent, FormatArg(is_output, arg)) return s if is_server: ret_type = 'void' else: ret_type = 'NaClSrpcError' s = '%s %s%s(\n' % (ret_type, class_name, rpc['name']) # Until SRPC uses RPC/Closure on the client side, these must be different. if is_server: s += ' %sNaClSrpcRpc* rpc,\n' % indent s += ' %sNaClSrpcClosure* done' % indent else: s += ' %sNaClSrpcChannel* channel' % indent s += '%s' % FormatArgs(False, rpc['inputs']) s += '%s' % FormatArgs(True, rpc['outputs']) s += ')' return s def PrintHeaderFile(output, is_server, guard_name, interface_name, specs): """Prints out the header file containing the prototypes for the RPCs.""" PrintHeaderFileTop(output, guard_name) s = '' # iterate over all the specified interfaces if is_server: suffix = 'Server' else: suffix = 'Client' for spec in specs: class_name = spec['name'] + suffix rpcs = spec['rpcs'] s += 'class %s {\n public:\n' % class_name for rpc in rpcs: s += ' static %s;\n' % FormatRpcPrototype(is_server, '', ' ', rpc) s += '\n private:\n %s();\n' % class_name s += ' %s(const %s&);\n' % (class_name, class_name) s += ' void operator=(const %s);\n' % class_name s += '}; // class %s\n\n' % class_name if is_server: s += 'class %s {\n' % interface_name s += ' public:\n' s += ' static NaClSrpcHandlerDesc srpc_methods[];\n' s += '}; // class %s' % interface_name print >>output, s PrintHeaderFileBottom(output, guard_name) def PrintServerFile(output, header_name, interface_name, specs): """Print the server (stub) .cc file.""" def FormatDispatchPrototype(indent, rpc): """Format the prototype of a dispatcher method.""" s = '%sstatic void %sDispatcher(\n' % (indent, rpc['name']) s += '%s NaClSrpcRpc* rpc,\n' % indent s += '%s NaClSrpcArg** inputs,\n' % indent s += '%s NaClSrpcArg** outputs,\n' % indent s += '%s NaClSrpcClosure* done\n' % indent s += '%s)' % indent return s def FormatMethodString(rpc): """Format the SRPC text string for a single rpc method.""" def FormatTypes(args): s = '' for arg in args: s += types[arg[1]][0] return s s = ' { "%s:%s:%s", %sDispatcher },\n' % (rpc['name'], FormatTypes(rpc['inputs']), FormatTypes(rpc['outputs']), rpc['name']) return s def FormatCall(class_name, indent, rpc): """Format a call from a dispatcher method to its stub.""" def FormatArgs(is_output, args): """Format the arguments passed to the stub.""" def FormatArg(is_output, num, arg): """Format an argument passed to a stub.""" if is_output: prefix = 'outputs[' + str(num) + ']->' addr_prefix = '&(' addr_suffix = ')' else: prefix = 'inputs[' + str(num) + ']->' addr_prefix = '' addr_suffix = '' type_info = types[arg[1]] if type_info[3]: s = '%s%s%s%s, %s%s' % (addr_prefix, prefix, type_info[3], addr_suffix, prefix, type_info[2]) else: s = '%s%s%s%s' % (addr_prefix, prefix, type_info[2], addr_suffix) return s # end FormatArg s = '' num = 0 for arg in args: s += ',\n%s %s' % (indent, FormatArg(is_output, num, arg)) num += 1 return s # end FormatArgs s = '%s::%s(\n%s rpc,\n' % (class_name, rpc['name'], indent) s += '%s done' % indent s += FormatArgs(False, rpc['inputs']) s += FormatArgs(True, rpc['outputs']) s += '\n%s)' % indent return s # end FormatCall PrintSourceFileTop(output, header_name) s = 'namespace {\n\n' for spec in specs: class_name = spec['name'] + 'Server' rpcs = spec['rpcs'] for rpc in rpcs: s += '%s {\n' % FormatDispatchPrototype('', rpc) if rpc['inputs'] == []: s += ' UNREFERENCED_PARAMETER(inputs);\n' if rpc['outputs'] == []: s += ' UNREFERENCED_PARAMETER(outputs);\n' s += ' %s;\n' % FormatCall(class_name, ' ', rpc) s += '}\n\n' s += '} // namespace\n\n' s += 'NaClSrpcHandlerDesc %s::srpc_methods[] = {\n' % interface_name for spec in specs: class_name = spec['name'] + 'Server' rpcs = spec['rpcs'] for rpc in rpcs: s += FormatMethodString(rpc) s += ' { NULL, NULL }\n};\n' print >>output, s def PrintClientFile(output, header_name, specs, thread_check): """Prints the client (proxy) .cc file.""" def InstanceInputArg(rpc): """Returns the name of the PP_Instance arg or None if there is none.""" for arg in rpc['inputs']: if arg[1] == 'PP_Instance': return arg[0] return None def DeadNexeHandling(rpc, retval): """Generates the code necessary to handle death of a nexe during the rpc call. This is only possible if PP_Instance arg is present, otherwise""" instance = InstanceInputArg(rpc); if instance is not None: check = (' if (%s == NACL_SRPC_RESULT_INTERNAL)\n' ' ppapi_proxy::CleanUpAfterDeadNexe(%s);\n') return check % (retval, instance) return '' # No handling def FormatCall(rpc): """Format a call to the generic dispatcher, NaClSrpcInvokeBySignature.""" def FormatTypes(args): """Format a the type signature string for either inputs or outputs.""" s = '' for arg in args: s += types[arg[1]][0] return s def FormatArgs(args): """Format the arguments for the call to the generic dispatcher.""" def FormatArg(arg): """Format a single argument for the call to the generic dispatcher.""" s = '' type_info = types[arg[1]] if type_info[3]: s += '%s, ' % CountName(arg[0]) s += arg[0] return s # end FormatArg s = '' for arg in args: s += ',\n %s' % FormatArg(arg) return s #end FormatArgs s = '(\n channel,\n "%s:%s:%s"' % (rpc['name'], FormatTypes(rpc['inputs']), FormatTypes(rpc['outputs'])) s += FormatArgs(rpc['inputs']) s += FormatArgs(rpc['outputs']) + '\n )' return s # end FormatCall # We need this to handle dead nexes. if header_name.startswith('trusted'): AddInclude('native_client/src/shared/ppapi_proxy/browser_globals.h') if thread_check: AddInclude('native_client/src/shared/ppapi_proxy/plugin_globals.h') AddInclude('ppapi/c/ppb_core.h') AddInclude('native_client/src/shared/platform/nacl_check.h') PrintSourceFileTop(output, header_name) s = '' for spec in specs: class_name = spec['name'] + 'Client' rpcs = spec['rpcs'] for rpc in rpcs: s += '%s {\n' % FormatRpcPrototype('', class_name + '::', '', rpc) if thread_check and rpc['name'] not in ['PPB_GetInterface', 'PPB_Core_CallOnMainThread']: error = '"%s: PPAPI calls are not supported off the main thread\\n"' s += ' VCHECK(ppapi_proxy::PPBCoreInterface()->IsMainThread(),\n' s += ' (%s,\n' % error s += ' __FUNCTION__));\n' s += ' NaClSrpcError retval;\n' s += ' retval = NaClSrpcInvokeBySignature%s;\n' % FormatCall(rpc) if header_name.startswith('trusted'): s += DeadNexeHandling(rpc, 'retval') s += ' return retval;\n' s += '}\n\n' print >>output, s def MakePath(name): paths = name.split(os.sep) path = os.sep.join(paths[:-1]) try: os.makedirs(path) except OSError: return def main(argv): usage = 'Usage: srpcgen.py <-c | -s> [--include=<name>] [--ppapi]' usage = usage + ' <iname> <gname> <.h> <.cc> <specs>' mode = None ppapi = False thread_check = False try: long_opts = ['include=', 'ppapi', 'thread-check'] opts, pargs = getopt.getopt(argv[1:], 'cs', long_opts) except getopt.error, e: print >>sys.stderr, 'Illegal option:', str(e) print >>sys.stderr, usage return 1 # Get the class name for the interface. interface_name = pargs[0] # Get the name for the token used as a multiple inclusion guard in the header. include_guard_name = pargs[1] # Get the name of the header file to be generated. h_file_name = pargs[2] MakePath(h_file_name) # Note we open output files in binary mode so that on Windows the files # will always get LF line-endings rather than CRLF. h_file = open(h_file_name, 'wb') # Get the name of the source file to be generated. Depending upon whether # -c or -s is generated, this file contains either client or server methods. cc_file_name = pargs[3] MakePath(cc_file_name) cc_file = open(cc_file_name, 'wb') # The remaining arguments are the spec files to be compiled. spec_files = pargs[4:] for opt, val in opts: if opt == '-c': mode = 'client' elif opt == '-s': mode = 'server' elif opt == '--include': h_file_name = val elif opt == '--ppapi': ppapi = True elif opt == '--thread-check': thread_check = True if ppapi: AddInclude("ppapi/c/pp_instance.h") AddInclude("ppapi/c/pp_module.h") AddInclude("ppapi/c/pp_resource.h") # Convert to forward slash paths if needed h_file_name = "/".join(h_file_name.split("\\")) # Verify we picked server or client mode if not mode: print >>sys.stderr, 'Neither -c nor -s specified' usage() return 1 # Combine the rpc specs from spec_files into rpcs. specs = [] for spec_file in spec_files: code_obj = compile(open(spec_file, 'r').read(), 'file', 'eval') specs.append(eval(code_obj)) # Print out the requested files. if mode == 'client': PrintHeaderFile(h_file, False, include_guard_name, interface_name, specs) PrintClientFile(cc_file, h_file_name, specs, thread_check) elif mode == 'server': PrintHeaderFile(h_file, True, include_guard_name, interface_name, specs) PrintServerFile(cc_file, h_file_name, interface_name, specs) return 0 if __name__ == '__main__': sys.exit(main(sys.argv))