| #!/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)) |