X-Git-Url: https://gerrit.automotivelinux.org/gerrit/gitweb?a=blobdiff_plain;f=generator%2Fnanopb_generator.py;h=50108148f193de0470e71a005122932e3088f109;hb=be0b9e047a007685aa10a268f9bf856e9a52ef58;hp=a4e4cc7bb412f8cfe352feb9af40f4bbd0a1a510;hpb=321ca6c1d5736edf6ec2ac0b97eae58227954fad;p=apps%2Fagl-service-can-low-level.git diff --git a/generator/nanopb_generator.py b/generator/nanopb_generator.py index a4e4cc7b..50108148 100755 --- a/generator/nanopb_generator.py +++ b/generator/nanopb_generator.py @@ -1,36 +1,42 @@ #!/usr/bin/python '''Generate header file for nanopb from a ProtoBuf FileDescriptorSet.''' -nanopb_version = "nanopb-0.2.5-dev" +nanopb_version = "nanopb-0.3.0-dev" + +import sys + +try: + # Add some dummy imports to keep packaging tools happy. + import google, distutils.util # bbfreeze seems to need these + import pkg_resources # pyinstaller / protobuf 2.5 seem to need these +except: + # Don't care, we will error out later if it is actually important. + pass try: - import google.protobuf.descriptor_pb2 as descriptor import google.protobuf.text_format as text_format + import google.protobuf.descriptor_pb2 as descriptor except: - print - print "*************************************************************" - print "*** Could not import the Google protobuf Python libraries ***" - print "*** Try installing package 'python-protobuf' or similar. ***" - print "*************************************************************" - print + sys.stderr.write(''' + ************************************************************* + *** Could not import the Google protobuf Python libraries *** + *** Try installing package 'python-protobuf' or similar. *** + ************************************************************* + ''' + '\n') raise try: - import nanopb_pb2 + import proto.nanopb_pb2 as nanopb_pb2 + import proto.plugin_pb2 as plugin_pb2 except: - print - print "***************************************************************" - print "*** Could not import the precompiled nanopb_pb2.py. ***" - print "*** Run 'make' in the 'generator' folder to update the file.***" - print "***************************************************************" - print + sys.stderr.write(''' + ******************************************************************** + *** Failed to import the protocol definitions for generator. *** + *** You have to run 'make' in the nanopb/generator/proto folder. *** + ******************************************************************** + ''' + '\n') raise - - - - - # --------------------------------------------------------------------------- # Generation of single fields # --------------------------------------------------------------------------- @@ -46,7 +52,7 @@ datatypes = { FieldD.TYPE_FIXED32: ('uint32_t', 'FIXED32', 4), FieldD.TYPE_FIXED64: ('uint64_t', 'FIXED64', 8), FieldD.TYPE_FLOAT: ('float', 'FLOAT', 4), - FieldD.TYPE_INT32: ('int32_t', 'INT32', 5), + FieldD.TYPE_INT32: ('int32_t', 'INT32', 10), FieldD.TYPE_INT64: ('int64_t', 'INT64', 10), FieldD.TYPE_SFIXED32: ('int32_t', 'SFIXED32', 4), FieldD.TYPE_SFIXED64: ('int64_t', 'SFIXED64', 8), @@ -163,6 +169,7 @@ class Field: self.max_count = None self.array_decl = "" self.enc_size = None + self.ctype = None # Parse field options if field_options.HasField("max_size"): @@ -189,6 +196,34 @@ class Field: else: raise NotImplementedError(desc.label) + # Check if the field can be implemented with static allocation + # i.e. whether the data size is known. + if desc.type == FieldD.TYPE_STRING and self.max_size is None: + can_be_static = False + + if desc.type == FieldD.TYPE_BYTES and self.max_size is None: + can_be_static = False + + # Decide how the field data will be allocated + if field_options.type == nanopb_pb2.FT_DEFAULT: + if can_be_static: + field_options.type = nanopb_pb2.FT_STATIC + else: + field_options.type = nanopb_pb2.FT_CALLBACK + + if field_options.type == nanopb_pb2.FT_STATIC and not can_be_static: + raise Exception("Field %s is defined as static, but max_size or " + "max_count is not given." % self.name) + + if field_options.type == nanopb_pb2.FT_STATIC: + self.allocation = 'STATIC' + elif field_options.type == nanopb_pb2.FT_POINTER: + self.allocation = 'POINTER' + elif field_options.type == nanopb_pb2.FT_CALLBACK: + self.allocation = 'CALLBACK' + else: + raise NotImplementedError(field_options.type) + # Decide the C data type to use in the struct. if datatypes.has_key(desc.type): self.ctype, self.pbtype, self.enc_size = datatypes[desc.type] @@ -200,19 +235,18 @@ class Field: self.enc_size = 5 # protoc rejects enum values > 32 bits elif desc.type == FieldD.TYPE_STRING: self.pbtype = 'STRING' - if self.max_size is None: - can_be_static = False - else: + self.ctype = 'char' + if self.allocation == 'STATIC': self.ctype = 'char' self.array_decl += '[%d]' % self.max_size self.enc_size = varint_max_size(self.max_size) + self.max_size elif desc.type == FieldD.TYPE_BYTES: self.pbtype = 'BYTES' - if self.max_size is None: - can_be_static = False - else: + if self.allocation == 'STATIC': self.ctype = self.struct_name + self.name + 't' self.enc_size = varint_max_size(self.max_size) + self.max_size + elif self.allocation == 'POINTER': + self.ctype = 'pb_bytes_array_t' elif desc.type == FieldD.TYPE_MESSAGE: self.pbtype = 'MESSAGE' self.ctype = self.submsgname = names_from_type_name(desc.type_name) @@ -220,71 +254,98 @@ class Field: else: raise NotImplementedError(desc.type) - if field_options.type == nanopb_pb2.FT_DEFAULT: - if can_be_static: - field_options.type = nanopb_pb2.FT_STATIC - else: - field_options.type = nanopb_pb2.FT_CALLBACK - - if field_options.type == nanopb_pb2.FT_STATIC and not can_be_static: - raise Exception("Field %s is defined as static, but max_size or max_count is not given." % self.name) - - if field_options.type == nanopb_pb2.FT_STATIC: - self.allocation = 'STATIC' - elif field_options.type == nanopb_pb2.FT_CALLBACK: - self.allocation = 'CALLBACK' - self.ctype = 'pb_callback_t' - self.array_decl = '' - else: - raise NotImplementedError(field_options.type) - def __cmp__(self, other): return cmp(self.tag, other.tag) def __str__(self): - 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' + result = '' + if self.allocation == 'POINTER': + if self.rules == 'REPEATED': + result += ' pb_size_t ' + self.name + '_count;\n' + + if self.pbtype == 'MESSAGE': + # Use struct definition, so recursive submessages are possible + result += ' struct _%s *%s;' % (self.ctype, self.name) + elif self.rules == 'REPEATED' and self.pbtype in ['STRING', 'BYTES']: + # String/bytes arrays need to be defined as pointers to pointers + result += ' %s **%s;' % (self.ctype, self.name) + else: + result += ' %s *%s;' % (self.ctype, self.name) + elif self.allocation == 'CALLBACK': + result += ' pb_callback_t %s;' % self.name else: - result = '' - result += ' %s %s%s;' % (self.ctype, self.name, self.array_decl) + if self.rules == 'OPTIONAL' and self.allocation == 'STATIC': + result += ' bool has_' + self.name + ';\n' + elif self.rules == 'REPEATED' and self.allocation == 'STATIC': + result += ' pb_size_t ' + self.name + '_count;\n' + result += ' %s %s%s;' % (self.ctype, self.name, self.array_decl) return result def types(self): '''Return definitions for any special types this field might need.''' if self.pbtype == 'BYTES' and self.allocation == 'STATIC': - result = 'typedef struct {\n' - result += ' size_t size;\n' - result += ' uint8_t bytes[%d];\n' % self.max_size - result += '} %s;\n' % self.ctype + result = 'typedef PB_BYTES_ARRAY_T(%d) %s;\n' % (self.max_size, self.ctype) else: result = None return result + def get_initializer(self, null_init): + '''Return literal expression for this field's default value.''' + + if self.pbtype == 'MESSAGE': + if null_init: + return '%s_init_zero' % self.ctype + else: + return '%s_init_default' % self.ctype + + if self.default is None or null_init: + if self.pbtype == 'STRING': + return '""' + elif self.pbtype == 'BYTES': + return '{0, {0}}' + elif self.pbtype == 'ENUM': + return '(%s)0' % self.ctype + else: + return '0' + + default = str(self.default) + + if self.pbtype == 'STRING': + default = default.encode('utf-8').encode('string_escape') + default = default.replace('"', '\\"') + default = '"' + default + '"' + elif self.pbtype == 'BYTES': + data = default.decode('string_escape') + data = ['0x%02x' % ord(c) for c in data] + if len(data) == 0: + default = '{0, {0}}' + else: + default = '{%d, {%s}}' % (len(data), ','.join(data)) + elif self.pbtype in ['FIXED32', 'UINT32']: + default += 'u' + elif self.pbtype in ['FIXED64', 'UINT64']: + default += 'ull' + elif self.pbtype in ['SFIXED64', 'INT64']: + default += 'll' + + return default + def default_decl(self, declaration_only = False): '''Return definition for this field's default value.''' if self.default is None: return None - ctype, default = self.ctype, self.default + ctype = self.ctype + default = self.get_initializer(False) array_decl = '' if self.pbtype == 'STRING': if self.allocation != 'STATIC': return None # Not implemented - array_decl = '[%d]' % self.max_size - default = str(self.default).encode('string_escape') - default = default.replace('"', '\\"') - default = '"' + default + '"' elif self.pbtype == 'BYTES': if self.allocation != 'STATIC': return None # Not implemented - - data = self.default.decode('string_escape') - data = ['0x%02x' % ord(c) for c in data] - default = '{%d, {%s}}' % (len(data), ','.join(data)) if declaration_only: return 'extern const %s %s_default%s;' % (ctype, self.struct_name + self.name, array_decl) @@ -303,7 +364,7 @@ class Field: result = ' PB_FIELD2(%3d, ' % self.tag result += '%-8s, ' % self.pbtype result += '%s, ' % self.rules - result += '%s, ' % self.allocation + result += '%-8s, ' % self.allocation result += '%s, ' % ("FIRST" if not prev_field_name else "OTHER") result += '%s, ' % self.struct_name result += '%s, ' % self.name @@ -315,6 +376,8 @@ class Field: result += '0)' elif self.pbtype in ['BYTES', 'STRING'] and self.allocation != 'STATIC': result += '0)' # Arbitrary size default values not implemented + elif self.rules == 'OPTEXT': + result += '0)' # Default value for extensions is not implemented else: result += '&%s_default)' % (self.struct_name + self.name) @@ -404,7 +467,7 @@ class ExtensionRange(Field): def tags(self): return '' - + def encoded_size(self, allmsgs): # We exclude extensions from the count, because they cannot be known # until runtime. Other option would be to return None here, but this @@ -515,6 +578,32 @@ class Message: result += types + '\n' return result + def get_initializer(self, null_init): + if not self.ordered_fields: + return '{0}' + + parts = [] + for field in self.ordered_fields: + if field.allocation == 'STATIC': + if field.rules == 'REPEATED': + parts.append('0') + parts.append('{' + + ', '.join([field.get_initializer(null_init)] * field.max_count) + + '}') + elif field.rules == 'OPTIONAL': + parts.append('false') + parts.append(field.get_initializer(null_init)) + else: + parts.append(field.get_initializer(null_init)) + elif field.allocation == 'POINTER': + parts.append('NULL') + elif field.allocation == 'CALLBACK': + if field.pbtype == 'EXTENSION': + parts.append('NULL') + else: + parts.append('{{NULL}, NULL}') + return '{' + ', '.join(parts) + '}' + def default_decl(self, declaration_only = False): result = "" for field in self.fields: @@ -601,13 +690,17 @@ def parse_file(fdesc, file_options): for names, message in iterate_messages(fdesc, base_name): message_options = get_nanopb_suboptions(message, file_options, names) + + if message_options.skip_message: + continue + messages.append(Message(names, message, message_options)) for enum in message.enum_type: 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) + field_options = get_nanopb_suboptions(extension, file_options, names + extension.name) if field_options.type != nanopb_pb2.FT_IGNORE: extensions.append(ExtensionField(names, extension, field_options)) @@ -669,11 +762,14 @@ def generate_header(dependencies, headername, enums, messages, extensions, optio ''' yield '/* Automatically generated nanopb header */\n' - yield '/* Generated by %s at %s. */\n\n' % (nanopb_version, time.asctime()) + if options.notimestamp: + yield '/* Generated by %s */\n\n' % (nanopb_version) + else: + yield '/* Generated by %s at %s. */\n\n' % (nanopb_version, time.asctime()) symbol = make_identifier(headername) - yield '#ifndef _PB_%s_\n' % symbol - yield '#define _PB_%s_\n' % symbol + yield '#ifndef PB_%s_INCLUDED\n' % symbol + yield '#define PB_%s_INCLUDED\n' % symbol try: yield options.libformat % ('pb.h') except TypeError: @@ -683,7 +779,7 @@ def generate_header(dependencies, headername, enums, messages, extensions, optio for dependency in dependencies: noext = os.path.splitext(dependency)[0] - yield options.genformat % (noext + '.' + options.extension + '.h') + yield options.genformat % (noext + options.extension + '.h') yield '\n' yield '#ifdef __cplusplus\n' @@ -710,6 +806,15 @@ def generate_header(dependencies, headername, enums, messages, extensions, optio yield msg.default_decl(True) yield '\n' + yield '/* Initializer values for message structs */\n' + for msg in messages: + identifier = '%s_init_default' % msg.name + yield '#define %-40s %s\n' % (identifier, msg.get_initializer(False)) + for msg in messages: + identifier = '%s_init_zero' % msg.name + yield '#define %-40s %s\n' % (identifier, msg.get_initializer(True)) + yield '\n' + yield '/* Field tags (for use in manual encoding/decoding) */\n' for msg in sort_dependencies(messages): for field in msg.fields: @@ -742,7 +847,10 @@ def generate_source(headername, enums, messages, extensions, options): '''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()) + if options.notimestamp: + yield '/* Generated by %s */\n\n' % (nanopb_version) + else: + yield '/* Generated by %s at %s. */\n\n' % (nanopb_version, time.asctime()) yield options.genformat % (headername) yield '\n' @@ -786,6 +894,23 @@ def generate_source(headername, enums, messages, extensions, options): if worst > 255 or checks: yield '\n/* Check that field information fits in pb_field_t */\n' + if worst > 65535 or checks: + yield '#if !defined(PB_FIELD_32BIT)\n' + if worst > 65535: + yield '#error Field descriptor for %s is too large. Define PB_FIELD_32BIT to fix this.\n' % worst_field + else: + assertion = ' && '.join(str(c) + ' < 65536' for c in checks) + msgs = '_'.join(str(n) for n in checks_msgnames) + yield '/* If you get an error here, it means that you need to define PB_FIELD_32BIT\n' + yield ' * compile-time option. You can do that in pb.h or on compiler command line.\n' + yield ' * \n' + yield ' * The reason you need to do this is that some of your messages contain tag\n' + yield ' * numbers or field sizes that are larger than what can fit in 8 or 16 bit\n' + yield ' * field descriptors.\n' + yield ' */\n' + yield 'PB_STATIC_ASSERT((%s), YOU_MUST_DEFINE_PB_FIELD_32BIT_FOR_MESSAGES_%s)\n'%(assertion,msgs) + yield '#endif\n\n' + if worst < 65536: yield '#if !defined(PB_FIELD_16BIT) && !defined(PB_FIELD_32BIT)\n' if worst > 255: @@ -793,18 +918,15 @@ def generate_source(headername, enums, messages, extensions, options): else: assertion = ' && '.join(str(c) + ' < 256' for c in checks) msgs = '_'.join(str(n) for n in checks_msgnames) - yield 'STATIC_ASSERT((%s), YOU_MUST_DEFINE_PB_FIELD_16BIT_FOR_MESSAGES_%s)\n'%(assertion,msgs) + yield '/* If you get an error here, it means that you need to define PB_FIELD_16BIT\n' + yield ' * compile-time option. You can do that in pb.h or on compiler command line.\n' + yield ' * \n' + yield ' * The reason you need to do this is that some of your messages contain tag\n' + yield ' * numbers or field sizes that are larger than what can fit in the default\n' + yield ' * 8 bit descriptors.\n' + yield ' */\n' + yield 'PB_STATIC_ASSERT((%s), YOU_MUST_DEFINE_PB_FIELD_16BIT_FOR_MESSAGES_%s)\n'%(assertion,msgs) yield '#endif\n\n' - - if worst > 65535 or checks: - yield '#if !defined(PB_FIELD_32BIT)\n' - if worst > 65535: - yield '#error Field descriptor for %s is too large. Define PB_FIELD_32BIT to fix this.\n' % worst_field - else: - assertion = ' && '.join(str(c) + ' < 65536' for c in checks) - msgs = '_'.join(str(n) for n in checks_msgnames) - yield 'STATIC_ASSERT((%s), YOU_MUST_DEFINE_PB_FIELD_32BIT_FOR_MESSAGES_%s)\n'%(assertion,msgs) - yield '#endif\n' # Add check for sizeof(double) has_double = False @@ -819,7 +941,7 @@ def generate_source(headername, enums, messages, extensions, options): 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 'PB_STATIC_ASSERT(sizeof(double) == 8, DOUBLE_MUST_BE_8_BYTES)\n' yield '\n' @@ -850,6 +972,7 @@ class Globals: '''Ugly global variables, should find a good way to pass these.''' verbose_options = False separate_options = [] + matched_namemasks = set() def get_nanopb_suboptions(subdesc, options, name): '''Get copy of options, and merge information from subdesc.''' @@ -860,6 +983,7 @@ def get_nanopb_suboptions(subdesc, options, name): 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 @@ -879,8 +1003,8 @@ def get_nanopb_suboptions(subdesc, options, name): new_options.MergeFrom(ext) if Globals.verbose_options: - print "Options for " + dotname + ":" - print text_format.MessageToString(new_options) + sys.stderr.write("Options for " + dotname + ": ") + sys.stderr.write(text_format.MessageToString(new_options) + "\n") return new_options @@ -899,8 +1023,8 @@ 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("-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", @@ -909,6 +1033,8 @@ optparser.add_option("-Q", "--generated-include-format", dest="genformat", 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("-T", "--no-timestamp", dest="notimestamp", action="store_true", default=False, + help="Don't add timestamp to .pb.h and .pb.c preambles") 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, @@ -938,28 +1064,38 @@ def process_file(filename, fdesc, options): 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 - - if options.verbose: - print 'Reading options from ' + optfilename - + had_abspath = True + 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: + # 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) + 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' + 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 @@ -973,6 +1109,14 @@ def process_file(filename, fdesc, 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} @@ -994,7 +1138,8 @@ def main_cli(): results = process_file(filename, None, options) if not options.quiet: - print "Writing to " + results['headername'] + " and " + results['sourcename'] + sys.stderr.write("Writing to " + results['headername'] + " and " + + results['sourcename'] + "\n") open(results['headername'], 'w').write(results['headerdata']) open(results['sourcename'], 'w').write(results['sourcedata']) @@ -1002,7 +1147,13 @@ def main_cli(): def main_plugin(): '''Main function when invoked as a protoc plugin.''' - import plugin_pb2 + import sys + if sys.platform == "win32": + import os, msvcrt + # Set stdin and stdout to binary mode + msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) + msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) + data = sys.stdin.read() request = plugin_pb2.CodeGeneratorRequest.FromString(data) @@ -1010,10 +1161,7 @@ def main_plugin(): 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 + Globals.verbose_options = options.verbose response = plugin_pb2.CodeGeneratorResponse() @@ -1034,7 +1182,7 @@ def main_plugin(): if __name__ == '__main__': # Check if we are running as a plugin under protoc - if 'protoc-gen-' in sys.argv[0]: + if 'protoc-gen-' in sys.argv[0] or '--protoc-plugin' in sys.argv: main_plugin() else: main_cli()