X-Git-Url: https://gerrit.automotivelinux.org/gerrit/gitweb?a=blobdiff_plain;f=generator%2Fnanopb_generator.py;h=e463b6ca8c8e99ddd9a2f7077e83e62d09ea7f5a;hb=840e213b9fa244bce4c67789971c02ce16e66295;hp=bd365dfde2c74824659bdd10308bab72d4d14714;hpb=a1cec46b3e824292b4ae22fa1f5c4faf9846196c;p=apps%2Fagl-service-can-low-level.git diff --git a/generator/nanopb_generator.py b/generator/nanopb_generator.py old mode 100644 new mode 100755 index bd365dfd..e463b6ca --- a/generator/nanopb_generator.py +++ b/generator/nanopb_generator.py @@ -1,5 +1,7 @@ +#!/usr/bin/python + '''Generate header file for nanopb from a ProtoBuf FileDescriptorSet.''' -nanopb_version = "nanopb-0.2.2-dev" +nanopb_version = "nanopb-0.2.3-dev" try: import google.protobuf.descriptor_pb2 as descriptor @@ -244,13 +246,14 @@ class Field: '''Return the pb_field_t initializer to use in the constant array. prev_field_name is the name of the previous field or None. ''' - result = ' PB_FIELD(%3d, ' % self.tag + result = ' PB_FIELD2(%3d, ' % self.tag result += '%-8s, ' % self.pbtype result += '%s, ' % self.rules result += '%s, ' % self.allocation result += '%s, ' % self.struct_name result += '%s, ' % self.name result += '%s, ' % (prev_field_name or self.name) + result += '%s, ' % ("first" if not prev_field_name else "other") if self.pbtype == 'MESSAGE': result += '&%s_fields)' % self.submsgname @@ -275,8 +278,72 @@ class Field: return max(self.tag, self.max_size, self.max_count) +class ExtensionRange(Field): + def __init__(self, struct_name, range_start, field_options): + '''Implements a special pb_extension_t* field in an extensible message + structure. The range_start signifies the index at which the extensions + start. Not necessarily all tags above this are extensions, it is merely + a speed optimization. + ''' + self.tag = range_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) + + if self.rules != 'OPTIONAL': + self.skip = True + else: + self.skip = False + self.rules = 'OPTEXT' + + def extension_decl(self): + '''Declaration of the extension type in the .pb.h file''' + if self.skip: + msg = '/* Extension field %s was skipped because only "optional"\n' % self.fullname + msg +=' type of extension fields is currently supported. */\n' + return msg + + return 'extern const pb_extension_type_t %s;\n' % self.fullname + + def extension_def(self): + '''Definition of the extension type in the .pb.c file''' + + if self.skip: + return '' + + result = 'typedef struct {\n' + result += str(self) + result += '\n} %s;\n\n' % self.struct_name + result += ('static const pb_field_t %s_field = \n %s;\n\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 # --------------------------------------------------------------------------- @@ -294,6 +361,12 @@ class Message: 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') + range_start = min([r.start for r in desc.extension_range]) + if field_options.type != nanopb_pb2.FT_IGNORE: + self.fields.append(ExtensionRange(self.name, range_start, field_options)) + self.packed = message_options.packed_struct self.ordered_fields = self.fields[:] self.ordered_fields.sort() @@ -358,9 +431,6 @@ class Message: - - - # --------------------------------------------------------------------------- # Processing of entire .proto files # --------------------------------------------------------------------------- @@ -380,11 +450,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('.')) @@ -402,6 +484,11 @@ def parse_file(fdesc, file_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) + if field_options.type != nanopb_pb2.FT_IGNORE: + 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: @@ -411,7 +498,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. @@ -454,7 +541,7 @@ def make_identifier(headername): result += '_' return result -def generate_header(dependencies, headername, enums, messages, options): +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. ''' @@ -489,6 +576,12 @@ def generate_header(dependencies, headername, enums, messages, options): 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: @@ -512,7 +605,7 @@ def generate_header(dependencies, headername, enums, messages, options): # End of header yield '\n#endif\n' -def generate_source(headername, enums, messages): +def generate_source(headername, enums, messages, extensions, options): '''Generate content for a source file.''' yield '/* Automatically generated nanopb constant definitions */\n' @@ -527,7 +620,11 @@ def generate_source(headername, enums, messages): 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) @@ -539,7 +636,6 @@ def generate_source(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 = [] @@ -687,72 +783,126 @@ 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 process(filenames, options): - '''Process the files given on the command line.''' +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 options.verbose: + print 'Reading options from ' + optfilename + + if os.path.isfile(optfilename): + Globals.separate_options = read_options_file(open(optfilename, "rU")) + else: + Globals.separate_options = [] + + # 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)) + + return {'headername': headername, 'headerdata': headerdata, + 'sourcename': sourcename, 'sourcedata': sourcedata} + +def main_cli(): + '''Main function when invoked directly from the command line.''' + + options, filenames = optparser.parse_args() if not filenames: optparser.print_help() - return False + sys.exit(1) if options.quiet: options.verbose = False Globals.verbose_options = options.verbose - toplevel_options = nanopb_pb2.NanoPBOptions() - for s in options.settings: - text_format.Merge(s, toplevel_options) - for filename in filenames: - data = open(filename, 'rb').read() - fdesc = descriptor.FileDescriptorSet.FromString(data) - - # 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 'Reading options from ' + optfilename - - if os.path.isfile(optfilename): - Globals.separate_options = read_options_file(open(optfilename, "rU")) - else: - Globals.separate_options = [] - - # Parse the file - file_options = get_nanopb_suboptions(fdesc.file[0], toplevel_options, Names([filename])) - enums, messages = parse_file(fdesc.file[0], file_options) - - noext = os.path.splitext(filename)[0] - headername = noext + '.' + options.extension + '.h' - sourcename = noext + '.' + options.extension + '.c' - headerbasename = os.path.basename(headername) + results = process_file(filename, None, options) if not options.quiet: - print "Writing to " + headername + " and " + sourcename - - # 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.file[0].dependency if d not in excludes] - - header = open(headername, 'w') - for part in generate_header(dependencies, headerbasename, enums, messages, options): - header.write(part) - - source = open(sourcename, 'w') - for part in generate_source(headerbasename, enums, messages): - source.write(part) + print "Writing to " + results['headername'] + " and " + results['sourcename'] + + open(results['headername'], 'w').write(results['headerdata']) + open(results['sourcename'], 'w').write(results['sourcedata']) - return True +def main_plugin(): + '''Main function when invoked as a protoc plugin.''' -if __name__ == '__main__': - options, filenames = optparser.parse_args() - status = process(filenames, options) + import plugin_pb2 + data = sys.stdin.read() + request = plugin_pb2.CodeGeneratorRequest.FromString(data) - if not status: - sys.exit(1) + import shlex + args = shlex.split(request.parameter) + options, dummy = optparser.parse_args(args) + + # We can't go printing stuff to stdout + Globals.verbose_options = False + options.verbose = False + options.quiet = True + + response = plugin_pb2.CodeGeneratorResponse() + + for filename in request.file_to_generate: + for fdesc in request.proto_file: + if fdesc.name == filename: + results = process_file(filename, fdesc, options) + + f = response.file.add() + f.name = results['headername'] + f.content = results['headerdata'] + + f = response.file.add() + f.name = results['sourcename'] + f.content = results['sourcedata'] + sys.stdout.write(response.SerializeToString()) + +if __name__ == '__main__': + # Check if we are running as a plugin under protoc + if 'protoc-gen-' in sys.argv[0]: + main_plugin() + else: + main_cli() +