diff options
author | Petteri Aimonen <jpa@git.mail.kapsi.fi> | 2023-10-19 12:14:45 +0300 |
---|---|---|
committer | Petteri Aimonen <jpa@git.mail.kapsi.fi> | 2023-10-19 12:14:45 +0300 |
commit | 499c696de469f2f5aa3f209af8fdc42f96525e0b (patch) | |
tree | 2e7b7ba86631552e8a65c4e0b2caeb6756089285 | |
parent | 31eb134c3fe886d9cc721e40594899eac78e8d2b (diff) |
Automatically break circular dependencies (#881)
It is better to define FT_CALLBACK manually at the point
where it makes sense, but the automatic logic avoids build
failures if you don't care about the details.
-rwxr-xr-x | generator/nanopb_generator.py | 29 | ||||
-rw-r--r-- | tests/recursive_proto/SConscript | 6 | ||||
-rw-r--r-- | tests/recursive_proto/recursive.proto | 12 |
3 files changed, 46 insertions, 1 deletions
diff --git a/generator/nanopb_generator.py b/generator/nanopb_generator.py index 6ea8e84..7b3d7ee 100755 --- a/generator/nanopb_generator.py +++ b/generator/nanopb_generator.py @@ -1697,6 +1697,24 @@ def iterate_extensions(desc, flatten = False, names = Names()): for extension in subdesc.extension: yield subname, extension +def check_recursive_dependencies(message, all_messages, root = None): + '''Returns True if message has a recursive dependency on root (or itself if root is None).''' + + if not isinstance(all_messages, dict): + all_messages = dict((str(m.name), m) for m in all_messages) + + if not root: + root = message + + for dep in message.get_dependencies(): + if dep == str(root.name): + return True + elif dep in all_messages: + if check_recursive_dependencies(all_messages[dep], all_messages, root): + return True + + return False + def sort_dependencies(messages): '''Sort a list of Messages based on dependencies.''' @@ -1861,12 +1879,21 @@ class ProtoFile: if message_options.skip_message: continue + # Apply any configured typename mangling options message = copy.deepcopy(message) for field in message.field: if field.type in (FieldD.TYPE_MESSAGE, FieldD.TYPE_ENUM): field.type_name = self.manglenames.mangle_field_typename(field.type_name) - self.messages.append(Message(name, message, message_options, comment_path, self.comment_locations)) + # Check for circular dependencies + msgobject = Message(name, message, message_options, comment_path, self.comment_locations) + if check_recursive_dependencies(msgobject, self.messages): + sys.stderr.write('Breaking circular dependency at message %s by converting to callback\n' % msgobject.name) + message_options.type = nanopb_pb2.FT_CALLBACK + msgobject = Message(name, message, message_options, comment_path, self.comment_locations) + self.messages.append(msgobject) + + # Process any nested enums for index, enum in enumerate(message.enum_type): name = self.manglenames.create_name(names + enum.name) enum_options = get_nanopb_suboptions(enum, message_options, name) diff --git a/tests/recursive_proto/SConscript b/tests/recursive_proto/SConscript new file mode 100644 index 0000000..8205851 --- /dev/null +++ b/tests/recursive_proto/SConscript @@ -0,0 +1,6 @@ +# Test building of a recursive protobuf definition + +Import('env') + +env.NanopbProto("recursive.proto") +env.Object("recursive.pb.c") diff --git a/tests/recursive_proto/recursive.proto b/tests/recursive_proto/recursive.proto new file mode 100644 index 0000000..b4eee22 --- /dev/null +++ b/tests/recursive_proto/recursive.proto @@ -0,0 +1,12 @@ +message SingleRecursion { + optional SingleRecursion msg = 1; +} + +message Recurse1 { + optional Recurse2 msg = 1; +} + +message Recurse2 { + optional Recurse1 msg = 1; + optional SingleRecursion msg2 = 2; +} |