+ 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)
+ largest_count = count_required_fields(largest_msg)
+ if largest_count > 64:
+ yield '\n/* Check that missing required fields will be properly detected */\n'
+ yield '#if PB_MAX_REQUIRED_FIELDS < %d\n' % largest_count
+ yield '#error Properly detecting missing required fields in %s requires \\\n' % largest_msg.name
+ yield ' setting PB_MAX_REQUIRED_FIELDS to %d or more.\n' % largest_count
+ yield '#endif\n'
+
+ worst = 0
+ worst_field = ''
+ checks = []
+ checks_msgnames = []
+ for msg in messages:
+ checks_msgnames.append(msg.name)
+ for field in msg.fields:
+ status = field.largest_field_value()
+ if isinstance(status, (str, unicode)):
+ checks.append(status)
+ elif status > worst:
+ worst = status
+ worst_field = str(field.struct_name) + '.' + str(field.name)
+
+ if worst > 255 or checks:
+ yield '\n/* Check that field information fits in pb_field_t */\n'
+
+ if worst < 65536:
+ yield '#if !defined(PB_FIELD_16BIT) && !defined(PB_FIELD_32BIT)\n'
+ if worst > 255:
+ yield '#error Field descriptor for %s is too large. Define PB_FIELD_16BIT to fix this.\n' % worst_field
+ 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 '#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
+ for msg in messages:
+ for field in msg.fields:
+ if field.ctype == 'double':
+ has_double = True
+
+ if has_double:
+ 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'
+
+# ---------------------------------------------------------------------------
+# 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)
+
+ # 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)
+
+ # 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:
+ print "Options for " + dotname + ":"
+ print text_format.MessageToString(new_options)
+
+ return new_options
+
+
+# ---------------------------------------------------------------------------
+# Command line interface
+# ---------------------------------------------------------------------------
+
+import sys
+import os.path
+from optparse import OptionParser
+
+optparser = OptionParser(
+ usage = "Usage: nanopb_generator.py [options] file.pb ...",
+ epilog = "Compile file.pb from file.proto by: 'protoc -ofile.pb file.proto'. " +
+ "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,
+ help="Print more information.")
+optparser.add_option("-s", dest="settings", metavar="OPTION:VALUE", action="append", default=[],
+ help="Set generator option (max_size, max_count etc.).")
+
+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()