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
Example #2
0
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
Example #4
0
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
    }
Example #6
0
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