Home | History | Annotate | Download | only in tools
      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