def get_nanopb_suboptions(subdesc, options, name): '''Get copy of options, and merge information from subdesc.''' new_options = nanopb_pb2.NanoPBOptions() new_options.CopyFrom(options) # Handle options defined in a separate file dotname = '.'.join(name.parts) for namemask, options in Globals.separate_options: if fnmatch(dotname, namemask): Globals.matched_namemasks.add(namemask) new_options.MergeFrom(options) # Handle options defined in .proto if isinstance(subdesc.options, descriptor.FieldOptions): ext_type = nanopb_pb2.nanopb elif isinstance(subdesc.options, descriptor.FileOptions): ext_type = nanopb_pb2.nanopb_fileopt elif isinstance(subdesc.options, descriptor.MessageOptions): ext_type = nanopb_pb2.nanopb_msgopt elif isinstance(subdesc.options, descriptor.EnumOptions): ext_type = nanopb_pb2.nanopb_enumopt else: raise Exception("Unknown options type") if subdesc.options.HasExtension(ext_type): ext = subdesc.options.Extensions[ext_type] new_options.MergeFrom(ext) if Globals.verbose_options: sys.stderr.write("Options for " + dotname + ": ") sys.stderr.write(text_format.MessageToString(new_options) + "\n") return new_options
def read_options_file(infile): '''Parse a separate options file to list: [(namemask, options), ...] ''' results = [] data = infile.read() data = re.sub('/\*.*?\*/', '', data, flags = re.MULTILINE) data = re.sub('//.*?$', '', data, flags = re.MULTILINE) data = re.sub('#.*?$', '', data, flags = re.MULTILINE) for i, line in enumerate(data.split('\n')): line = line.strip() if not line: continue parts = line.split(None, 1) if len(parts) < 2: sys.stderr.write("%s:%d: " % (infile.name, i + 1) + "Option lines should have space between field name and options. " + "Skipping line: '%s'\n" % line) continue opts = nanopb_pb2.NanoPBOptions() try: text_format.Merge(parts[1], opts) except Exception as e: sys.stderr.write("%s:%d: " % (infile.name, i + 1) + "Unparseable option line: '%s'. " % line + "Error: %s\n" % str(e)) continue results.append((parts[0], opts)) return results
def read_options_file(infile): '''Parse a separate options file to list: [(namemask, options), ...] ''' results = [] for line in infile: line = line.strip() if not line or line.startswith('//') or line.startswith('#'): continue parts = line.split(None, 1) opts = nanopb_pb2.NanoPBOptions() text_format.Merge(parts[1], opts) results.append((parts[0], opts)) return results
def parse_file(filename, fdesc, options): '''Parse a single file. Returns a ProtoFile instance.''' toplevel_options = nanopb_pb2.NanoPBOptions() for s in options.settings: text_format.Merge(s, toplevel_options) if not fdesc: data = open(filename, 'rb').read() fdesc = descriptor.FileDescriptorSet.FromString(data).file[0] # Check if there is a separate .options file had_abspath = False try: optfilename = options.options_file % os.path.splitext(filename)[0] except TypeError: # No %s specified, use the filename as-is optfilename = options.options_file had_abspath = True paths = ['.'] + options.options_path for p in paths: if os.path.isfile(os.path.join(p, optfilename)): optfilename = os.path.join(p, optfilename) if options.verbose: sys.stderr.write('Reading options from ' + optfilename + '\n') Globals.separate_options = read_options_file(open(optfilename, "rU")) break else: # If we are given a full filename and it does not exist, give an error. # However, don't give error when we automatically look for .options file # with the same name as .proto. if options.verbose or had_abspath: sys.stderr.write('Options file not found: ' + optfilename + '\n') Globals.separate_options = [] Globals.matched_namemasks = set() # Parse the file file_options = get_nanopb_suboptions(fdesc, toplevel_options, Names([filename])) f = ProtoFile(fdesc, file_options) f.optfilename = optfilename return f
def process_file(filename, fdesc, options): '''Process a single file. filename: The full path to the .proto or .pb source file, as string. fdesc: The loaded FileDescriptorSet, or None to read from the input file. options: Command line options as they come from OptionsParser. Returns a dict: {'headername': Name of header file, 'headerdata': Data for the .h header file, 'sourcename': Name of the source code file, 'sourcedata': Data for the .c source code file } ''' toplevel_options = nanopb_pb2.NanoPBOptions() for s in options.settings: text_format.Merge(s, toplevel_options) if not fdesc: data = open(filename, 'rb').read() fdesc = descriptor.FileDescriptorSet.FromString(data).file[0] # Check if there is a separate .options file try: optfilename = options.options_file % os.path.splitext(filename)[0] except TypeError: # No %s specified, use the filename as-is optfilename = options.options_file if os.path.isfile(optfilename): if options.verbose: sys.stderr.write('Reading options from ' + optfilename + '\n') Globals.separate_options = read_options_file(open(optfilename, "rU")) else: Globals.separate_options = [] Globals.matched_namemasks = set() # Parse the file file_options = get_nanopb_suboptions(fdesc, toplevel_options, Names([filename])) enums, messages, extensions = parse_file(fdesc, file_options) # Decide the file names noext = os.path.splitext(filename)[0] headername = noext + '.' + options.extension + '.h' sourcename = noext + '.' + options.extension + '.c' headerbasename = os.path.basename(headername) # List of .proto files that should not be included in the C header file # even if they are mentioned in the source .proto. excludes = ['nanopb.proto', 'google/protobuf/descriptor.proto' ] + options.exclude dependencies = [d for d in fdesc.dependency if d not in excludes] headerdata = ''.join( generate_header(dependencies, headerbasename, enums, messages, extensions, options)) sourcedata = ''.join( generate_source(headerbasename, enums, messages, extensions, options)) # Check if there were any lines in .options that did not match a member unmatched = [ n for n, o in Globals.separate_options if n not in Globals.matched_namemasks ] if unmatched and not options.quiet: sys.stderr.write("Following patterns in " + optfilename + " did not match any fields: " + ', '.join(unmatched) + "\n") if not Globals.verbose_options: sys.stderr.write( "Use protoc --nanopb-out=-v:. to see a list of the field names.\n" ) return { 'headername': headername, 'headerdata': headerdata, 'sourcename': sourcename, 'sourcedata': sourcedata }
def process_request(request): response = plugin_pb2.CodeGeneratorResponse() for proto_file in request.proto_file: if proto_file.name not in request.file_to_generate: # this is a dependency file, so we don't need to process it continue if request.parameter == 'python': f = response.file.add() f.name = proto_file.name.replace('.proto', '_pb2.py') f.insertion_point = 'module_scope' f.content = "MESSAGE_ID_TO_CLASS = {}" # iterate through each message in this proto file for message in proto_file.message_type: if not message.options.HasExtension(nanopb_pb2.nanopb_msgopt): # ignore messages without the nanopb_msgopt option continue elif not message.options.HasExtension(sonar_extensions_pb2.sonar_msgopt): # ignore messages without the sonar_msgopt option continue # grab the msgid option from nanopb nanopb_options = nanopb_pb2.NanoPBOptions() nanopb_options.MergeFrom(message.options.Extensions[nanopb_pb2.nanopb_msgopt]) msg_id = nanopb_options.msgid # grab our attr_ops option sonar_options = sonar_extensions_pb2.SonarOptions() sonar_options.MergeFrom(message.options.Extensions[sonar_extensions_pb2.sonar_msgopt]) attr_ops = sonar_options.attr_ops attr_ops_name = sonar_extensions_pb2.AttrOps.Name(attr_ops).replace("ATTR_OPS_", "") f = response.file.add() if request.parameter == "java": if 'SONAR_PROTO_GEN_REQUEST_INFO_JAVA_CLASS' not in os.environ: sys.stderr.write("Must define SONAR_PROTO_GEN_REQUEST_INFO_JAVA_CLASS before calling sonar_proto_gen.py\n") sys.exit(1) request_info_class = os.environ['SONAR_PROTO_GEN_REQUEST_INFO_JAVA_CLASS'] # generate the path to the target java file path which we'll be modifying f.name = os.path.join(os.path.join(*proto_file.options.java_package.split('.')), proto_file.package + ".java") f.insertion_point = "class_scope:" + proto_file.package + "." + message.name # generate a REQUEST_INFO field f.content = "" f.content += "public static %s<%s> REQUEST_INFO;\n"%(request_info_class, message.name) f.content += "static {\n" f.content += " REQUEST_INFO = new %s<%s>() {\n"%(request_info_class, message.name) f.content += " @Override\n" f.content += " public short getAttributeId() {\n" f.content += " return 0x%03x;\n"%(msg_id) f.content += " }\n" f.content += " @Override\n" f.content += " public boolean supportsNotify() {\n" f.content += " return %s;\n"%("true" if "N" in attr_ops_name else "false") f.content += " }\n" f.content += " @Override\n" f.content += " public com.google.protobuf.Parser<%s> getParser() {\n"%(message.name) f.content += " return parser();\n" f.content += " }\n" f.content += " };\n" f.content += "}\n" elif request.parameter == "python": # # generate the path to the target python file path which we'll be modifying f.name = proto_file.name.replace('.proto', '_pb2.py') f.insertion_point = "module_scope" f.content = "MESSAGE_ID_TO_CLASS[%d] = %s" % (msg_id, message.name) elif request.parameter == "c": # generate the path to the target C header which we'll be modifying f.name = proto_file.name.replace(".proto", ".pb.h") struct_name = proto_file.package + "_" + message.name f.insertion_point = "struct:" + struct_name # generate a define for the operations which will get passed to SONAR_ATTR_DEF() f.content += "#define %s_ops %s\n"%(struct_name, attr_ops_name) else: raise Exception("Unknown target language (parameter=%s)"%(request.parameter)) return response