X-Git-Url: https://gerrit.automotivelinux.org/gerrit/gitweb?a=blobdiff_plain;f=generator%2Fnanopb_generator.py;h=61f4d7b3129a452114119066ad3501d32ce7e5e4;hb=7c5e184c261bd5f5652993232f2125a6802004ab;hp=9ecc8f3eb5ba651fbe25deeb3dca69991f9426ff;hpb=258ba8335dd2b54ee9ce934782239f357bca3581;p=apps%2Fagl-service-can-low-level.git diff --git a/generator/nanopb_generator.py b/generator/nanopb_generator.py index 9ecc8f3e..61f4d7b3 100644 --- a/generator/nanopb_generator.py +++ b/generator/nanopb_generator.py @@ -1,8 +1,9 @@ '''Generate header file for nanopb from a ProtoBuf FileDescriptorSet.''' -nanopb_version = "0.2.0-dev" +nanopb_version = "nanopb-0.2.2-dev" try: import google.protobuf.descriptor_pb2 as descriptor + import google.protobuf.text_format as text_format except: print print "*************************************************************" @@ -54,9 +55,7 @@ datatypes = { } class Names: - '''Keeps a set of nested names and formats them to C identifier. - You can subclass this with your own implementation. - ''' + '''Keeps a set of nested names and formats them to C identifier.''' def __init__(self, parts = ()): if isinstance(parts, Names): parts = parts.parts @@ -187,7 +186,7 @@ class Field: return cmp(self.tag, other.tag) def __str__(self): - if self.rules == 'OPTIONAL': + if self.rules == 'OPTIONAL' and self.allocation == 'STATIC': result = ' bool has_' + self.name + ';\n' elif self.rules == 'REPEATED' and self.allocation == 'STATIC': result = ' size_t ' + self.name + '_count;\n' @@ -236,6 +235,11 @@ class Field: else: return 'const %s %s_default%s = %s;' % (ctype, self.struct_name + self.name, array_decl, default) + def tags(self): + '''Return the #define for the tag number of this field.''' + identifier = '%s_%s_tag' % (self.struct_name, self.name) + return '#define %-40s %d\n' % (identifier, self.tag) + def pb_field_t(self, prev_field_name): '''Return the pb_field_t initializer to use in the constant array. prev_field_name is the name of the previous field or None. @@ -271,8 +275,53 @@ class Field: return max(self.tag, self.max_size, self.max_count) +class ExtensionRange(Field): + def __init__(self, struct_name, desc, field_options): + '''desc is ExtensionRange''' + self.tag = desc.start + self.struct_name = struct_name + self.name = 'extensions' + self.pbtype = 'EXTENSION' + self.rules = 'OPTIONAL' + self.allocation = 'CALLBACK' + self.ctype = 'pb_extension_t' + self.array_decl = '' + self.default = None + self.max_size = 0 + self.max_count = 0 + + def __str__(self): + return ' pb_extension_t *extensions;' + + def types(self): + return None + + def tags(self): + return '' - +class ExtensionField(Field): + def __init__(self, struct_name, desc, field_options): + self.fullname = struct_name + desc.name + self.extendee_name = names_from_type_name(desc.extendee) + Field.__init__(self, self.fullname + 'struct', desc, field_options) + + def extension_decl(self): + '''Declaration of the extension type in the .pb.h file''' + return 'extern const pb_extension_type_t %s;' % self.fullname + + def extension_def(self): + '''Definition of the extension type in the .pb.c file''' + result = 'typedef struct {\n' + result += str(self) + result += '} %s;\n' % self.struct_name + result += ('static const pb_field_t %s_field = %s;\n' % + (self.fullname, self.pb_field_t(None))) + result += 'const pb_extension_type_t %s = {\n' % self.fullname + result += ' NULL,\n' + result += ' NULL,\n' + result += ' &%s_field\n' % self.fullname + result += '};\n' + return result # --------------------------------------------------------------------------- @@ -286,10 +335,15 @@ class Message: self.fields = [] for f in desc.field: - field_options = get_nanopb_suboptions(f, message_options) + field_options = get_nanopb_suboptions(f, message_options, self.name + f.name) if field_options.type != nanopb_pb2.FT_IGNORE: self.fields.append(Field(self.name, f, field_options)) + if len(desc.extension_range) > 0: + field_options = get_nanopb_suboptions(desc, message_options, self.name + 'extensions') + if field_options.type != nanopb_pb2.FT_IGNORE: + self.fields.append(ExtensionRange(self.name, desc.extension_range[0], field_options)) + self.packed = message_options.packed_struct self.ordered_fields = self.fields[:] self.ordered_fields.sort() @@ -300,6 +354,12 @@ class Message: def __str__(self): result = 'typedef struct _%s {\n' % self.name + + if not self.ordered_fields: + # Empty structs are not allowed in C standard. + # Therefore add a dummy field if an empty message occurs. + result += ' uint8_t dummy_field;' + result += '\n'.join([str(f) for f in self.ordered_fields]) result += '\n}' @@ -307,6 +367,11 @@ class Message: result += ' pb_packed' result += ' %s;' % self.name + + if self.packed: + result = 'PB_PACKED_STRUCT_START\n' + result + result += '\nPB_PACKED_STRUCT_END' + return result def types(self): @@ -343,9 +408,6 @@ class Message: - - - # --------------------------------------------------------------------------- # Processing of entire .proto files # --------------------------------------------------------------------------- @@ -365,11 +427,23 @@ def iterate_messages(desc, names = Names()): for x in iterate_messages(submsg, sub_names): yield x +def iterate_extensions(desc, names = Names()): + '''Recursively find all extensions. + For each, yield name, FieldDescriptorProto. + ''' + for extension in desc.extension: + yield names, extension + + for subname, subdesc in iterate_messages(desc, names): + for extension in subdesc.extension: + yield subname, extension + def parse_file(fdesc, file_options): - '''Takes a FileDescriptorProto and returns tuple (enum, messages).''' + '''Takes a FileDescriptorProto and returns tuple (enums, messages, extensions).''' enums = [] messages = [] + extensions = [] if fdesc.package: base_name = Names(fdesc.package.split('.')) @@ -377,16 +451,20 @@ def parse_file(fdesc, file_options): base_name = Names() for enum in fdesc.enum_type: - enum_options = get_nanopb_suboptions(enum, file_options) + enum_options = get_nanopb_suboptions(enum, file_options, base_name + enum.name) enums.append(Enum(base_name, enum, enum_options)) for names, message in iterate_messages(fdesc, base_name): - message_options = get_nanopb_suboptions(message, file_options) + message_options = get_nanopb_suboptions(message, file_options, names) messages.append(Message(names, message, message_options)) for enum in message.enum_type: - enum_options = get_nanopb_suboptions(enum, message_options) + enum_options = get_nanopb_suboptions(enum, message_options, names + enum.name) enums.append(Enum(names, enum, enum_options)) + for names, extension in iterate_extensions(fdesc, base_name): + field_options = get_nanopb_suboptions(extension, file_options, names) + extensions.append(ExtensionField(names, extension, field_options)) + # Fix field default values where enum short names are used. for enum in enums: if not enum.options.long_names: @@ -396,7 +474,7 @@ def parse_file(fdesc, file_options): idx = enum.value_longnames.index(field.default) field.default = enum.values[idx][0] - return enums, messages + return enums, messages, extensions def toposort2(data): '''Topological sort. @@ -439,7 +517,7 @@ def make_identifier(headername): result += '_' return result -def generate_header(dependencies, headername, enums, messages): +def generate_header(dependencies, headername, enums, messages, extensions, options): '''Generate content for a header file. Generates strings, which should be concatenated and stored to file. ''' @@ -450,12 +528,18 @@ def generate_header(dependencies, headername, enums, messages): symbol = make_identifier(headername) yield '#ifndef _PB_%s_\n' % symbol yield '#define _PB_%s_\n' % symbol - yield '#include \n\n' + try: + yield options.libformat % ('pb.h') + except TypeError: + # no %s specified - use whatever was passed in as options.libformat + yield options.libformat + yield '\n' for dependency in dependencies: noext = os.path.splitext(dependency)[0] - yield '#include "%s.pb.h"\n' % noext - + yield options.genformat % (noext + '.' + options.extension + '.h') + yield '\n' + yield '#ifdef __cplusplus\n' yield 'extern "C" {\n' yield '#endif\n\n' @@ -468,16 +552,55 @@ def generate_header(dependencies, headername, enums, messages): for msg in sort_dependencies(messages): yield msg.types() yield str(msg) + '\n\n' + + if extensions: + yield '/* Extensions */\n' + for extension in extensions: + yield extension.extension_decl() + yield '\n' yield '/* Default values for struct fields */\n' for msg in messages: yield msg.default_decl(True) yield '\n' + yield '/* Field tags (for use in manual encoding/decoding) */\n' + for msg in sort_dependencies(messages): + for field in msg.fields: + yield field.tags() + yield '\n' + yield '/* Struct field encoding specification for nanopb */\n' for msg in messages: yield msg.fields_declaration() + '\n' + yield '\n#ifdef __cplusplus\n' + yield '} /* extern "C" */\n' + yield '#endif\n' + + # End of header + yield '\n#endif\n' + +def generate_source(headername, enums, messages, extensions): + '''Generate content for a source file.''' + + yield '/* Automatically generated nanopb constant definitions */\n' + yield '/* Generated by %s at %s. */\n\n' % (nanopb_version, time.asctime()) + yield options.genformat % (headername) + yield '\n' + + for msg in messages: + yield msg.default_decl(False) + + yield '\n\n' + + for msg in messages: + yield msg.fields_definition() + '\n\n' + + for ext in extensions: + yield ext.extension_def() + '\n' + + # Add checks for numeric limits if messages: count_required_fields = lambda m: len([f for f in msg.fields if f.rules == 'REQUIRED']) largest_msg = max(messages, key = count_required_fields) @@ -489,7 +612,6 @@ def generate_header(dependencies, headername, enums, messages): yield ' setting PB_MAX_REQUIRED_FIELDS to %d or more.\n' % largest_count yield '#endif\n' - # Add checks for numeric limits worst = 0 worst_field = '' checks = [] @@ -538,31 +660,72 @@ def generate_header(dependencies, headername, enums, messages): yield '\n' yield '/* On some platforms (such as AVR), double is really float.\n' yield ' * These are not directly supported by nanopb, but see example_avr_double.\n' + yield ' * To get rid of this error, remove any double fields from your .proto.\n' yield ' */\n' yield 'STATIC_ASSERT(sizeof(double) == 8, DOUBLE_MUST_BE_8_BYTES)\n' - yield '\n#ifdef __cplusplus\n' - yield '} /* extern "C" */\n' - yield '#endif\n' - - # End of header - yield '\n#endif\n' + yield '\n' -def generate_source(headername, enums, messages): - '''Generate content for a source file.''' +# --------------------------------------------------------------------------- +# Options parsing for the .proto files +# --------------------------------------------------------------------------- + +from fnmatch import fnmatch + +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 + +class Globals: + '''Ugly global variables, should find a good way to pass these.''' + verbose_options = False + separate_options = [] + +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) - yield '/* Automatically generated nanopb constant definitions */\n' - yield '/* Generated by %s at %s. */\n\n' % (nanopb_version, time.asctime()) - yield '#include "%s"\n\n' % headername + # Handle options defined in a separate file + dotname = '.'.join(name.parts) + for namemask, options in Globals.separate_options: + if fnmatch(dotname, namemask): + new_options.MergeFrom(options) - for msg in messages: - yield msg.default_decl(False) + # 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") - yield '\n\n' + if subdesc.options.HasExtension(ext_type): + ext = subdesc.options.Extensions[ext_type] + new_options.MergeFrom(ext) - for msg in messages: - yield msg.fields_definition() + '\n\n' - + if Globals.verbose_options: + print "Options for " + dotname + ":" + print text_format.MessageToString(new_options) + + return new_options # --------------------------------------------------------------------------- @@ -572,7 +735,6 @@ def generate_source(headername, enums, messages): import sys import os.path from optparse import OptionParser -import google.protobuf.text_format as text_format optparser = OptionParser( usage = "Usage: nanopb_generator.py [options] file.pb ...", @@ -580,6 +742,16 @@ optparser = OptionParser( "Output will be written to file.pb.h and file.pb.c.") optparser.add_option("-x", dest="exclude", metavar="FILE", action="append", default=[], help="Exclude file from generated #include list.") +optparser.add_option("-e", "--extension", dest="extension", metavar="EXTENSION", default="pb", + help="Set extension to use instead of 'pb' for generated files. [default: %default]") +optparser.add_option("-f", "--options-file", dest="options_file", metavar="FILE", default="%s.options", + help="Set name of a separate generator options file.") +optparser.add_option("-Q", "--generated-include-format", dest="genformat", + metavar="FORMAT", default='#include "%s"\n', + help="Set format string to use for including other .pb.h files. [default: %default]") +optparser.add_option("-L", "--library-include-format", dest="libformat", + metavar="FORMAT", default='#include <%s>\n', + help="Set format string to use for including the nanopb pb.h header. [default: %default]") optparser.add_option("-q", "--quiet", dest="quiet", action="store_true", default=False, help="Don't print anything except errors.") optparser.add_option("-v", "--verbose", dest="verbose", action="store_true", default=False, @@ -587,28 +759,6 @@ optparser.add_option("-v", "--verbose", dest="verbose", action="store_true", def optparser.add_option("-s", dest="settings", metavar="OPTION:VALUE", action="append", default=[], help="Set generator option (max_size, max_count etc.).") -def get_nanopb_suboptions(subdesc, options): - '''Get copy of options, and merge information from subdesc.''' - new_options = nanopb_pb2.NanoPBOptions() - new_options.CopyFrom(options) - - 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) - - return new_options - def process(filenames, options): '''Process the files given on the command line.''' @@ -618,6 +768,8 @@ def process(filenames, options): if options.quiet: options.verbose = False + + Globals.verbose_options = options.verbose toplevel_options = nanopb_pb2.NanoPBOptions() for s in options.settings: @@ -627,17 +779,28 @@ def process(filenames, options): data = open(filename, 'rb').read() fdesc = descriptor.FileDescriptorSet.FromString(data) - file_options = get_nanopb_suboptions(fdesc.file[0], toplevel_options) + # Check if any separate options are specified + 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 options.verbose: - print "Options for " + filename + ":" - print text_format.MessageToString(file_options) + print 'Reading options from ' + optfilename + + if os.path.isfile(optfilename): + Globals.separate_options = read_options_file(open(optfilename, "rU")) + else: + Globals.separate_options = [] - enums, messages = parse_file(fdesc.file[0], file_options) + # Parse the file + file_options = get_nanopb_suboptions(fdesc.file[0], toplevel_options, Names([filename])) + enums, messages, extensions = parse_file(fdesc.file[0], file_options) noext = os.path.splitext(filename)[0] - headername = noext + '.pb.h' - sourcename = noext + '.pb.c' + headername = noext + '.' + options.extension + '.h' + sourcename = noext + '.' + options.extension + '.c' headerbasename = os.path.basename(headername) if not options.quiet: @@ -649,11 +812,12 @@ def process(filenames, options): dependencies = [d for d in fdesc.file[0].dependency if d not in excludes] header = open(headername, 'w') - for part in generate_header(dependencies, headerbasename, enums, messages): + for part in generate_header(dependencies, headerbasename, enums, + messages, extensions, options): header.write(part) source = open(sourcename, 'w') - for part in generate_source(headerbasename, enums, messages): + for part in generate_source(headerbasename, enums, messages, extensions): source.write(part) return True