diff options
author | Petteri Aimonen <jpa@git.mail.kapsi.fi> | 2012-10-29 18:20:15 +0200 |
---|---|---|
committer | Petteri Aimonen <jpa@git.mail.kapsi.fi> | 2012-10-29 18:20:15 +0200 |
commit | 28b0136ea4dcd045f0422d16a25b7d82b0d2aaee (patch) | |
tree | 7ec5fd5962a733b53e2bc93ea90618df58e5806f | |
parent | 9e0ee92f0a42ce2c5c9d4bf4f1d7d822caf1c561 (diff) |
Improve .proto options parsing.
Options can now be defined on command line, file, message or in field
scope.
Update issue 12
Status: Started
-rw-r--r-- | generator/nanopb.proto | 8 | ||||
-rw-r--r-- | generator/nanopb_generator.py | 73 | ||||
-rw-r--r-- | generator/nanopb_pb2.py | 29 | ||||
-rw-r--r-- | tests/Makefile | 10 | ||||
-rw-r--r-- | tests/options.expected | 3 | ||||
-rw-r--r-- | tests/options.proto | 28 |
6 files changed, 129 insertions, 22 deletions
diff --git a/generator/nanopb.proto b/generator/nanopb.proto index 2610cd5..a377f63 100644 --- a/generator/nanopb.proto +++ b/generator/nanopb.proto @@ -20,6 +20,14 @@ message NanoPBOptions { // Extensions: 1010 (all types) // -------------------------------- +extend google.protobuf.FileOptions { + optional NanoPBOptions nanopb_fileopt = 1010; +} + +extend google.protobuf.MessageOptions { + optional NanoPBOptions nanopb_msgopt = 1010; +} + extend google.protobuf.FieldOptions { optional NanoPBOptions nanopb = 1010; } diff --git a/generator/nanopb_generator.py b/generator/nanopb_generator.py index 6ce91cf..69a9eab 100644 --- a/generator/nanopb_generator.py +++ b/generator/nanopb_generator.py @@ -79,7 +79,7 @@ def names_from_type_name(type_name): return Names(type_name[1:].split('.')) class Enum: - def __init__(self, names, desc): + def __init__(self, names, desc, enum_options): '''desc is EnumDescriptorProto''' self.names = names + desc.name self.values = [(self.names + x.name, x.number) for x in desc.value] @@ -91,7 +91,7 @@ class Enum: return result class Field: - def __init__(self, struct_name, desc): + def __init__(self, struct_name, desc, field_options): '''desc is FieldDescriptorProto''' self.tag = desc.number self.struct_name = struct_name @@ -101,13 +101,12 @@ class Field: self.max_count = None self.array_decl = "" - # Parse nanopb-specific field options - if desc.options.HasExtension(nanopb_pb2.nanopb): - ext = desc.options.Extensions[nanopb_pb2.nanopb] - if ext.HasField("max_size"): - self.max_size = ext.max_size - if ext.HasField("max_count"): - self.max_count = ext.max_count + # Parse field options + if field_options.HasField("max_size"): + self.max_size = field_options.max_size + + if field_options.HasField("max_count"): + self.max_count = field_options.max_count if desc.HasField('default_value'): self.default = desc.default_value @@ -284,9 +283,9 @@ class Field: class Message: - def __init__(self, names, desc): + def __init__(self, names, desc, message_options): self.name = names - self.fields = [Field(self.name, f) for f in desc.field] + self.fields = [Field(self.name, f, get_nanopb_suboptions(f, message_options)) for f in desc.field] self.ordered_fields = self.fields[:] self.ordered_fields.sort() @@ -356,7 +355,7 @@ def iterate_messages(desc, names = Names()): for x in iterate_messages(submsg, sub_names): yield x -def parse_file(fdesc): +def parse_file(fdesc, file_options): '''Takes a FileDescriptorProto and returns tuple (enum, messages).''' enums = [] @@ -368,12 +367,13 @@ def parse_file(fdesc): base_name = Names() for enum in fdesc.enum_type: - enums.append(Enum(base_name, enum)) + enums.append(Enum(base_name, enum, file_options)) for names, message in iterate_messages(fdesc, base_name): - messages.append(Message(names, message)) + message_options = get_nanopb_suboptions(message, file_options) + messages.append(Message(names, message, message_options)) for enum in message.enum_type: - enums.append(Enum(names, enum)) + enums.append(Enum(names, enum, message_options)) return enums, messages @@ -513,6 +513,7 @@ 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 ...", @@ -522,6 +523,30 @@ optparser.add_option("-x", dest="exclude", metavar="FILE", action="append", defa help="Exclude file from generated #include list.") 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 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 + 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.''' @@ -530,10 +555,24 @@ def process(filenames, options): optparser.print_help() return False + if options.quiet: + options.verbose = False + + 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) - enums, messages = parse_file(fdesc.file[0]) + + file_options = get_nanopb_suboptions(fdesc.file[0], toplevel_options) + + if options.verbose: + print "Options for " + filename + ":" + print text_format.MessageToString(file_options) + + enums, messages = parse_file(fdesc.file[0], file_options) noext = os.path.splitext(filename)[0] headername = noext + '.pb.h' @@ -545,7 +584,7 @@ def process(filenames, options): # 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'] + 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') diff --git a/generator/nanopb_pb2.py b/generator/nanopb_pb2.py index f2fbeef..0937819 100644 --- a/generator/nanopb_pb2.py +++ b/generator/nanopb_pb2.py @@ -7,15 +7,33 @@ from google.protobuf import descriptor_pb2 # @@protoc_insertion_point(imports) +import google.protobuf.descriptor_pb2 + DESCRIPTOR = descriptor.FileDescriptor( name='nanopb.proto', package='', - serialized_pb='\n\x0cnanopb.proto\x1a google/protobuf/descriptor.proto\"4\n\rNanoPBOptions\x12\x10\n\x08max_size\x18\x01 \x01(\x05\x12\x11\n\tmax_count\x18\x02 \x01(\x05:>\n\x06nanopb\x12\x1d.google.protobuf.FieldOptions\x18\xf2\x07 \x01(\x0b\x32\x0e.NanoPBOptions') + serialized_pb='\n\x0cnanopb.proto\x1a google/protobuf/descriptor.proto\"4\n\rNanoPBOptions\x12\x10\n\x08max_size\x18\x01 \x01(\x05\x12\x11\n\tmax_count\x18\x02 \x01(\x05:E\n\x0enanopb_fileopt\x12\x1c.google.protobuf.FileOptions\x18\xf2\x07 \x01(\x0b\x32\x0e.NanoPBOptions:G\n\rnanopb_msgopt\x12\x1f.google.protobuf.MessageOptions\x18\xf2\x07 \x01(\x0b\x32\x0e.NanoPBOptions:>\n\x06nanopb\x12\x1d.google.protobuf.FieldOptions\x18\xf2\x07 \x01(\x0b\x32\x0e.NanoPBOptions') +NANOPB_FILEOPT_FIELD_NUMBER = 1010 +nanopb_fileopt = descriptor.FieldDescriptor( + name='nanopb_fileopt', full_name='nanopb_fileopt', index=0, + number=1010, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=True, extension_scope=None, + options=None) +NANOPB_MSGOPT_FIELD_NUMBER = 1010 +nanopb_msgopt = descriptor.FieldDescriptor( + name='nanopb_msgopt', full_name='nanopb_msgopt', index=1, + number=1010, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=True, extension_scope=None, + options=None) NANOPB_FIELD_NUMBER = 1010 nanopb = descriptor.FieldDescriptor( - name='nanopb', full_name='nanopb', index=0, + name='nanopb', full_name='nanopb', index=2, number=1010, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, @@ -57,8 +75,7 @@ _NANOPBOPTIONS = descriptor.Descriptor( serialized_end=102, ) -import google.protobuf.descriptor_pb2 - +DESCRIPTOR.message_types_by_name['NanoPBOptions'] = _NANOPBOPTIONS class NanoPBOptions(message.Message): __metaclass__ = reflection.GeneratedProtocolMessageType @@ -66,6 +83,10 @@ class NanoPBOptions(message.Message): # @@protoc_insertion_point(class_scope:NanoPBOptions) +nanopb_fileopt.message_type = _NANOPBOPTIONS +google.protobuf.descriptor_pb2.FileOptions.RegisterExtension(nanopb_fileopt) +nanopb_msgopt.message_type = _NANOPBOPTIONS +google.protobuf.descriptor_pb2.MessageOptions.RegisterExtension(nanopb_msgopt) nanopb.message_type = _NANOPBOPTIONS google.protobuf.descriptor_pb2.FieldOptions.RegisterExtension(nanopb) # @@protoc_insertion_point(module_scope) diff --git a/tests/Makefile b/tests/Makefile index 7656175..434819c 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -55,7 +55,7 @@ coverage: run_unittests gcov pb_encode.gcda gcov pb_decode.gcda -run_unittests: decode_unittests encode_unittests test_cxxcompile test_encode1 test_encode2 test_encode3 test_decode1 test_decode2 test_decode3 test_encode_callbacks test_decode_callbacks test_missing_fields +run_unittests: decode_unittests encode_unittests test_cxxcompile test_encode1 test_encode2 test_encode3 test_decode1 test_decode2 test_decode3 test_encode_callbacks test_decode_callbacks test_missing_fields test_options rm -f *.gcda ./decode_unittests > /dev/null @@ -82,5 +82,13 @@ run_unittests: decode_unittests encode_unittests test_cxxcompile test_encode1 te ./test_missing_fields +test_options: options.pb.h options.expected + for p in $$(grep . options.expected); do \ + if ! grep -qF "$$p" $<; then \ + echo Expected: $$p; \ + exit 1; \ + fi \ + done + run_fuzztest: test_decode2 bash -c 'I=1; while true; do cat /dev/urandom | ./test_decode2 > /dev/null; I=$$(($$I+1)); echo -en "\r$$I"; done' diff --git a/tests/options.expected b/tests/options.expected new file mode 100644 index 0000000..e184cf9 --- /dev/null +++ b/tests/options.expected @@ -0,0 +1,3 @@ +char filesize[20]; +char msgsize[30]; +char fieldsize[40]; diff --git a/tests/options.proto b/tests/options.proto new file mode 100644 index 0000000..73edf2b --- /dev/null +++ b/tests/options.proto @@ -0,0 +1,28 @@ +/* Test nanopb option parsing. + * options.expected lists the patterns that are searched for in the output. + */ + +import "nanopb.proto"; + +// File level options +option (nanopb_fileopt).max_size = 20; + +message Message1 +{ + required string filesize = 1; +} + +// Message level options +message Message2 +{ + option (nanopb_msgopt).max_size = 30; + required string msgsize = 1; +} + +// Field level options +message Message3 +{ + required string fieldsize = 1 [(nanopb).max_size = 40]; +} + + |