Add generator option sort_by_tag (#542)
For historical reasons fields in .pb.h have been ordered by tag number.
As of 0.4.0 it is no longer necessary, and can now be disabled.
This may become the default in 0.5.0.
diff --git a/generator/nanopb_generator.py b/generator/nanopb_generator.py
index 07beed3..91c8975 100755
--- a/generator/nanopb_generator.py
+++ b/generator/nanopb_generator.py
@@ -362,6 +362,7 @@
self.fixed_count = False
self.callback_datatype = field_options.callback_datatype
self.math_include_required = False
+ self.sort_by_tag = field_options.sort_by_tag
if field_options.type == nanopb_pb2.FT_INLINE:
# Before nanopb-0.3.8, fixed length bytes arrays were specified
@@ -871,7 +872,7 @@
# ---------------------------------------------------------------------------
class OneOf(Field):
- def __init__(self, struct_name, oneof_desc):
+ def __init__(self, struct_name, oneof_desc, oneof_options):
self.struct_name = struct_name
self.name = oneof_desc.name
self.ctype = 'union'
@@ -880,7 +881,8 @@
self.allocation = 'ONEOF'
self.default = None
self.rules = 'ONEOF'
- self.anonymous = False
+ self.anonymous = oneof_options.anonymous_oneof
+ self.sort_by_tag = oneof_options.sort_by_tag
self.has_msg_cb = False
def add_field(self, field):
@@ -888,7 +890,9 @@
field.rules = 'ONEOF'
field.anonymous = self.anonymous
self.fields.append(field)
- self.fields.sort(key = lambda f: f.tag)
+
+ if self.sort_by_tag:
+ self.fields.sort()
if field.pbtype == 'MSG_W_CB':
self.has_msg_cb = True
@@ -1019,11 +1023,8 @@
elif oneof_options.type == nanopb_pb2.FT_IGNORE:
pass # No union and skip fields also
else:
- oneof = OneOf(self.name, f)
- if oneof_options.anonymous_oneof:
- oneof.anonymous = True
+ oneof = OneOf(self.name, f, oneof_options)
self.oneofs[i] = oneof
- self.fields.append(oneof)
else:
sys.stderr.write('Note: This Python protobuf library has no OneOf support\n')
@@ -1038,6 +1039,9 @@
f.oneof_index not in no_unions):
if f.oneof_index in self.oneofs:
self.oneofs[f.oneof_index].add_field(field)
+
+ if self.oneofs[f.oneof_index] not in self.fields:
+ self.fields.append(self.oneofs[f.oneof_index])
else:
self.fields.append(field)
if field.math_include_required:
@@ -1049,6 +1053,9 @@
if field_options.type != nanopb_pb2.FT_IGNORE:
self.fields.append(ExtensionRange(self.name, range_start, field_options))
+ if message_options.sort_by_tag:
+ self.fields.sort()
+
def get_dependencies(self):
'''Get list of type names that this structure refers to.'''
deps = []
@@ -1064,7 +1071,7 @@
# Therefore add a dummy field if an empty message occurs.
result += ' char dummy_field;'
- result += '\n'.join([str(f) for f in sorted(self.fields)])
+ result += '\n'.join([str(f) for f in self.fields])
if Globals.protoc_insertion_points:
result += '\n/* @@protoc_insertion_point(struct:%s) */' % self.name
@@ -1090,7 +1097,7 @@
return '{0}'
parts = []
- for field in sorted(self.fields):
+ for field in self.fields:
parts.append(field.get_initializer(null_init))
return '{' + ', '.join(parts) + '}'
diff --git a/generator/proto/nanopb.proto b/generator/proto/nanopb.proto
index 6a3c620..96e24b4 100644
--- a/generator/proto/nanopb.proto
+++ b/generator/proto/nanopb.proto
@@ -141,6 +141,11 @@
// Override type of the field in generated C code. Only to be used with related field types
optional google.protobuf.FieldDescriptorProto.Type type_override = 27;
+
+ // Due to historical reasons, nanopb orders fields in structs by their tag number
+ // instead of the order in .proto. Set this to false to keep the .proto order.
+ // The default value will probably change to false in nanopb-0.5.0.
+ optional bool sort_by_tag = 28 [default = true];
}
// Extensions to protoc 'Descriptor' type in order to define options
diff --git a/tests/sort_by_tag/SConscript b/tests/sort_by_tag/SConscript
new file mode 100644
index 0000000..96a746d
--- /dev/null
+++ b/tests/sort_by_tag/SConscript
@@ -0,0 +1,8 @@
+# Test sort_by_tag generator option
+
+Import("env")
+
+env.NanopbProto(["sort_by_tag.proto", "sort_by_tag.options"])
+test = env.Program(["sort_by_tag.c", "sort_by_tag.pb.c", "$COMMON/pb_encode.o", "$COMMON/pb_decode.o", "$COMMON/pb_common.o"])
+env.RunTest(test)
+
diff --git a/tests/sort_by_tag/sort_by_tag.c b/tests/sort_by_tag/sort_by_tag.c
new file mode 100644
index 0000000..bdb1e3e
--- /dev/null
+++ b/tests/sort_by_tag/sort_by_tag.c
@@ -0,0 +1,55 @@
+#include "sort_by_tag.pb.h"
+#include <pb_encode.h>
+#include <pb_decode.h>
+#include "unittests.h"
+
+int main()
+{
+ int status = 0;
+ size_t msglen;
+ pb_byte_t buf[256];
+
+ {
+ pb_ostream_t ostream = pb_ostream_from_buffer(buf, sizeof(buf));
+ Unsorted msg = Unsorted_init_zero;
+ COMMENT("Test encoding with unsorted structure");
+
+ TEST(&msg.first < &msg.oneof.second);
+ TEST(&msg.oneof.second < &msg.last);
+
+ msg.first = 101;
+ msg.which_oneof = Unsorted_second_tag;
+ msg.oneof.second = 102;
+ msg.last = 103;
+
+ if (!pb_encode(&ostream, Unsorted_fields, &msg))
+ {
+ fprintf(stderr, "Encoding failed: %s\n", PB_GET_ERROR(&ostream));
+ return 1;
+ }
+
+ msglen = ostream.bytes_written;
+ }
+
+ {
+ pb_istream_t istream = pb_istream_from_buffer(buf, msglen);
+ Sorted msg = Sorted_init_zero;
+ COMMENT("Test decoding with sorted structure");
+
+ if (!pb_decode(&istream, Sorted_fields, &msg))
+ {
+ fprintf(stderr, "Decoding failed: %s\n", PB_GET_ERROR(&istream));
+ return 2;
+ }
+
+ TEST(msg.first == 101);
+ TEST(msg.which_oneof == Sorted_second_tag);
+ TEST(msg.oneof.second == 102);
+ TEST(msg.last == 103);
+
+ TEST(&msg.first > &msg.oneof.second);
+ TEST(&msg.oneof.second > &msg.last);
+ }
+
+ return status;
+}
\ No newline at end of file
diff --git a/tests/sort_by_tag/sort_by_tag.options b/tests/sort_by_tag/sort_by_tag.options
new file mode 100644
index 0000000..b9b1d8e
--- /dev/null
+++ b/tests/sort_by_tag/sort_by_tag.options
@@ -0,0 +1,2 @@
+Unsorted sort_by_tag:false
+Sorted sort_by_tag:true
diff --git a/tests/sort_by_tag/sort_by_tag.proto b/tests/sort_by_tag/sort_by_tag.proto
new file mode 100644
index 0000000..95596bf
--- /dev/null
+++ b/tests/sort_by_tag/sort_by_tag.proto
@@ -0,0 +1,21 @@
+syntax = "proto3";
+
+message Unsorted
+{
+ uint32 first = 99;
+ oneof oneof {
+ uint32 second = 80;
+ uint32 third = 50;
+ }
+ uint32 last = 1;
+}
+
+message Sorted
+{
+ uint32 first = 99;
+ oneof oneof {
+ uint32 second = 80;
+ uint32 third = 50;
+ }
+ uint32 last = 1;
+}