From 6266b779b7487c7c2097094ed11b999442aedb20 Mon Sep 17 00:00:00 2001 From: Dave Allison Date: Wed, 19 Aug 2026 11:28:52 -0700 Subject: [PATCH 1/5] Add ROS metadata handling --- phaser/BUILD.bazel | 22 ++ phaser/compiler/gen.cc | 6 +- phaser/compiler/message_gen.cc | 259 ++++++++++++++++++ phaser/compiler/message_gen.h | 2 + phaser/options.proto | 38 +++ phaser/phaser_library.bzl | 20 +- phaser/ros_metadata_protobuf_frontend_test.cc | 39 +++ phaser/ros_metadata_ros_frontend_test.cc | 28 ++ phaser/runtime/BUILD.bazel | 12 + phaser/runtime/md5.cc | 138 ++++++++++ phaser/runtime/md5.h | 15 + phaser/runtime/md5_test.cc | 36 +++ phaser/runtime/runtime.h | 1 + phaser/testdata/BUILD | 23 ++ phaser/testdata/RosMetadata.proto | 45 +++ 15 files changed, 680 insertions(+), 4 deletions(-) create mode 100644 phaser/ros_metadata_protobuf_frontend_test.cc create mode 100644 phaser/ros_metadata_ros_frontend_test.cc create mode 100644 phaser/runtime/md5.cc create mode 100644 phaser/runtime/md5.h create mode 100644 phaser/runtime/md5_test.cc create mode 100644 phaser/testdata/RosMetadata.proto diff --git a/phaser/BUILD.bazel b/phaser/BUILD.bazel index d983157..5a27b0b 100644 --- a/phaser/BUILD.bazel +++ b/phaser/BUILD.bazel @@ -76,6 +76,28 @@ cc_test( ], ) +cc_test( + name = "ros_metadata_protobuf_frontend_test", + srcs = ["ros_metadata_protobuf_frontend_test.cc"], + copts = PHASER_COPTS, + deps = [ + "//phaser/runtime:phaser_runtime", + "//phaser/testdata:ros_metadata_protobuf_phaser", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "ros_metadata_ros_frontend_test", + srcs = ["ros_metadata_ros_frontend_test.cc"], + copts = PHASER_COPTS, + deps = [ + "//phaser/runtime:phaser_runtime", + "//phaser/testdata:ros_metadata_ros_phaser", + "@com_google_googletest//:gtest_main", + ], +) + cc_test( name = "ros_intrinsics_test", srcs = ["ros_intrinsics_test.cc"], diff --git a/phaser/compiler/gen.cc b/phaser/compiler/gen.cc index 716a5ed..f4472d0 100644 --- a/phaser/compiler/gen.cc +++ b/phaser/compiler/gen.cc @@ -130,7 +130,8 @@ bool CodeGenerator::Generate( if (file->message_type_count() == 0 && file->enum_type_count() == 0) { return true; } - if (file->name() == std::string("google/protobuf/descriptor.proto")) { + if (file->name() == std::string("google/protobuf/descriptor.proto") || + file->name() == std::string("phaser/options.proto")) { return true; } @@ -239,7 +240,8 @@ void Generator::GenerateHeaders(std::ostream& os, std::string* error) { if (dep->message_type_count() == 0 && dep->enum_type_count() == 0) { continue; } - if (dep->name() == std::string("google/protobuf/descriptor.proto")) { + if (dep->name() == std::string("google/protobuf/descriptor.proto") || + dep->name() == std::string("phaser/options.proto")) { continue; } std::string base = GeneratedFilename( diff --git a/phaser/compiler/message_gen.cc b/phaser/compiler/message_gen.cc index 5667c1b..af8791c 100644 --- a/phaser/compiler/message_gen.cc +++ b/phaser/compiler/message_gen.cc @@ -13,6 +13,7 @@ #include #include +#include "absl/strings/escaping.h" #include "absl/strings/str_format.h" #include "absl/strings/str_replace.h" #include "phaser/options.pb.h" @@ -138,6 +139,159 @@ static bool IsFixedWireType( } } +static std::string RosBaseType(std::string type) { + const size_t array = type.find('['); + if (array != std::string::npos) { + type.resize(array); + } + return type; +} + +static bool IsRosBuiltinType(const std::string& type) { + static const absl::flat_hash_set builtin_types = { + "bool", "byte", "char", "duration", "float32", "float64", + "int8", "int16", "int32", "int64", "string", "time", + "uint8", "uint16", "uint32", "uint64", + }; + return builtin_types.contains(RosBaseType(type)); +} + +static std::string RosArraySuffix( + const google::protobuf::FieldDescriptor* field) { + if (!field->is_repeated()) { + return ""; + } + if (field->options().HasExtension(phaser::array_size)) { + return absl::StrFormat( + "[%u]", field->options().GetExtension(phaser::array_size)); + } + return "[]"; +} + +static std::string InferredRosFieldType( + const google::protobuf::FieldDescriptor* field) { + using Field = google::protobuf::FieldDescriptor; + std::string type; + switch (field->type()) { + case Field::TYPE_BOOL: + type = "bool"; + break; + case Field::TYPE_INT64: + type = "int64"; + break; + case Field::TYPE_UINT64: + type = "uint64"; + break; + case Field::TYPE_FLOAT: + type = "float32"; + break; + case Field::TYPE_DOUBLE: + type = "float64"; + break; + case Field::TYPE_STRING: + type = "string"; + break; + case Field::TYPE_MESSAGE: + if (field->message_type()->full_name() == "google.protobuf.Timestamp") { + type = "time"; + } else if (field->message_type()->full_name() == + "google.protobuf.Duration") { + type = "duration"; + } else if (field->message_type()->options().HasExtension( + phaser::ros_message)) { + type = field->message_type() + ->options() + .GetExtension(phaser::ros_message) + .data_type(); + } + break; + default: + // int32, uint32, bytes, and enums each have multiple possible ROS + // source types and therefore require an explicit override. + break; + } + if (type.empty()) { + return ""; + } + return type + RosArraySuffix(field); +} + +static std::string RosFieldType( + const google::protobuf::FieldDescriptor* field) { + if (field->options().HasExtension(phaser::ros_field)) { + const auto& metadata = + field->options().GetExtension(phaser::ros_field); + if (!metadata.type().empty()) { + return metadata.type(); + } + } + return InferredRosFieldType(field); +} + +static std::string RosFieldName( + const google::protobuf::FieldDescriptor* field) { + if (field->options().HasExtension(phaser::ros_field)) { + const auto& metadata = + field->options().GetExtension(phaser::ros_field); + if (!metadata.name().empty()) { + return metadata.name(); + } + } + return std::string(field->name()); +} + +static void AppendRosDependencies(const google::protobuf::Descriptor* message, + absl::flat_hash_set* seen, + std::string* definition) { + for (int i = 0; i < message->field_count(); ++i) { + const auto* field = message->field(i); + const auto& field_metadata = + field->options().GetExtension(phaser::ros_field); + if (IsRosBuiltinType(RosFieldType(field))) { + continue; + } + + std::string data_type; + std::string source_definition; + const google::protobuf::Descriptor* dependency = nullptr; + if (!field_metadata.nested_data_type().empty()) { + data_type = field_metadata.nested_data_type(); + source_definition = field_metadata.nested_definition(); + } else if (field->type() == + google::protobuf::FieldDescriptor::TYPE_MESSAGE && + field->message_type()->options().HasExtension( + phaser::ros_message)) { + dependency = field->message_type(); + const auto& dependency_metadata = + dependency->options().GetExtension(phaser::ros_message); + data_type = dependency_metadata.data_type(); + source_definition = dependency_metadata.definition(); + } else { + continue; + } + + if (!seen->insert(data_type).second) { + continue; + } + *definition += std::string(80, '=') + "\n"; + *definition += "MSG: " + data_type + "\n"; + *definition += source_definition + "\n"; + if (dependency != nullptr) { + AppendRosDependencies(dependency, seen, definition); + } + } +} + +static std::string RosFullDefinition( + const google::protobuf::Descriptor* message) { + const auto& metadata = message->options().GetExtension(phaser::ros_message); + std::string definition = metadata.definition() + "\n"; + absl::flat_hash_set seen; + AppendRosDependencies(message, &seen, &definition); + definition.pop_back(); + return definition; +} + std::string MessageGenerator::SanitizedIdentifier( const std::string& name) const { if (IsCppReservedWord(name)) { @@ -215,6 +369,9 @@ absl::Status MessageGenerator::ValidateArraySizeOption( } absl::Status MessageGenerator::ValidateFieldOptions() const { + if (absl::Status status = ValidateRosMetadataOptions(); !status.ok()) { + return status; + } if (IsRosFrontend() && IsRosHeader(message_) && added_namespace_.empty()) { return absl::InvalidArgumentError( "ROS frontend generation for std_msgs.Header requires add_namespace " @@ -246,6 +403,56 @@ absl::Status MessageGenerator::ValidateFieldOptions() const { return absl::OkStatus(); } +absl::Status MessageGenerator::ValidateRosMetadataOptions() const { + if (!message_->options().HasExtension(phaser::ros_message)) { + return absl::OkStatus(); + } + const auto& metadata = message_->options().GetExtension(phaser::ros_message); + if (metadata.data_type().empty()) { + return absl::InvalidArgumentError(absl::StrFormat( + "phaser.ros_message.data_type must not be empty on message %s", + message_->full_name())); + } + for (int i = 0; i < message_->field_count(); ++i) { + const auto* field = message_->field(i); + const std::string field_type = RosFieldType(field); + if (field_type.empty()) { + return absl::InvalidArgumentError(absl::StrFormat( + "ROS type for field %s.%s is ambiguous; set phaser.ros_field.type", + message_->full_name(), field->name())); + } + const auto& field_metadata = + field->options().GetExtension(phaser::ros_field); + if (!IsRosBuiltinType(field_type) && + field->type() != google::protobuf::FieldDescriptor::TYPE_MESSAGE && + field_metadata.nested_md5_text().empty()) { + return absl::InvalidArgumentError(absl::StrFormat( + "non-builtin ROS field %s.%s must map to a protobuf message or " + "provide nested_md5_text", + message_->full_name(), field->name())); + } + if (!field_metadata.nested_md5_text().empty() && + (field_metadata.nested_data_type().empty() || + field_metadata.nested_definition().empty())) { + return absl::InvalidArgumentError(absl::StrFormat( + "non-protobuf ROS field %s.%s must provide nested_data_type and " + "nested_definition", + message_->full_name(), field->name())); + } + if (!IsRosBuiltinType(field_type) && + field->type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE && + !field->message_type()->options().HasExtension(phaser::ros_message) && + field_metadata.nested_md5_text().empty()) { + return absl::InvalidArgumentError(absl::StrFormat( + "non-builtin ROS field %s.%s references %s, which must declare " + "phaser.ros_message", + message_->full_name(), field->name(), + field->message_type()->full_name())); + } + } + return absl::OkStatus(); +} + absl::Status MessageGenerator::ValidateRosHeaderDescriptor() const { if (!IsRosHeader(message_)) { return absl::OkStatus(); @@ -882,6 +1089,7 @@ absl::Status MessageGenerator::GenerateHeader(std::ostream& os) { << message_->full_name() << "\"; }\n"; os << " static constexpr std::string_view Name() { return \"" << message_->name() << "\"; }\n\n"; + GenerateRosMetadata(os); os << " std::string GetName() const override { return std::string(Name()); " "}\n"; @@ -939,6 +1147,57 @@ absl::Status MessageGenerator::GenerateHeader(std::ostream& os) { return absl::OkStatus(); } +void MessageGenerator::GenerateRosMetadata(std::ostream& os) { + if (!message_->options().HasExtension(phaser::ros_message)) { + return; + } + const auto& metadata = message_->options().GetExtension(phaser::ros_message); + os << " static constexpr std::string_view RosDataType() { return \"" + << absl::CEscape(metadata.data_type()) << "\"; }\n"; + os << " static constexpr std::string_view RosDefinition() { return \"" + << absl::CEscape(RosFullDefinition(message_)) << "\"; }\n"; + os << " static std::string RosMd5() {\n"; + os << " std::string text;\n"; + + bool first_declaration = true; + auto generate_separator = [&os, &first_declaration]() { + if (!first_declaration) { + os << " text.push_back('\\n');\n"; + } + first_declaration = false; + }; + auto generate_append_literal = + [&os, &generate_separator](const std::string& line) { + generate_separator(); + os << " text += \"" << absl::CEscape(line) << "\";\n"; + }; + for (const auto& constant : metadata.constants()) { + generate_append_literal(constant); + } + for (int i = 0; i < message_->field_count(); ++i) { + const auto* field = message_->field(i); + const auto& field_metadata = + field->options().GetExtension(phaser::ros_field); + const std::string field_type = RosFieldType(field); + const std::string field_name = RosFieldName(field); + generate_separator(); + if (IsRosBuiltinType(field_type)) { + os << " text += \"" + << absl::CEscape(field_type + " " + field_name) << "\";\n"; + } else if (!field_metadata.nested_md5_text().empty()) { + os << " text += ::phaser::Md5(\"" + << absl::CEscape(field_metadata.nested_md5_text()) << "\");\n"; + os << " text += \" " << absl::CEscape(field_name) << "\";\n"; + } else { + os << " text += " << MessageName(field->message_type()) + << "::RosMd5();\n"; + os << " text += \" " << absl::CEscape(field_name) << "\";\n"; + } + } + os << " return ::phaser::Md5(text);\n"; + os << " }\n\n"; +} + void MessageGenerator::GenerateRosSyncToPayload(std::ostream& os) { os << " void SyncToPayload() const override {\n"; for (const auto& field : fields_) { diff --git a/phaser/compiler/message_gen.h b/phaser/compiler/message_gen.h index 2742810..8860c28 100644 --- a/phaser/compiler/message_gen.h +++ b/phaser/compiler/message_gen.h @@ -101,6 +101,7 @@ class MessageGenerator { void GeneratePublicFieldDeclarations(std::ostream& os); void GenerateRosOneofTypes(std::ostream& os); void GenerateRosOwnerCopyMove(std::ostream& os, bool decl); + void GenerateRosMetadata(std::ostream& os); void GenerateRosSyncToPayload(std::ostream& os); void GenerateProtobufAccessors(std::ostream& os); void GenerateFieldProtobufAccessors(std::ostream& os); @@ -205,6 +206,7 @@ class MessageGenerator { std::string RosIntrinsicCType( const google::protobuf::FieldDescriptor* field); absl::Status ValidateFieldOptions() const; + absl::Status ValidateRosMetadataOptions() const; absl::Status ValidateArraySizeOption( const google::protobuf::FieldDescriptor* field) const; absl::Status ValidateRosHeaderDescriptor() const; diff --git a/phaser/options.proto b/phaser/options.proto index 63938ec..d5fcc50 100644 --- a/phaser/options.proto +++ b/phaser/options.proto @@ -4,9 +4,47 @@ package phaser; import "google/protobuf/descriptor.proto"; +message RosMessageMetadata { + // Original ROS datatype in package/Message form. + string data_type = 1; + + // Original ROS source definition. Phaser appends transitive definitions. + string definition = 2; + + // Canonical constant declarations used by the ROS MD5 algorithm. + repeated string constants = 3; +} + +message RosFieldMetadata { + // Original ROS field type, including any array suffix. Omit when the + // protobuf type maps unambiguously to a ROS type. + string type = 1; + + // Original ROS field name. Omit when it is unchanged. + string name = 2; + + // Canonical MD5 text for a nested ROS message that is not represented as a + // protobuf message, such as a constants-only ROS enum message. + string nested_md5_text = 3; + + // Datatype for the non-protobuf nested ROS message. + string nested_data_type = 4; + + // Source definition for the non-protobuf nested ROS message. + string nested_definition = 5; +} + extend google.protobuf.FieldOptions { // Fixed logical extent for a repeated field. Must be a positive integer. // Only valid on non-map repeated fields. ROS frontend generation exposes a // fixed-extent array facade; protobuf frontend keeps repeated accessors. uint32 array_size = 50001; + + // Overrides for ROS information that cannot be inferred from the field. + RosFieldMetadata ros_field = 50002; +} + +extend google.protobuf.MessageOptions { + // Original ROS message identity and source definition. + RosMessageMetadata ros_message = 50001; } diff --git a/phaser/phaser_library.bzl b/phaser/phaser_library.bzl index 64e8d29..9d98c15 100644 --- a/phaser/phaser_library.bzl +++ b/phaser/phaser_library.bzl @@ -162,7 +162,11 @@ def _phaser_impl(ctx): # #include "phaser/testdata/Test.phaser.h" # so we create the symlink: # Test.phaser.h -> phaser/testdata/phaser/testdata/Test.phaser.h - if out_file.extension == "h" and out in dep[MessageInfo].symlink_headers: + if ( + ctx.attr.direct_header_symlinks and + out_file.extension == "h" and + out in dep[MessageInfo].symlink_headers + ): prefix = paths.join(ctx.attr.target_name, package_name) symlink_name = out_file.short_path[len(prefix) + 1:] if symlink_name.startswith(package_name): @@ -211,6 +215,7 @@ _phaser_gen = rule( aspects = [phaser_aspect], ), "add_namespace": attr.string(), + "direct_header_symlinks": attr.bool(default = True), "package_name": attr.string(), "target_name": attr.string(), "frontend": attr.string(default = "protobuf"), @@ -235,7 +240,15 @@ _split_files = rule( implementation = _split_files_impl, ) -def phaser_library(name, deps = [], runtime = "@phaser//phaser/runtime:phaser_runtime", add_namespace = "", enable_active_message = False, frontend = "protobuf", cc_deps = []): +def phaser_library( + name, + deps = [], + runtime = "@phaser//phaser/runtime:phaser_runtime", + add_namespace = "", + enable_active_message = False, + frontend = "protobuf", + cc_deps = [], + direct_header_symlinks = True): """ Generate a cc_libary for protobuf files specified in deps. @@ -251,6 +264,8 @@ def phaser_library(name, deps = [], runtime = "@phaser//phaser/runtime:phaser_ru frontend: generated C++ API style, either "protobuf" (default) or "ros". cc_deps: additional C++ dependencies required by generated headers, such as ROS1 message/runtime libraries for intrinsic ROS fields. + direct_header_symlinks: create short direct-source header aliases. + Disable this when generating multiple frontends from one proto target. """ if frontend not in ("protobuf", "ros"): fail("phaser_library frontend must be 'protobuf' or 'ros', got: {}".format(frontend)) @@ -261,6 +276,7 @@ def phaser_library(name, deps = [], runtime = "@phaser//phaser/runtime:phaser_ru name = phaser, deps = deps, add_namespace = add_namespace, + direct_header_symlinks = direct_header_symlinks, package_name = native.package_name(), target_name = name, enable_active_message = enable_active_message, diff --git a/phaser/ros_metadata_protobuf_frontend_test.cc b/phaser/ros_metadata_protobuf_frontend_test.cc new file mode 100644 index 0000000..5097540 --- /dev/null +++ b/phaser/ros_metadata_protobuf_frontend_test.cc @@ -0,0 +1,39 @@ +// Copyright 2024-2026 David Allison +// All Rights Reserved +// See LICENSE file for licensing information. + +#include + +#include "phaser/runtime/md5.h" +#include "phaser/testdata/ros_metadata_protobuf_phaser/phaser/testdata/RosMetadata.phaser.h" + +namespace ros_metadata::protobuf_api { +namespace { + +TEST(RosMetadataProtobufFrontendTest, MatchesRosMetadata) { + EXPECT_EQ(Bool::RosDataType(), "std_msgs/Bool"); + EXPECT_EQ(Bool::RosDefinition(), "bool data\n"); + EXPECT_EQ(Bool::RosMd5(), "8b94c1b53db61fb6aed406028ad6332a"); + + EXPECT_EQ(Wrapper::RosDataType(), "example_msgs/Wrapper"); + EXPECT_EQ(Wrapper::RosDefinition(), + "uint8 READY=1\nstd_msgs/Bool child\nint32[3] samples\n" + "example_msgs/Status status\nbool ready\n\n" + "==================================================================" + "==============\n" + "MSG: std_msgs/Bool\n" + "bool data\n\n" + "==================================================================" + "==============\n" + "MSG: example_msgs/Status\n" + "uint8 OK=0\nuint8 BAD=1\n"); + EXPECT_EQ( + Wrapper::RosMd5(), + ::phaser::Md5("uint8 READY=1\n" + "8b94c1b53db61fb6aed406028ad6332a child\n" + "int32[3] samples\n" + + ::phaser::Md5("uint8 OK=0\nuint8 BAD=1") + " status\nbool ready")); +} + +} // namespace +} // namespace ros_metadata::protobuf_api diff --git a/phaser/ros_metadata_ros_frontend_test.cc b/phaser/ros_metadata_ros_frontend_test.cc new file mode 100644 index 0000000..ea47e6a --- /dev/null +++ b/phaser/ros_metadata_ros_frontend_test.cc @@ -0,0 +1,28 @@ +// Copyright 2024-2026 David Allison +// All Rights Reserved +// See LICENSE file for licensing information. + +#include + +#include "phaser/runtime/md5.h" +#include "phaser/testdata/ros_metadata_ros_phaser/phaser/testdata/RosMetadata.phaser.h" + +namespace ros_metadata::ros_api { +namespace { + +TEST(RosMetadataRosFrontendTest, MatchesRosMetadata) { + EXPECT_EQ(Bool::RosDataType(), "std_msgs/Bool"); + EXPECT_EQ(Bool::RosDefinition(), "bool data\n"); + EXPECT_EQ(Bool::RosMd5(), "8b94c1b53db61fb6aed406028ad6332a"); + + EXPECT_EQ(Wrapper::RosDataType(), "example_msgs/Wrapper"); + EXPECT_EQ( + Wrapper::RosMd5(), + ::phaser::Md5("uint8 READY=1\n" + "8b94c1b53db61fb6aed406028ad6332a child\n" + "int32[3] samples\n" + + ::phaser::Md5("uint8 OK=0\nuint8 BAD=1") + " status\nbool ready")); +} + +} // namespace +} // namespace ros_metadata::ros_api diff --git a/phaser/runtime/BUILD.bazel b/phaser/runtime/BUILD.bazel index 3ae8eba..0d31d38 100644 --- a/phaser/runtime/BUILD.bazel +++ b/phaser/runtime/BUILD.bazel @@ -6,6 +6,7 @@ package(default_visibility = ["//visibility:public"]) cc_library( name = "phaser_runtime", srcs = [ + "md5.cc", "message.cc", "phaser_bank.cc", ], @@ -15,6 +16,7 @@ cc_library( "arrays.h", "fields.h", "iterators.h", + "md5.h", "message.h", "runtime.h", "ros_wireformat.h", @@ -35,6 +37,16 @@ cc_library( ], ) +cc_test( + name = "md5_test", + srcs = ["md5_test.cc"], + copts = PHASER_COPTS, + deps = [ + ":phaser_runtime", + "@com_google_googletest//:gtest_main", + ], +) + cc_test( name = "message_test", srcs = [ diff --git a/phaser/runtime/md5.cc b/phaser/runtime/md5.cc new file mode 100644 index 0000000..2f620d8 --- /dev/null +++ b/phaser/runtime/md5.cc @@ -0,0 +1,138 @@ +// Copyright 2024-2026 David Allison +// All Rights Reserved +// See LICENSE file for licensing information. + +#include "phaser/runtime/md5.h" + +#include +#include +#include + +namespace phaser { +namespace { + +constexpr std::array kConstants = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, + 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, + 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, + 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa, + 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, + 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391, +}; + +constexpr std::array kRotations = { + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, +}; + +constexpr uint32_t RotateLeft(uint32_t value, uint32_t count) { + return (value << count) | (value >> (32 - count)); +} + +uint32_t ReadLittleEndian32(const uint8_t* bytes) { + return uint32_t{bytes[0]} | (uint32_t{bytes[1]} << 8) | + (uint32_t{bytes[2]} << 16) | (uint32_t{bytes[3]} << 24); +} + +void ProcessBlock(const uint8_t* block, std::array* state) { + std::array words; + for (size_t i = 0; i < words.size(); ++i) { + words[i] = ReadLittleEndian32(block + i * 4); + } + + uint32_t a = (*state)[0]; + uint32_t b = (*state)[1]; + uint32_t c = (*state)[2]; + uint32_t d = (*state)[3]; + + for (size_t i = 0; i < kConstants.size(); ++i) { + uint32_t function = 0; + size_t word_index = 0; + if (i < 16) { + function = (b & c) | (~b & d); + word_index = i; + } else if (i < 32) { + function = (d & b) | (~d & c); + word_index = (5 * i + 1) % 16; + } else if (i < 48) { + function = b ^ c ^ d; + word_index = (3 * i + 5) % 16; + } else { + function = c ^ (b | ~d); + word_index = (7 * i) % 16; + } + + const uint32_t previous_d = d; + d = c; + c = b; + b += RotateLeft(a + function + kConstants[i] + words[word_index], + kRotations[i]); + a = previous_d; + } + + (*state)[0] += a; + (*state)[1] += b; + (*state)[2] += c; + (*state)[3] += d; +} + +char HexDigit(uint8_t value) { + constexpr char kHexDigits[] = "0123456789abcdef"; + return kHexDigits[value]; +} + +} // namespace + +std::string Md5(std::string_view input) { + std::array state = { + 0x67452301, + 0xefcdab89, + 0x98badcfe, + 0x10325476, + }; + + const auto* bytes = reinterpret_cast(input.data()); + size_t offset = 0; + while (input.size() - offset >= 64) { + ProcessBlock(bytes + offset, &state); + offset += 64; + } + + std::array tail = {}; + const size_t remaining = input.size() - offset; + if (remaining != 0) { + std::memcpy(tail.data(), bytes + offset, remaining); + } + tail[remaining] = 0x80; + + const size_t tail_size = remaining < 56 ? 64 : 128; + const uint64_t bit_length = uint64_t{input.size()} * 8; + for (size_t i = 0; i < 8; ++i) { + tail[tail_size - 8 + i] = + static_cast((bit_length >> (i * 8)) & 0xff); + } + ProcessBlock(tail.data(), &state); + if (tail_size == 128) { + ProcessBlock(tail.data() + 64, &state); + } + + std::string digest(32, '\0'); + size_t digest_offset = 0; + for (uint32_t word : state) { + for (size_t i = 0; i < 4; ++i) { + const uint8_t byte = static_cast((word >> (i * 8)) & 0xff); + digest[digest_offset++] = HexDigit(byte >> 4); + digest[digest_offset++] = HexDigit(byte & 0x0f); + } + } + return digest; +} + +} // namespace phaser diff --git a/phaser/runtime/md5.h b/phaser/runtime/md5.h new file mode 100644 index 0000000..2fa8f9d --- /dev/null +++ b/phaser/runtime/md5.h @@ -0,0 +1,15 @@ +// Copyright 2024-2026 David Allison +// All Rights Reserved +// See LICENSE file for licensing information. + +#pragma once + +#include +#include + +namespace phaser { + +/// Computes the lowercase hexadecimal MD5 digest of `input`. +std::string Md5(std::string_view input); + +} // namespace phaser diff --git a/phaser/runtime/md5_test.cc b/phaser/runtime/md5_test.cc new file mode 100644 index 0000000..20f54ef --- /dev/null +++ b/phaser/runtime/md5_test.cc @@ -0,0 +1,36 @@ +// Copyright 2024-2026 David Allison +// All Rights Reserved +// See LICENSE file for licensing information. + +#include "phaser/runtime/md5.h" + +#include + +#include + +namespace phaser { +namespace { + +TEST(Md5Test, MatchesPublishedTestVectors) { + EXPECT_EQ(Md5(""), "d41d8cd98f00b204e9800998ecf8427e"); + EXPECT_EQ(Md5("a"), "0cc175b9c0f1b6a831c399e269772661"); + EXPECT_EQ(Md5("abc"), "900150983cd24fb0d6963f7d28e17f72"); + EXPECT_EQ(Md5("message digest"), "f96b697d7cb7938d525a2f31aaf161d0"); + EXPECT_EQ(Md5("abcdefghijklmnopqrstuvwxyz"), + "c3fcd3d76192e4007dfb496cca67e13b"); + EXPECT_EQ( + Md5("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), + "d174ab98d277d9f5a5611c2c9f419d9f"); + EXPECT_EQ( + Md5("123456789012345678901234567890123456789012345678901234567890123" + "45678901234567890"), + "57edf4a22be3c955ac49da2e2107b67a"); +} + +TEST(Md5Test, HandlesMultipleCompleteBlocks) { + EXPECT_EQ(Md5(std::string(1'000'000, 'a')), + "7707d6ae4e027c70eea2a935c2296f21"); +} + +} // namespace +} // namespace phaser diff --git a/phaser/runtime/runtime.h b/phaser/runtime/runtime.h index 171e670..44beac7 100644 --- a/phaser/runtime/runtime.h +++ b/phaser/runtime/runtime.h @@ -9,6 +9,7 @@ #include "phaser/runtime/arrays.h" #include "phaser/runtime/fields.h" #include "phaser/runtime/iterators.h" +#include "phaser/runtime/md5.h" #include "phaser/runtime/message.h" #include "phaser/runtime/phaser_bank.h" #include "phaser/runtime/ros_wireformat.h" diff --git a/phaser/testdata/BUILD b/phaser/testdata/BUILD index 30cef86..f8ee908 100644 --- a/phaser/testdata/BUILD +++ b/phaser/testdata/BUILD @@ -87,6 +87,29 @@ phaser_library( deps = [":ros_compile_proto"], ) +proto_library( + name = "ros_metadata_proto", + srcs = ["RosMetadata.proto"], + deps = ["//phaser:options_proto"], +) + +phaser_library( + name = "ros_metadata_protobuf_phaser", + add_namespace = "protobuf_api", + direct_header_symlinks = False, + runtime = "//phaser/runtime:phaser_runtime", + deps = [":ros_metadata_proto"], +) + +phaser_library( + name = "ros_metadata_ros_phaser", + add_namespace = "ros_api", + direct_header_symlinks = False, + frontend = "ros", + runtime = "//phaser/runtime:phaser_runtime", + deps = [":ros_metadata_proto"], +) + cc_library( name = "ros1_shim", hdrs = glob(["ros_shim/**/*.h"]), diff --git a/phaser/testdata/RosMetadata.proto b/phaser/testdata/RosMetadata.proto new file mode 100644 index 0000000..474d3e7 --- /dev/null +++ b/phaser/testdata/RosMetadata.proto @@ -0,0 +1,45 @@ +syntax = "proto3"; + +package ros_metadata; + +import "phaser/options.proto"; + +enum Status { + STATUS_OK = 0; + STATUS_BAD = 1; +} + +message Bool { + option (phaser.ros_message) = { + data_type: "std_msgs/Bool" + definition: "bool data\n" + }; + + bool data = 1; +} + +message Wrapper { + option (phaser.ros_message) = { + data_type: "example_msgs/Wrapper" + definition: "uint8 READY=1\nstd_msgs/Bool child\nint32[3] samples\nexample_msgs/Status status\nbool ready\n" + constants: "uint8 READY=1" + }; + + Bool child = 1; + repeated int32 samples = 2 [ + (phaser.array_size) = 3, + (phaser.ros_field) = { + type: "int32[3]" + } + ]; + Status status_ = 3 [(phaser.ros_field) = { + type: "example_msgs/Status" + name: "status" + nested_md5_text: "uint8 OK=0\nuint8 BAD=1" + nested_data_type: "example_msgs/Status" + nested_definition: "uint8 OK=0\nuint8 BAD=1\n" + }]; + bool ready_ = 4 [(phaser.ros_field) = { + name: "ready" + }]; +} From 95f797f8e3b1660d6c5f045e9bfb9f3df0fbf37e Mon Sep 17 00:00:00 2001 From: Dave Allison Date: Wed, 19 Aug 2026 13:03:13 -0700 Subject: [PATCH 2/5] Fix phaser generator for field names --- phaser/BUILD.bazel | 1 + phaser/compiler/gen.cc | 16 +- phaser/compiler/gen.h | 5 +- phaser/compiler/message_gen.cc | 768 +++++++++++------- phaser/compiler/message_gen.h | 9 +- phaser/options.proto | 40 +- phaser/phaser_library.bzl | 12 +- phaser/ros_compile_test.cc | 50 +- phaser/ros_metadata_protobuf_frontend_test.cc | 23 +- phaser/ros_metadata_ros_frontend_test.cc | 13 +- phaser/testdata/BUILD | 8 +- phaser/testdata/RosCompile.proto | 21 + phaser/testdata/RosMetadata.proto | 55 +- 13 files changed, 653 insertions(+), 368 deletions(-) diff --git a/phaser/BUILD.bazel b/phaser/BUILD.bazel index 5a27b0b..4b7c690 100644 --- a/phaser/BUILD.bazel +++ b/phaser/BUILD.bazel @@ -72,6 +72,7 @@ cc_test( "//phaser/runtime:phaser_runtime", "//phaser/testdata:ros_compile_cc_proto", "//phaser/testdata:ros_compile_phaser", + "@com_google_absl//absl/types:span", "@com_google_googletest//:gtest_main", ], ) diff --git a/phaser/compiler/gen.cc b/phaser/compiler/gen.cc index f4472d0..f1efb7c 100644 --- a/phaser/compiler/gen.cc +++ b/phaser/compiler/gen.cc @@ -107,6 +107,10 @@ bool CodeGenerator::Generate( generate_active_message_ = option.second.empty() || option.second == "true" || option.second == "1"; + } else if (option.first == "ros_metadata") { + generate_ros_metadata_ = option.second.empty() || + option.second == "true" || + option.second == "1"; } else if (option.first == "frontend") { if (option.second == "protobuf" || option.second.empty()) { frontend_style_ = FrontendStyle::kProtobuf; @@ -123,6 +127,9 @@ bool CodeGenerator::Generate( const FrontendStyle effective_frontend = EffectiveFrontendStyle(file, frontend_style_); + const bool effective_ros_metadata = + generate_ros_metadata_ && + file->package().rfind("google.protobuf", 0) != 0; // Custom option schemas and other message-free protos need no C++ output. // descriptor.proto is imported for extensions but must not be emitted as a @@ -136,7 +143,8 @@ bool CodeGenerator::Generate( } Generator gen(file, added_namespace_, package_name_, target_name_, - generate_active_message_, effective_frontend); + generate_active_message_, effective_frontend, + effective_ros_metadata); std::string filename = GeneratedFilename(package_name_, target_name_, std::string(file->name())); @@ -208,17 +216,19 @@ void Generator::CloseNamespace(std::ostream& os) { Generator::Generator(const google::protobuf::FileDescriptor* file, const std::string& ns, const std::string& pn, const std::string& tn, bool generate_active_message, - FrontendStyle frontend_style) + FrontendStyle frontend_style, + bool generate_ros_metadata) : file_(file), added_namespace_(ns), package_name_(pn), target_name_(tn), generate_active_message_(generate_active_message), + generate_ros_metadata_(generate_ros_metadata), frontend_style_(frontend_style) { for (int i = 0; i < file->message_type_count(); i++) { message_gens_.push_back(std::make_unique( file->message_type(i), added_namespace_, std::string(file->package()), - generate_active_message_, frontend_style_)); + generate_active_message_, frontend_style_, generate_ros_metadata_)); } // Enums for (int i = 0; i < file->enum_type_count(); i++) { diff --git a/phaser/compiler/gen.h b/phaser/compiler/gen.h index 762a1c7..b56a5bc 100644 --- a/phaser/compiler/gen.h +++ b/phaser/compiler/gen.h @@ -36,6 +36,7 @@ class CodeGenerator : public google::protobuf::compiler::CodeGenerator { // field. Enabled via the `active_message=true` plugin command-line option // (set by phaser_library(enable_active_message = True)). mutable bool generate_active_message_ = false; + mutable bool generate_ros_metadata_ = false; mutable FrontendStyle frontend_style_ = FrontendStyle::kProtobuf; }; @@ -44,7 +45,8 @@ class Generator { Generator(const google::protobuf::FileDescriptor* file, const std::string& ns, const std::string& pn, const std::string& tn, bool generate_active_message = false, - FrontendStyle frontend_style = FrontendStyle::kProtobuf); + FrontendStyle frontend_style = FrontendStyle::kProtobuf, + bool generate_ros_metadata = false); void GenerateHeaders(std::ostream& os, std::string* error); void GenerateSources(std::ostream& os); @@ -60,6 +62,7 @@ class Generator { const std::string& package_name_; const std::string& target_name_; bool generate_active_message_; + bool generate_ros_metadata_; FrontendStyle frontend_style_; }; diff --git a/phaser/compiler/message_gen.cc b/phaser/compiler/message_gen.cc index af8791c..5d92df2 100644 --- a/phaser/compiler/message_gen.cc +++ b/phaser/compiler/message_gen.cc @@ -123,8 +123,7 @@ static bool IsCppReservedWord(const std::string& s) { return reserved_words.contains(s); } -static bool IsFixedWireType( - const google::protobuf::FieldDescriptor* field) { +static bool IsFixedWireType(const google::protobuf::FieldDescriptor* field) { using Field = google::protobuf::FieldDescriptor; switch (field->type()) { case Field::TYPE_FIXED32: @@ -156,14 +155,32 @@ static bool IsRosBuiltinType(const std::string& type) { return builtin_types.contains(RosBaseType(type)); } +static std::string RosDataType(const google::protobuf::Descriptor* message) { + if (message->options().HasExtension(phaser::ros_message)) { + const auto& metadata = message->options().GetExtension(phaser::ros_message); + if (!metadata.data_type().empty()) { + return metadata.data_type(); + } + } + std::string package(message->file()->package()); + constexpr std::string_view suffix = ".proto"; + if (package.size() <= suffix.size() || + package.compare(package.size() - suffix.size(), suffix.size(), suffix) != + 0) { + return ""; + } + package.resize(package.size() - suffix.size()); + return package + "/" + std::string(message->name()); +} + static std::string RosArraySuffix( const google::protobuf::FieldDescriptor* field) { if (!field->is_repeated()) { return ""; } if (field->options().HasExtension(phaser::array_size)) { - return absl::StrFormat( - "[%u]", field->options().GetExtension(phaser::array_size)); + return absl::StrFormat("[%u]", + field->options().GetExtension(phaser::array_size)); } return "[]"; } @@ -176,6 +193,9 @@ static std::string InferredRosFieldType( case Field::TYPE_BOOL: type = "bool"; break; + case Field::TYPE_INT32: + type = "int32"; + break; case Field::TYPE_INT64: type = "int64"; break; @@ -197,17 +217,13 @@ static std::string InferredRosFieldType( } else if (field->message_type()->full_name() == "google.protobuf.Duration") { type = "duration"; - } else if (field->message_type()->options().HasExtension( - phaser::ros_message)) { - type = field->message_type() - ->options() - .GetExtension(phaser::ros_message) - .data_type(); + } else { + type = RosDataType(field->message_type()); } break; default: - // int32, uint32, bytes, and enums each have multiple possible ROS - // source types and therefore require an explicit override. + // uint32, bytes, and enums each have multiple possible ROS source types. + // Noncanonical signed encodings also require an explicit override. break; } if (type.empty()) { @@ -219,9 +235,12 @@ static std::string InferredRosFieldType( static std::string RosFieldType( const google::protobuf::FieldDescriptor* field) { if (field->options().HasExtension(phaser::ros_field)) { - const auto& metadata = - field->options().GetExtension(phaser::ros_field); + const auto& metadata = field->options().GetExtension(phaser::ros_field); if (!metadata.type().empty()) { + if (field->is_repeated() && + metadata.type().find('[') == std::string::npos) { + return metadata.type() + RosArraySuffix(field); + } return metadata.type(); } } @@ -231,8 +250,7 @@ static std::string RosFieldType( static std::string RosFieldName( const google::protobuf::FieldDescriptor* field) { if (field->options().HasExtension(phaser::ros_field)) { - const auto& metadata = - field->options().GetExtension(phaser::ros_field); + const auto& metadata = field->options().GetExtension(phaser::ros_field); if (!metadata.name().empty()) { return metadata.name(); } @@ -240,6 +258,55 @@ static std::string RosFieldName( return std::string(field->name()); } +static std::vector RosConstantDeclarations( + const google::protobuf::Descriptor* message) { + const auto& message_metadata = + message->options().GetExtension(phaser::ros_message); + if (!message_metadata.constants().empty()) { + return {message_metadata.constants().begin(), + message_metadata.constants().end()}; + } + + std::vector constants; + for (int i = 0; i < message->enum_type_count(); ++i) { + const auto* enum_type = message->enum_type(i); + if (!enum_type->options().HasExtension(phaser::ros_enum)) { + continue; + } + const auto& enum_metadata = + enum_type->options().GetExtension(phaser::ros_enum); + for (int j = 0; j < enum_type->value_count(); ++j) { + const auto* value = enum_type->value(j); + const auto& value_metadata = + value->options().GetExtension(phaser::ros_enum_value); + if (value_metadata.ignore()) { + continue; + } + const std::string name = value_metadata.name().empty() + ? std::string(value->name()) + : value_metadata.name(); + const std::string text_value = value_metadata.value().empty() + ? std::to_string(value->number()) + : value_metadata.value(); + constants.push_back(enum_metadata.type() + " " + name + "=" + text_value); + } + } + return constants; +} + +static std::string RosSourceDefinition( + const google::protobuf::Descriptor* message) { + std::string definition; + for (const auto& constant : RosConstantDeclarations(message)) { + definition += constant + "\n"; + } + for (int i = 0; i < message->field_count(); ++i) { + const auto* field = message->field(i); + definition += RosFieldType(field) + " " + RosFieldName(field) + "\n"; + } + return definition; +} + static void AppendRosDependencies(const google::protobuf::Descriptor* message, absl::flat_hash_set* seen, std::string* definition) { @@ -256,16 +323,12 @@ static void AppendRosDependencies(const google::protobuf::Descriptor* message, const google::protobuf::Descriptor* dependency = nullptr; if (!field_metadata.nested_data_type().empty()) { data_type = field_metadata.nested_data_type(); - source_definition = field_metadata.nested_definition(); + source_definition = field_metadata.nested_md5_text() + "\n"; } else if (field->type() == - google::protobuf::FieldDescriptor::TYPE_MESSAGE && - field->message_type()->options().HasExtension( - phaser::ros_message)) { + google::protobuf::FieldDescriptor::TYPE_MESSAGE) { dependency = field->message_type(); - const auto& dependency_metadata = - dependency->options().GetExtension(phaser::ros_message); - data_type = dependency_metadata.data_type(); - source_definition = dependency_metadata.definition(); + data_type = RosDataType(dependency); + source_definition = RosSourceDefinition(dependency); } else { continue; } @@ -275,7 +338,7 @@ static void AppendRosDependencies(const google::protobuf::Descriptor* message, } *definition += std::string(80, '=') + "\n"; *definition += "MSG: " + data_type + "\n"; - *definition += source_definition + "\n"; + *definition += source_definition; if (dependency != nullptr) { AppendRosDependencies(dependency, seen, definition); } @@ -284,11 +347,9 @@ static void AppendRosDependencies(const google::protobuf::Descriptor* message, static std::string RosFullDefinition( const google::protobuf::Descriptor* message) { - const auto& metadata = message->options().GetExtension(phaser::ros_message); - std::string definition = metadata.definition() + "\n"; + std::string definition = RosSourceDefinition(message); absl::flat_hash_set seen; AppendRosDependencies(message, &seen, &definition); - definition.pop_back(); return definition; } @@ -404,13 +465,35 @@ absl::Status MessageGenerator::ValidateFieldOptions() const { } absl::Status MessageGenerator::ValidateRosMetadataOptions() const { - if (!message_->options().HasExtension(phaser::ros_message)) { + if (!generate_ros_metadata_ && + !message_->options().HasExtension(phaser::ros_message)) { return absl::OkStatus(); } - const auto& metadata = message_->options().GetExtension(phaser::ros_message); - if (metadata.data_type().empty()) { + if (RosDataType(message_).empty()) { + return absl::InvalidArgumentError( + absl::StrFormat("cannot infer ROS datatype for message %s; set " + "phaser.ros_message.data_type", + message_->full_name())); + } + bool has_ros_constant_enums = false; + for (int i = 0; i < message_->enum_type_count(); ++i) { + const auto* enum_type = message_->enum_type(i); + if (!enum_type->options().HasExtension(phaser::ros_enum)) { + continue; + } + has_ros_constant_enums = true; + if (enum_type->options().GetExtension(phaser::ros_enum).type().empty()) { + return absl::InvalidArgumentError( + absl::StrFormat("phaser.ros_enum.type must not be empty on enum %s", + enum_type->full_name())); + } + } + const auto& message_metadata = + message_->options().GetExtension(phaser::ros_message); + if (has_ros_constant_enums && !message_metadata.constants().empty()) { return absl::InvalidArgumentError(absl::StrFormat( - "phaser.ros_message.data_type must not be empty on message %s", + "message %s cannot combine phaser.ros_message.constants with " + "phaser.ros_enum constant groups", message_->full_name())); } for (int i = 0; i < message_->field_count(); ++i) { @@ -432,20 +515,17 @@ absl::Status MessageGenerator::ValidateRosMetadataOptions() const { message_->full_name(), field->name())); } if (!field_metadata.nested_md5_text().empty() && - (field_metadata.nested_data_type().empty() || - field_metadata.nested_definition().empty())) { + field_metadata.nested_data_type().empty()) { return absl::InvalidArgumentError(absl::StrFormat( - "non-protobuf ROS field %s.%s must provide nested_data_type and " - "nested_definition", + "non-protobuf ROS field %s.%s must provide nested_data_type", message_->full_name(), field->name())); } if (!IsRosBuiltinType(field_type) && field->type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE && - !field->message_type()->options().HasExtension(phaser::ros_message) && + RosDataType(field->message_type()).empty() && field_metadata.nested_md5_text().empty()) { return absl::InvalidArgumentError(absl::StrFormat( - "non-builtin ROS field %s.%s references %s, which must declare " - "phaser.ros_message", + "cannot infer ROS datatype for nested field %s.%s referencing %s", message_->full_name(), field->name(), field->message_type()->full_name())); } @@ -1148,12 +1228,12 @@ absl::Status MessageGenerator::GenerateHeader(std::ostream& os) { } void MessageGenerator::GenerateRosMetadata(std::ostream& os) { - if (!message_->options().HasExtension(phaser::ros_message)) { + if (!generate_ros_metadata_ && + !message_->options().HasExtension(phaser::ros_message)) { return; } - const auto& metadata = message_->options().GetExtension(phaser::ros_message); os << " static constexpr std::string_view RosDataType() { return \"" - << absl::CEscape(metadata.data_type()) << "\"; }\n"; + << absl::CEscape(RosDataType(message_)) << "\"; }\n"; os << " static constexpr std::string_view RosDefinition() { return \"" << absl::CEscape(RosFullDefinition(message_)) << "\"; }\n"; os << " static std::string RosMd5() {\n"; @@ -1171,7 +1251,7 @@ void MessageGenerator::GenerateRosMetadata(std::ostream& os) { generate_separator(); os << " text += \"" << absl::CEscape(line) << "\";\n"; }; - for (const auto& constant : metadata.constants()) { + for (const auto& constant : RosConstantDeclarations(message_)) { generate_append_literal(constant); } for (int i = 0; i < message_->field_count(); ++i) { @@ -1182,8 +1262,8 @@ void MessageGenerator::GenerateRosMetadata(std::ostream& os) { const std::string field_name = RosFieldName(field); generate_separator(); if (IsRosBuiltinType(field_type)) { - os << " text += \"" - << absl::CEscape(field_type + " " + field_name) << "\";\n"; + os << " text += \"" << absl::CEscape(field_type + " " + field_name) + << "\";\n"; } else if (!field_metadata.nested_md5_text().empty()) { os << " text += ::phaser::Md5(\"" << absl::CEscape(field_metadata.nested_md5_text()) << "\");\n"; @@ -2188,7 +2268,7 @@ void MessageGenerator::GenerateROSFieldSize( case google::protobuf::FieldDescriptor::TYPE_FIXED32: case google::protobuf::FieldDescriptor::TYPE_FLOAT: case google::protobuf::FieldDescriptor::TYPE_ENUM: - os << indent << "size += 4;\n"; + os << indent << "_phaser_serialized_size += 4;\n"; return; case google::protobuf::FieldDescriptor::TYPE_INT64: case google::protobuf::FieldDescriptor::TYPE_SINT64: @@ -2196,29 +2276,30 @@ void MessageGenerator::GenerateROSFieldSize( case google::protobuf::FieldDescriptor::TYPE_UINT64: case google::protobuf::FieldDescriptor::TYPE_FIXED64: case google::protobuf::FieldDescriptor::TYPE_DOUBLE: - os << indent << "size += 8;\n"; + os << indent << "_phaser_serialized_size += 8;\n"; return; case google::protobuf::FieldDescriptor::TYPE_BOOL: - os << indent << "size += 1;\n"; + os << indent << "_phaser_serialized_size += 1;\n"; return; case google::protobuf::FieldDescriptor::TYPE_STRING: case google::protobuf::FieldDescriptor::TYPE_BYTES: - os << indent << "size += 4 + (" << value_expression << ").size();\n"; + os << indent << "_phaser_serialized_size += 4 + (" << value_expression + << ").size();\n"; return; case google::protobuf::FieldDescriptor::TYPE_MESSAGE: if (IsAny(field)) { // Any has no static ROS1 type. An absent Any is represented by its two // empty declared fields; a populated Any is rejected by the writer. - os << indent << "size += 8;\n"; + os << indent << "_phaser_serialized_size += 8;\n"; } else if (IsRosFrontend() && IsRosTime(field->message_type())) { - os << indent << "size += 8;\n"; + os << indent << "_phaser_serialized_size += 8;\n"; } else if (IsRosFrontend() && IsRosDuration(field->message_type())) { - os << indent << "size += 8;\n"; + os << indent << "_phaser_serialized_size += 8;\n"; } else if (IsRosFrontend() && IsRosHeader(field->message_type())) { - os << indent << "size += 16 + (" << value_expression + os << indent << "_phaser_serialized_size += 16 + (" << value_expression << ").frame_id.size();\n"; } else { - os << indent << "size += (" << value_expression + os << indent << "_phaser_serialized_size += (" << value_expression << ").ROSSerializedSize();\n"; } return; @@ -2232,8 +2313,8 @@ void MessageGenerator::GenerateROSFieldWrite( std::ostream& os, const google::protobuf::FieldDescriptor* field, const std::string& value_expression, const std::string& indent) { auto write = [&](const std::string& expression) { - os << indent << "if (absl::Status status = buffer.Write(" << expression - << "); !status.ok()) return status;\n"; + os << indent << "if (absl::Status _phaser_status = _phaser_buffer.Write(" + << expression << "); !_phaser_status.ok()) return _phaser_status;\n"; }; switch (field->type()) { case google::protobuf::FieldDescriptor::TYPE_INT32: @@ -2268,8 +2349,10 @@ void MessageGenerator::GenerateROSFieldWrite( return; case google::protobuf::FieldDescriptor::TYPE_STRING: case google::protobuf::FieldDescriptor::TYPE_BYTES: - os << indent << "if (absl::Status status = buffer.WriteString(" - << value_expression << "); !status.ok()) return status;\n"; + os << indent + << "if (absl::Status _phaser_status = _phaser_buffer.WriteString(" + << value_expression + << "); !_phaser_status.ok()) return _phaser_status;\n"; return; case google::protobuf::FieldDescriptor::TYPE_MESSAGE: if (IsAny(field)) { @@ -2280,11 +2363,13 @@ void MessageGenerator::GenerateROSFieldWrite( "populated google.protobuf.Any is unsupported\");\n"; os << indent << "}\n"; os << indent - << "if (absl::Status status = buffer.WriteString({}); " - "!status.ok()) return status;\n"; + << "if (absl::Status _phaser_status = " + "_phaser_buffer.WriteString({}); " + "!_phaser_status.ok()) return _phaser_status;\n"; os << indent - << "if (absl::Status status = buffer.WriteString({}); " - "!status.ok()) return status;\n"; + << "if (absl::Status _phaser_status = " + "_phaser_buffer.WriteString({}); " + "!_phaser_status.ok()) return _phaser_status;\n"; } else if (IsRosFrontend() && IsRosTime(field->message_type())) { write("static_cast((" + value_expression + ").sec)"); write("static_cast((" + value_expression + ").nsec)"); @@ -2295,11 +2380,16 @@ void MessageGenerator::GenerateROSFieldWrite( write("static_cast((" + value_expression + ").seq)"); write("static_cast((" + value_expression + ").stamp.sec)"); write("static_cast((" + value_expression + ").stamp.nsec)"); - os << indent << "if (absl::Status status = buffer.WriteString((" - << value_expression << ").frame_id); !status.ok()) return status;\n"; + os << indent + << "if (absl::Status _phaser_status = " + "_phaser_buffer.WriteString((" + << value_expression + << ").frame_id); !_phaser_status.ok()) return _phaser_status;\n"; } else { - os << indent << "if (absl::Status status = (" << value_expression - << ").SerializeToROS(buffer); !status.ok()) return status;\n"; + os << indent << "if (absl::Status _phaser_status = (" + << value_expression + << ").SerializeToROS(_phaser_buffer); !_phaser_status.ok()) return " + "_phaser_status;\n"; } return; case google::protobuf::FieldDescriptor::TYPE_GROUP: @@ -2342,11 +2432,12 @@ void MessageGenerator::GenerateROSFieldRead( auto read_value = [&](const std::string& type, const std::string& conversion = "") { os << indent << "{\n"; - os << indent << " absl::StatusOr<" << type << "> ros_value = buffer.Read<" - << type << ">();\n"; - os << indent << " if (!ros_value.ok()) return ros_value.status();\n"; + os << indent << " absl::StatusOr<" << type + << "> _phaser_value = _phaser_buffer.Read<" << type << ">();\n"; + os << indent + << " if (!_phaser_value.ok()) return _phaser_value.status();\n"; const std::string value = - conversion.empty() ? "*ros_value" : conversion + "(*ros_value)"; + conversion.empty() ? "*_phaser_value" : conversion + "(*_phaser_value)"; set_value(value); os << indent << "}\n"; }; @@ -2387,27 +2478,31 @@ void MessageGenerator::GenerateROSFieldRead( case google::protobuf::FieldDescriptor::TYPE_BYTES: os << indent << "{\n"; os << indent - << " absl::StatusOr ros_value = " - "buffer.ReadString();\n"; - os << indent << " if (!ros_value.ok()) return ros_value.status();\n"; - set_value("*ros_value"); + << " absl::StatusOr _phaser_value = " + "_phaser_buffer.ReadString();\n"; + os << indent + << " if (!_phaser_value.ok()) return _phaser_value.status();\n"; + set_value("*_phaser_value"); os << indent << "}\n"; return; case google::protobuf::FieldDescriptor::TYPE_MESSAGE: if (IsAny(field)) { os << indent << "{\n"; os << indent - << " absl::StatusOr ros_type_url = " - "buffer.ReadString();\n"; + << " absl::StatusOr _phaser_type_url = " + "_phaser_buffer.ReadString();\n"; os << indent - << " if (!ros_type_url.ok()) return ros_type_url.status();\n"; + << " if (!_phaser_type_url.ok()) return " + "_phaser_type_url.status();\n"; os << indent - << " absl::StatusOr ros_any_value = " - "buffer.ReadString();\n"; + << " absl::StatusOr _phaser_any_value = " + "_phaser_buffer.ReadString();\n"; os << indent - << " if (!ros_any_value.ok()) return ros_any_value.status();\n"; + << " if (!_phaser_any_value.ok()) return " + "_phaser_any_value.status();\n"; os << indent - << " if (!ros_type_url->empty() || !ros_any_value->empty()) {\n"; + << " if (!_phaser_type_url->empty() || " + "!_phaser_any_value->empty()) {\n"; os << indent << " return absl::UnimplementedError(\"ROS1 deserialization of " "a populated google.protobuf.Any is unsupported\");\n"; @@ -2417,75 +2512,86 @@ void MessageGenerator::GenerateROSFieldRead( } else if (IsRosFrontend() && IsRosTime(field->message_type())) { os << indent << "{\n"; os << indent - << " absl::StatusOr ros_sec = " - "buffer.Read();\n"; - os << indent << " if (!ros_sec.ok()) return ros_sec.status();\n"; + << " absl::StatusOr _phaser_sec = " + "_phaser_buffer.Read();\n"; + os << indent + << " if (!_phaser_sec.ok()) return _phaser_sec.status();\n"; os << indent - << " absl::StatusOr ros_nsec = " - "buffer.Read();\n"; - os << indent << " if (!ros_nsec.ok()) return ros_nsec.status();\n"; - os << indent << " ::ros::Time ros_value;\n"; - os << indent << " ros_value.sec = *ros_sec;\n"; - os << indent << " ros_value.nsec = *ros_nsec;\n"; - set_value("ros_value"); + << " absl::StatusOr _phaser_nsec = " + "_phaser_buffer.Read();\n"; + os << indent + << " if (!_phaser_nsec.ok()) return _phaser_nsec.status();\n"; + os << indent << " ::ros::Time _phaser_value;\n"; + os << indent << " _phaser_value.sec = *_phaser_sec;\n"; + os << indent << " _phaser_value.nsec = *_phaser_nsec;\n"; + set_value("_phaser_value"); os << indent << "}\n"; } else if (IsRosFrontend() && IsRosDuration(field->message_type())) { os << indent << "{\n"; os << indent - << " absl::StatusOr ros_sec = " - "buffer.Read();\n"; - os << indent << " if (!ros_sec.ok()) return ros_sec.status();\n"; + << " absl::StatusOr _phaser_sec = " + "_phaser_buffer.Read();\n"; + os << indent + << " if (!_phaser_sec.ok()) return _phaser_sec.status();\n"; os << indent - << " absl::StatusOr ros_nsec = " - "buffer.Read();\n"; - os << indent << " if (!ros_nsec.ok()) return ros_nsec.status();\n"; - os << indent << " ::ros::Duration ros_value;\n"; - os << indent << " ros_value.sec = *ros_sec;\n"; - os << indent << " ros_value.nsec = *ros_nsec;\n"; - set_value("ros_value"); + << " absl::StatusOr _phaser_nsec = " + "_phaser_buffer.Read();\n"; + os << indent + << " if (!_phaser_nsec.ok()) return _phaser_nsec.status();\n"; + os << indent << " ::ros::Duration _phaser_value;\n"; + os << indent << " _phaser_value.sec = *_phaser_sec;\n"; + os << indent << " _phaser_value.nsec = *_phaser_nsec;\n"; + set_value("_phaser_value"); os << indent << "}\n"; } else if (IsRosFrontend() && IsRosHeader(field->message_type())) { os << indent << "{\n"; os << indent - << " absl::StatusOr ros_seq = " - "buffer.Read();\n"; - os << indent << " if (!ros_seq.ok()) return ros_seq.status();\n"; + << " absl::StatusOr _phaser_seq = " + "_phaser_buffer.Read();\n"; + os << indent + << " if (!_phaser_seq.ok()) return _phaser_seq.status();\n"; + os << indent + << " absl::StatusOr _phaser_sec = " + "_phaser_buffer.Read();\n"; os << indent - << " absl::StatusOr ros_sec = " - "buffer.Read();\n"; - os << indent << " if (!ros_sec.ok()) return ros_sec.status();\n"; + << " if (!_phaser_sec.ok()) return _phaser_sec.status();\n"; os << indent - << " absl::StatusOr ros_nsec = " - "buffer.Read();\n"; - os << indent << " if (!ros_nsec.ok()) return ros_nsec.status();\n"; + << " absl::StatusOr _phaser_nsec = " + "_phaser_buffer.Read();\n"; os << indent - << " absl::StatusOr ros_frame_id = " - "buffer.ReadString();\n"; + << " if (!_phaser_nsec.ok()) return _phaser_nsec.status();\n"; os << indent - << " if (!ros_frame_id.ok()) return ros_frame_id.status();\n"; + << " absl::StatusOr _phaser_frame_id = " + "_phaser_buffer.ReadString();\n"; + os << indent + << " if (!_phaser_frame_id.ok()) return " + "_phaser_frame_id.status();\n"; if (union_index < 0 && !append && index_expression.empty()) { - os << indent << " auto ros_value = " << target_expression + os << indent << " auto _phaser_value = " << target_expression << ".Mutable();\n"; - os << indent << " ros_value.seq = *ros_seq;\n"; - os << indent << " ros_value.stamp.sec = *ros_sec;\n"; - os << indent << " ros_value.stamp.nsec = *ros_nsec;\n"; - os << indent << " ros_value.frame_id = *ros_frame_id;\n"; + os << indent << " _phaser_value.seq = *_phaser_seq;\n"; + os << indent << " _phaser_value.stamp.sec = *_phaser_sec;\n"; + os << indent << " _phaser_value.stamp.nsec = *_phaser_nsec;\n"; + os << indent << " _phaser_value.frame_id = *_phaser_frame_id;\n"; } else { - os << indent << " auto ros_value = " << mutable_message() << ";\n"; - os << indent << " ros_value->seq = *ros_seq;\n"; - os << indent << " ros_value->stamp = " - << "::ros::Time(*ros_sec, *ros_nsec);\n"; - os << indent << " ros_value->frame_id = *ros_frame_id;\n"; - os << indent << " ros_value->SyncToPayload();\n"; + os << indent << " auto _phaser_value = " << mutable_message() + << ";\n"; + os << indent << " _phaser_value->seq = *_phaser_seq;\n"; + os << indent << " _phaser_value->stamp = " + << "::ros::Time(*_phaser_sec, *_phaser_nsec);\n"; + os << indent << " _phaser_value->frame_id = *_phaser_frame_id;\n"; + os << indent << " _phaser_value->SyncToPayload();\n"; } os << indent << "}\n"; } else { os << indent << "{\n"; - os << indent << " auto ros_message = " << mutable_message() << ";\n"; + os << indent << " auto _phaser_message = " << mutable_message() + << ";\n"; os << indent - << " if (absl::Status status = " - "ros_message->DeserializeFromROS(buffer); !status.ok()) " - "return status;\n"; + << " if (absl::Status _phaser_status = " + "_phaser_message->DeserializeFromROS(_phaser_buffer); " + "!_phaser_status.ok()) " + "return _phaser_status;\n"; os << indent << "}\n"; } return; @@ -2799,7 +2905,8 @@ void MessageGenerator::GenerateDirectProtobufField( "return status;\n"; os << indent << " ros_emitted += ros_packed_count;\n"; } else { - os << indent << " ::phaser::ProtoBuffer ros_values(*ros_packed);\n"; + os << indent + << " ::phaser::ProtoBuffer ros_values(*ros_packed);\n"; os << indent << " while (!ros_values.Eof()) {\n"; emit_value("ros_values", indent + " "); os << indent << " }\n"; @@ -3064,8 +3171,7 @@ void MessageGenerator::GenerateDirectROSFieldToProtobuf( if (field->is_packable() && field->is_packed() && fixed_wire_type) { os << indent << " const uint64_t ros_packed_byte_size = " << "static_cast(" << count << ") * sizeof(" << type << ");\n"; - os << indent - << " if (ros_packed_byte_size > ros.Remaining()) {\n"; + os << indent << " if (ros_packed_byte_size > ros.Remaining()) {\n"; os << indent << " return absl::InvalidArgumentError(" "\"truncated ROS packed fixed-width field\");\n"; @@ -3171,9 +3277,10 @@ void MessageGenerator::GenerateROSSerialization(std::ostream& os, bool decl) { const std::string name = MessageName(message_); if (decl) { os << " size_t ROSSerializedSize() const;\n"; - os << " absl::Status SerializeToROS(::phaser::ROSBuffer& buffer) const;\n"; + os << " absl::Status SerializeToROS(" + "::phaser::ROSBuffer& _phaser_buffer) const;\n"; os << " absl::Status DeserializeFromROS(" - "::phaser::ROSReader& buffer);\n"; + "::phaser::ROSReader& _phaser_buffer);\n"; os << " absl::Status ParseFromROS(absl::Span input);\n"; os << R"XXX( absl::Status SerializeToROSArray(void* data, size_t size) const { ::phaser::ROSBuffer buffer(data, size); @@ -3218,11 +3325,11 @@ void MessageGenerator::GenerateROSSerialization(std::ostream& os, bool decl) { if (IsRosTime(message_) || IsRosDuration(message_)) { os << " return 8;\n"; } else { - os << " size_t size = 0;\n"; + os << " size_t _phaser_serialized_size = 0;\n"; for (const auto& item : fields_in_order_) { if (item->IsUnion()) { auto union_info = std::static_pointer_cast(item); - os << " size += 4;\n"; + os << " _phaser_serialized_size += 4;\n"; os << " switch (" << union_info->member_name << ".Discriminator()) {\n"; for (size_t i = 0; i < union_info->members.size(); ++i) { @@ -3249,7 +3356,7 @@ void MessageGenerator::GenerateROSSerialization(std::ostream& os, bool decl) { const int fixed_extent = GetArraySize(descriptor); if (fixed_extent <= 0) { - os << " size += 4;\n"; + os << " _phaser_serialized_size += 4;\n"; } const std::string count = fixed_extent > 0 ? std::to_string(fixed_extent) @@ -3259,39 +3366,46 @@ void MessageGenerator::GenerateROSSerialization(std::ostream& os, bool decl) { bulk_type = EnumName(descriptor->enum_type()); } if (!bulk_type.empty()) { - os << " size += " << count << " * sizeof(" << bulk_type << ");\n"; + os << " _phaser_serialized_size += " << count << " * sizeof(" + << bulk_type << ");\n"; } else { - os << " for (size_t ros_index = 0; ros_index < " << count - << "; ++ros_index) {\n"; + os << " for (size_t _phaser_index = 0; _phaser_index < " << count + << "; ++_phaser_index) {\n"; GenerateROSFieldSize(os, descriptor, - item->member_name + ".Get(ros_index)", " "); + item->member_name + ".Get(_phaser_index)", " "); os << " }\n"; } } - os << " return size;\n"; + os << " return _phaser_serialized_size;\n"; } os << "}\n\n"; os << "absl::Status " << name - << "::SerializeToROS(::phaser::ROSBuffer& buffer) const {\n"; + << "::SerializeToROS(::phaser::ROSBuffer& _phaser_buffer) const {\n"; os << " SyncToPayload();\n"; if (IsRosTime(message_)) { - os << " if (absl::Status status = buffer.Write(" - "static_cast(seconds())); !status.ok()) return status;\n"; - os << " if (absl::Status status = buffer.Write(" - "static_cast(nanos())); !status.ok()) return status;\n"; + os << " if (absl::Status _phaser_status = _phaser_buffer.Write(" + "static_cast(seconds())); !_phaser_status.ok()) " + "return _phaser_status;\n"; + os << " if (absl::Status _phaser_status = _phaser_buffer.Write(" + "static_cast(nanos())); !_phaser_status.ok()) " + "return _phaser_status;\n"; } else if (IsRosDuration(message_)) { - os << " if (absl::Status status = buffer.Write(" - "static_cast(seconds())); !status.ok()) return status;\n"; - os << " if (absl::Status status = buffer.Write(" - "static_cast(nanos())); !status.ok()) return status;\n"; + os << " if (absl::Status _phaser_status = _phaser_buffer.Write(" + "static_cast(seconds())); !_phaser_status.ok()) " + "return _phaser_status;\n"; + os << " if (absl::Status _phaser_status = _phaser_buffer.Write(" + "static_cast(nanos())); !_phaser_status.ok()) " + "return _phaser_status;\n"; } else { for (const auto& item : fields_in_order_) { if (item->IsUnion()) { auto union_info = std::static_pointer_cast(item); - os << " if (absl::Status status = buffer.Write(static_cast(" + os << " if (absl::Status _phaser_status = " + "_phaser_buffer.Write(static_cast(" << union_info->member_name - << ".Discriminator())); !status.ok()) return status;\n"; + << ".Discriminator())); !_phaser_status.ok()) " + "return _phaser_status;\n"; os << " switch (" << union_info->member_name << ".Discriminator()) {\n"; for (size_t i = 0; i < union_info->members.size(); ++i) { @@ -3318,8 +3432,10 @@ void MessageGenerator::GenerateROSSerialization(std::ostream& os, bool decl) { const int fixed_extent = GetArraySize(descriptor); if (fixed_extent <= 0) { - os << " if (absl::Status status = buffer.WriteSequenceLength(" - << item->member_name << ".size()); !status.ok()) return status;\n"; + os << " if (absl::Status _phaser_status = " + "_phaser_buffer.WriteSequenceLength(" + << item->member_name + << ".size()); !_phaser_status.ok()) return _phaser_status;\n"; } const std::string count = fixed_extent > 0 ? std::to_string(fixed_extent) @@ -3331,24 +3447,29 @@ void MessageGenerator::GenerateROSSerialization(std::ostream& os, bool decl) { if (!bulk_type.empty()) { if (fixed_extent > 0) { os << " if (" << item->member_name << ".data() == nullptr) {\n"; - os << " if (absl::Status status = buffer.WriteZeros(" << count - << " * sizeof(" << bulk_type - << ")); !status.ok()) return status;\n"; + os << " if (absl::Status _phaser_status = " + "_phaser_buffer.WriteZeros(" + << count << " * sizeof(" << bulk_type + << ")); !_phaser_status.ok()) return _phaser_status;\n"; os << " } else {\n"; - os << " if (absl::Status status = buffer.WriteArray<" << bulk_type - << ">(absl::Span(" << item->member_name - << ".data(), " << count << ")); !status.ok()) return status;\n"; + os << " if (absl::Status _phaser_status = " + "_phaser_buffer.WriteArray<" + << bulk_type << ">(absl::Span(" + << item->member_name << ".data(), " << count + << ")); !_phaser_status.ok()) return _phaser_status;\n"; os << " }\n"; } else { - os << " if (absl::Status status = buffer.WriteArray<" << bulk_type - << ">(absl::Span(" << item->member_name - << ".data(), " << count << ")); !status.ok()) return status;\n"; + os << " if (absl::Status _phaser_status = " + "_phaser_buffer.WriteArray<" + << bulk_type << ">(absl::Span(" + << item->member_name << ".data(), " << count + << ")); !_phaser_status.ok()) return _phaser_status;\n"; } } else { - os << " for (size_t ros_index = 0; ros_index < " << count - << "; ++ros_index) {\n"; - GenerateROSFieldWrite(os, descriptor, - item->member_name + ".Get(ros_index)", " "); + os << " for (size_t _phaser_index = 0; _phaser_index < " << count + << "; ++_phaser_index) {\n"; + GenerateROSFieldWrite( + os, descriptor, item->member_name + ".Get(_phaser_index)", " "); os << " }\n"; } } @@ -3357,32 +3478,36 @@ void MessageGenerator::GenerateROSSerialization(std::ostream& os, bool decl) { os << "}\n\n"; os << "absl::Status " << name - << "::DeserializeFromROS(::phaser::ROSReader& buffer) {\n"; + << "::DeserializeFromROS(::phaser::ROSReader& _phaser_buffer) {\n"; os << " Clear();\n"; if (IsRosTime(message_)) { - os << " absl::StatusOr ros_sec = buffer.Read();\n"; - os << " if (!ros_sec.ok()) return ros_sec.status();\n"; - os << " absl::StatusOr ros_nsec = buffer.Read();\n"; - os << " if (!ros_nsec.ok()) return ros_nsec.status();\n"; - os << " set_seconds(static_cast(*ros_sec));\n"; - os << " set_nanos(static_cast(*ros_nsec));\n"; + os << " absl::StatusOr _phaser_sec = " + "_phaser_buffer.Read();\n"; + os << " if (!_phaser_sec.ok()) return _phaser_sec.status();\n"; + os << " absl::StatusOr _phaser_nsec = " + "_phaser_buffer.Read();\n"; + os << " if (!_phaser_nsec.ok()) return _phaser_nsec.status();\n"; + os << " set_seconds(static_cast(*_phaser_sec));\n"; + os << " set_nanos(static_cast(*_phaser_nsec));\n"; } else if (IsRosDuration(message_)) { - os << " absl::StatusOr ros_sec = buffer.Read();\n"; - os << " if (!ros_sec.ok()) return ros_sec.status();\n"; - os << " absl::StatusOr ros_nsec = buffer.Read();\n"; - os << " if (!ros_nsec.ok()) return ros_nsec.status();\n"; - os << " set_seconds(static_cast(*ros_sec));\n"; - os << " set_nanos(*ros_nsec);\n"; + os << " absl::StatusOr _phaser_sec = " + "_phaser_buffer.Read();\n"; + os << " if (!_phaser_sec.ok()) return _phaser_sec.status();\n"; + os << " absl::StatusOr _phaser_nsec = " + "_phaser_buffer.Read();\n"; + os << " if (!_phaser_nsec.ok()) return _phaser_nsec.status();\n"; + os << " set_seconds(static_cast(*_phaser_sec));\n"; + os << " set_nanos(*_phaser_nsec);\n"; } else { for (const auto& item : fields_in_order_) { if (item->IsUnion()) { auto union_info = std::static_pointer_cast(item); os << " {\n"; - os << " absl::StatusOr ros_discriminator = " - "buffer.Read();\n"; - os << " if (!ros_discriminator.ok()) return " - "ros_discriminator.status();\n"; - os << " switch (*ros_discriminator) {\n"; + os << " absl::StatusOr _phaser_discriminator = " + "_phaser_buffer.Read();\n"; + os << " if (!_phaser_discriminator.ok()) return " + "_phaser_discriminator.status();\n"; + os << " switch (*_phaser_discriminator) {\n"; os << " case 0:\n"; os << " " << union_info->member_name << ".reset();\n"; os << " break;\n"; @@ -3414,38 +3539,41 @@ void MessageGenerator::GenerateROSSerialization(std::ostream& os, bool decl) { } if (fixed_extent <= 0) { os << " {\n"; - os << " absl::StatusOr ros_count = " - "buffer.ReadSequenceLength();\n"; - os << " if (!ros_count.ok()) return ros_count.status();\n"; + os << " absl::StatusOr _phaser_count = " + "_phaser_buffer.ReadSequenceLength();\n"; + os << " if (!_phaser_count.ok()) return _phaser_count.status();\n"; if (!bulk_type.empty()) { - os << " if (*ros_count > buffer.Remaining() / sizeof(" << bulk_type - << ")) {\n"; + os << " if (*_phaser_count > _phaser_buffer.Remaining() / sizeof(" + << bulk_type << ")) {\n"; os << " return absl::InvalidArgumentError(" "\"ROS sequence length exceeds remaining input\");\n"; os << " }\n"; - os << " " << item->member_name << ".resize(*ros_count);\n"; - os << " if (absl::Status status = buffer.ReadArray<" << bulk_type - << ">(absl::Span<" << bulk_type << ">(" << item->member_name - << ".data(), static_cast(*ros_count))); " - "!status.ok()) return status;\n"; + os << " " << item->member_name << ".resize(*_phaser_count);\n"; + os << " if (absl::Status _phaser_status = " + "_phaser_buffer.ReadArray<" + << bulk_type << ">(absl::Span<" << bulk_type << ">(" + << item->member_name + << ".data(), static_cast(*_phaser_count))); " + "!_phaser_status.ok()) return _phaser_status;\n"; } else { - os << " for (uint32_t ros_index = 0; ros_index < *ros_count; " - "++ros_index) {\n"; + os << " for (uint32_t _phaser_index = 0; " + "_phaser_index < *_phaser_count; ++_phaser_index) {\n"; GenerateROSFieldRead(os, descriptor, item->member_name, " ", true); os << " }\n"; } os << " }\n"; } else if (!bulk_type.empty()) { - os << " if (absl::Status status = buffer.ReadArray<" << bulk_type - << ">(absl::Span<" << bulk_type << ">(" << item->member_name - << ".data(), " << fixed_extent - << ")); !status.ok()) return status;\n"; + os << " if (absl::Status _phaser_status = " + "_phaser_buffer.ReadArray<" + << bulk_type << ">(absl::Span<" << bulk_type << ">(" + << item->member_name << ".data(), " << fixed_extent + << ")); !_phaser_status.ok()) return _phaser_status;\n"; } else { - os << " for (size_t ros_index = 0; ros_index < " << fixed_extent - << "; ++ros_index) {\n"; + os << " for (size_t _phaser_index = 0; _phaser_index < " + << fixed_extent << "; ++_phaser_index) {\n"; GenerateROSFieldRead(os, descriptor, item->member_name, " ", false, - "ros_index"); + "_phaser_index"); os << " }\n"; } } @@ -3516,13 +3644,15 @@ void MessageGenerator::GenerateSerializedSize(std::ostream& os, bool decl) { return; } os << "size_t " << MessageName(message_) << "::SerializedSize() const {\n"; - os << " size_t size = 0;\n"; + os << " size_t _phaser_serialized_size = 0;\n"; for (auto& field : fields_) { if (field->field->is_repeated()) { - os << " size += " << field->member_name << ".SerializedSize();\n"; + os << " _phaser_serialized_size += " << field->member_name + << ".SerializedSize();\n"; } else { os << " if (" << field->member_name << ".IsPresent()) {\n"; - os << " size += " << field->member_name << ".SerializedSize();\n"; + os << " _phaser_serialized_size += " << field->member_name + << ".SerializedSize();\n"; os << " }\n"; } } @@ -3531,31 +3661,34 @@ void MessageGenerator::GenerateSerializedSize(std::ostream& os, bool decl) { for (size_t i = 0; i < u->members.size(); i++) { auto& field = u->members[i]; os << " case " << field->field->number() << ":\n"; - os << " size += " << u->member_name << ".SerializedSize<" << i << ">(" - << field->field->number() << ");\n"; + os << " _phaser_serialized_size += " << u->member_name + << ".SerializedSize<" << i << ">(" << field->field->number() << ");\n"; os << " break;\n"; } os << " }\n"; } - os << " return size;\n"; + os << " return _phaser_serialized_size;\n"; os << "}\n\n"; } void MessageGenerator::GenerateSerializer(std::ostream& os, bool decl) { if (decl) { - os << " absl::Status Serialize(::phaser::ProtoBuffer &buffer) const;\n"; + os << " absl::Status Serialize(" + "::phaser::ProtoBuffer &_phaser_buffer) const;\n"; return; } os << "absl::Status " << MessageName(message_) - << "::Serialize(::phaser::ProtoBuffer &buffer) const {\n"; + << "::Serialize(::phaser::ProtoBuffer &_phaser_buffer) const {\n"; for (auto& field : fields_) { if (field->field->is_repeated()) { - os << " if (absl::Status status = " << field->member_name - << ".Serialize(buffer); !status.ok()) return status;\n"; + os << " if (absl::Status _phaser_status = " << field->member_name + << ".Serialize(_phaser_buffer); !_phaser_status.ok()) " + "return _phaser_status;\n"; } else { os << " if (" << field->member_name << ".IsPresent()) {\n"; - os << " if (absl::Status status = " << field->member_name - << ".Serialize(buffer); !status.ok()) return status;\n"; + os << " if (absl::Status _phaser_status = " << field->member_name + << ".Serialize(_phaser_buffer); !_phaser_status.ok()) " + "return _phaser_status;\n"; os << " }\n"; } } @@ -3564,9 +3697,10 @@ void MessageGenerator::GenerateSerializer(std::ostream& os, bool decl) { for (size_t i = 0; i < u->members.size(); i++) { auto& field = u->members[i]; os << " case " << field->field->number() << ":\n"; - os << " if (absl::Status status = " << u->member_name << ".Serialize<" - << i << ">(" << field->field->number() - << ", buffer); !status.ok()) return status;\n"; + os << " if (absl::Status _phaser_status = " << u->member_name + << ".Serialize<" << i << ">(" << field->field->number() + << ", _phaser_buffer); !_phaser_status.ok()) " + "return _phaser_status;\n"; os << " break;\n"; } os << " }\n"; @@ -3577,11 +3711,12 @@ void MessageGenerator::GenerateSerializer(std::ostream& os, bool decl) { void MessageGenerator::GenerateDeserializer(std::ostream& os, bool decl) { if (decl) { - os << " absl::Status Deserialize(::phaser::ProtoBuffer &buffer);\n"; + os << " absl::Status Deserialize(" + "::phaser::ProtoBuffer &_phaser_buffer);\n"; return; } os << "absl::Status " << MessageName(message_) - << "::Deserialize(::phaser::ProtoBuffer &buffer) {"; + << "::Deserialize(::phaser::ProtoBuffer &_phaser_buffer) {"; bool has_array_fields = false; for (auto& field : fields_) { if (UsesArrayFacade(field->field)) { @@ -3598,35 +3733,40 @@ void MessageGenerator::GenerateDeserializer(std::ostream& os, bool decl) { } } os << R"XXX( - while (!buffer.Eof()) { - absl::StatusOr tag = - buffer.DeserializeVarint(); - if (!tag.ok()) { - return tag.status(); + while (!_phaser_buffer.Eof()) { + absl::StatusOr _phaser_tag = + _phaser_buffer.DeserializeVarint(); + if (!_phaser_tag.ok()) { + return _phaser_tag.status(); } - uint32_t field_number = *tag >> ::phaser::ProtoBuffer::kFieldIdShift; - switch (field_number) { + uint32_t _phaser_field_number = + *_phaser_tag >> ::phaser::ProtoBuffer::kFieldIdShift; + switch (_phaser_field_number) { )XXX"; for (auto& field : fields_) { os << " case " << field->field->number() << ":\n"; - os << " if (absl::Status status = " << field->member_name - << ".Deserialize(buffer); !status.ok()) return status;\n"; + os << " if (absl::Status _phaser_status = " << field->member_name + << ".Deserialize(_phaser_buffer); !_phaser_status.ok()) " + "return _phaser_status;\n"; os << " break;\n"; } for (auto& [oneof, u] : unions_) { for (size_t i = 0; i < u->members.size(); i++) { auto& field = u->members[i]; os << " case " << field->field->number() << ":\n"; - os << " if (absl::Status status = " << u->member_name + os << " if (absl::Status _phaser_status = " << u->member_name << ".Deserialize<" << i << ">(" << field->field->number() - << ", buffer); !status.ok()) return status;\n"; + << ", _phaser_buffer); !_phaser_status.ok()) " + "return _phaser_status;\n"; os << " break;\n"; } } os << R"XXX( default: - if (absl::Status status = buffer.SkipTag(*tag); !status.ok()) { - return status; + if (absl::Status _phaser_status = + _phaser_buffer.SkipTag(*_phaser_tag); + !_phaser_status.ok()) { + return _phaser_status; } } } @@ -3635,8 +3775,9 @@ void MessageGenerator::GenerateDeserializer(std::ostream& os, bool decl) { os << "\n"; for (auto& field : fields_) { if (UsesArrayFacade(field->field)) { - os << " if (absl::Status status = " << field->member_name - << ".FinalizeDeserialize(); !status.ok()) return status;\n"; + os << " if (absl::Status _phaser_status = " << field->member_name + << ".FinalizeDeserialize(); !_phaser_status.ok()) " + "return _phaser_status;\n"; } } } @@ -3687,12 +3828,12 @@ void MessageGenerator::GenerateProtobufSerialization(std::ostream& os) { } void MessageGenerator::GenerateIndent(std::ostream& os) { - os << " void Indent([[maybe_unused]] int indent) const {\n"; + os << " void Indent([[maybe_unused]] int _phaser_indent) const {\n"; for (auto& field : fields_) { - os << " " << field->member_name << ".Indent(indent);\n"; + os << " " << field->member_name << ".Indent(_phaser_indent);\n"; } for (auto& [oneof, u] : unions_) { - os << " " << u->member_name << ".Indent(indent);\n"; + os << " " << u->member_name << ".Indent(_phaser_indent);\n"; } os << " }\n\n"; } @@ -3768,11 +3909,13 @@ void MessageGenerator::GenerateStreamer(std::ostream& os) { void MessageGenerator::GenerateCopy(std::ostream& os, bool decl) { if (decl) { os << " template \n"; - os << " absl::Status CloneFrom(const T & other);\n\n"; - os << " void CopyFrom(const ::phaser::Message& other) override{\n"; - os << " const " << MessageName(message_) << "& m = static_cast(other);\n"; - os << " (void)CloneFrom(m);\n"; + os << " absl::Status CloneFrom(const T& _phaser_other);\n\n"; + os << " void CopyFrom(" + "const ::phaser::Message& _phaser_other) override {\n"; + os << " const " << MessageName(message_) + << "& _phaser_message = static_cast(_phaser_other);\n"; + os << " (void)CloneFrom(_phaser_message);\n"; os << " }\n\n"; return; } @@ -3780,7 +3923,7 @@ void MessageGenerator::GenerateCopy(std::ostream& os, bool decl) { // CloneFrom. os << "template \n"; os << "inline absl::Status " << MessageName(message_) - << "::CloneFrom([[maybe_unused]] const T & other) {\n"; + << "::CloneFrom([[maybe_unused]] const T& _phaser_other) {\n"; if (IsRosFrontend()) { for (auto& field : fields_) { if (field->field->is_repeated()) { @@ -3789,55 +3932,66 @@ void MessageGenerator::GenerateCopy(std::ostream& os, bool decl) { const int array_size = GetArraySize(field->field); if (field->field->type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE) { - os << " for (size_t i = 0; i < static_cast(" << array_size - << "); i++) {\n"; - os << " auto source = other." << field->member_name - << ".Get(i);\n"; - os << " if (source.IsBound()) {\n"; - os << " auto destination = " << field->member_name - << ".Mutable(i);\n"; - os << " if (absl::Status s = destination.CloneFrom(source); " - "!s.ok()) return s;\n"; + os << " for (size_t _phaser_index = 0; " + "_phaser_index < static_cast(" + << array_size << "); ++_phaser_index) {\n"; + os << " auto _phaser_source = _phaser_other." + << field->member_name << ".Get(_phaser_index);\n"; + os << " if (_phaser_source.IsBound()) {\n"; + os << " auto _phaser_destination = " << field->member_name + << ".Mutable(_phaser_index);\n"; + os << " if (absl::Status _phaser_status = " + "_phaser_destination.CloneFrom(_phaser_source); " + "!_phaser_status.ok()) return _phaser_status;\n"; os << " }\n"; os << " }\n"; } else if (field->field->type() == google::protobuf::FieldDescriptor::TYPE_STRING || field->field->type() == google::protobuf::FieldDescriptor::TYPE_BYTES) { - os << " for (size_t i = 0; i < static_cast(" << array_size - << "); i++) {\n"; - os << " " << field->member_name << ".Set(i, other." - << field->member_name << ".Get(i));\n"; + os << " for (size_t _phaser_index = 0; " + "_phaser_index < static_cast(" + << array_size << "); ++_phaser_index) {\n"; + os << " " << field->member_name + << ".Set(_phaser_index, _phaser_other." << field->member_name + << ".Get(_phaser_index));\n"; os << " }\n"; } else { - os << " for (size_t i = 0; i < static_cast(" << array_size - << "); i++) {\n"; - os << " " << field->member_name << ".Set(i, other." - << field->member_name << ".Get(i));\n"; + os << " for (size_t _phaser_index = 0; " + "_phaser_index < static_cast(" + << array_size << "); ++_phaser_index) {\n"; + os << " " << field->member_name + << ".Set(_phaser_index, _phaser_other." << field->member_name + << ".Get(_phaser_index));\n"; os << " }\n"; } } else if (field->field->type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE) { - os << " for (auto v : other." << field->member_name << ") {\n"; - os << " auto m = " << field->member_name << ".Add();\n"; - os << " if (absl::Status s = m.CloneFrom(v); !s.ok()) return " - "s;\n"; + os << " for (auto _phaser_value : _phaser_other." + << field->member_name << ") {\n"; + os << " auto _phaser_message = " << field->member_name + << ".Add();\n"; + os << " if (absl::Status _phaser_status = " + "_phaser_message.CloneFrom(_phaser_value); " + "!_phaser_status.ok()) return _phaser_status;\n"; os << " }\n"; } else { - os << " for (auto v : other." << field->member_name << ") {\n"; - os << " " << field->member_name << ".Add(v);\n"; + os << " for (auto _phaser_value : _phaser_other." + << field->member_name << ") {\n"; + os << " " << field->member_name << ".Add(_phaser_value);\n"; os << " }\n"; } } else if (field->field->type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE) { - os << " if (other." << field->member_name << ".IsPresent()) {\n"; + os << " if (_phaser_other." << field->member_name + << ".IsPresent()) {\n"; if (IsRosIntrinsic(field->field)) { - os << " " << field->member_name << ".Set(other." + os << " " << field->member_name << ".Set(_phaser_other." << field->member_name << ".Get());\n"; } else { - os << " if (absl::Status s = " << field->member_name - << ".Mutable()->CloneFrom(other." << field->member_name - << ".Get()); !s.ok()) return s;\n"; + os << " if (absl::Status _phaser_status = " << field->member_name + << ".Mutable()->CloneFrom(_phaser_other." << field->member_name + << ".Get()); !_phaser_status.ok()) return _phaser_status;\n"; } os << " } else {\n"; os << " " << field->member_name << ".Clear();\n"; @@ -3846,15 +4000,17 @@ void MessageGenerator::GenerateCopy(std::ostream& os, bool decl) { google::protobuf::FieldDescriptor::TYPE_STRING || field->field->type() == google::protobuf::FieldDescriptor::TYPE_BYTES) { - os << " if (other." << field->member_name << ".IsPresent()) {\n"; - os << " " << field->member_name << ".Set(other." + os << " if (_phaser_other." << field->member_name + << ".IsPresent()) {\n"; + os << " " << field->member_name << ".Set(_phaser_other." << field->member_name << ".Get());\n"; os << " } else {\n"; os << " " << field->member_name << ".Clear();\n"; os << " }\n"; } else { - os << " if (other." << field->member_name << ".IsPresent()) {\n"; - os << " " << field->member_name << ".Set(other." + os << " if (_phaser_other." << field->member_name + << ".IsPresent()) {\n"; + os << " " << field->member_name << ".Set(_phaser_other." << field->member_name << ".Get());\n"; os << " } else {\n"; os << " " << field->member_name << ".Clear();\n"; @@ -3863,23 +4019,24 @@ void MessageGenerator::GenerateCopy(std::ostream& os, bool decl) { } if (!unions_.empty()) { for (auto& [oneof, u] : unions_) { - os << " switch (other." << u->member_name << ".Discriminator()) {\n"; + os << " switch (_phaser_other." << u->member_name + << ".Discriminator()) {\n"; for (size_t i = 0; i < u->members.size(); i++) { auto& field = u->members[i]; os << " case " << field->field->number() << ":\n"; if (field->field->type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE) { - os << " if (absl::Status s = " << u->member_name - << ".template CloneFrom<" << i << ">(other." << u->member_name - << ".template GetReference<" << i << ", " + os << " if (absl::Status _phaser_status = " << u->member_name + << ".template CloneFrom<" << i << ">(_phaser_other." + << u->member_name << ".template GetReference<" << i << ", " << MessageName(field->field->message_type()) - << ">()); !s.ok()) " - "return s;\n"; + << ">()); !_phaser_status.ok()) return _phaser_status;\n"; } else { - os << " if (absl::Status s = " << u->member_name - << ".template CloneFrom<" << i << ">(other." << u->member_name - << ".template GetValue<" << i << ", " << field->c_type - << ">()); !s.ok()) return s;\n"; + os << " if (absl::Status _phaser_status = " << u->member_name + << ".template CloneFrom<" << i << ">(_phaser_other." + << u->member_name << ".template GetValue<" << i << ", " + << field->c_type + << ">()); !_phaser_status.ok()) return _phaser_status;\n"; } os << " break;\n"; } @@ -3894,26 +4051,33 @@ void MessageGenerator::GenerateCopy(std::ostream& os, bool decl) { } else { for (auto& field : fields_) { if (field->field->is_repeated()) { - os << " for (auto v : other." << field->field->name() << "()) {\n"; + os << " for (auto _phaser_value : _phaser_other." + << field->field->name() << "()) {\n"; if (field->field->type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE) { - os << " auto m = add_" << field->field->name() << "();\n"; - os << " if (absl::Status s = m.CloneFrom(v); !s.ok()) return " - "s;\n"; + os << " auto _phaser_message = add_" << field->field->name() + << "();\n"; + os << " if (absl::Status _phaser_status = " + "_phaser_message.CloneFrom(_phaser_value); " + "!_phaser_status.ok()) return _phaser_status;\n"; } else { - os << " add_" << field->field->name() << "(v);\n"; + os << " add_" << field->field->name() << "(_phaser_value);\n"; } os << " }\n"; } else { - os << " if (other." << field->member_name << ".IsPresent()) {\n"; + os << " if (_phaser_other." << field->member_name + << ".IsPresent()) {\n"; if (field->field->type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE) { - os << " auto* m = mutable_" << field->field->name() << "();\n"; - os << " if (absl::Status s = m->CloneFrom(other." - << field->field->name() << "()); !s.ok()) return s;\n"; + os << " auto* _phaser_message = mutable_" << field->field->name() + << "();\n"; + os << " if (absl::Status _phaser_status = " + "_phaser_message->CloneFrom(_phaser_other." + << field->field->name() + << "()); !_phaser_status.ok()) return _phaser_status;\n"; } else { - os << " set_" << field->field->name() << "(other." + os << " set_" << field->field->name() << "(_phaser_other." << field->field->name() << "());\n"; } os << " }\n"; @@ -3921,13 +4085,15 @@ void MessageGenerator::GenerateCopy(std::ostream& os, bool decl) { } if (!unions_.empty()) { for (auto& [oneof, u] : unions_) { - os << " switch (other." << u->member_name << ".Discriminator()) {\n"; + os << " switch (_phaser_other." << u->member_name + << ".Discriminator()) {\n"; for (size_t i = 0; i < u->members.size(); i++) { auto& field = u->members[i]; os << " case " << field->field->number() << ":\n"; - os << " if (absl::Status s = " << u->member_name - << ".template CloneFrom<" << i << ">(other." - << field->field->name() << "()); !s.ok()) return s;\n"; + os << " if (absl::Status _phaser_status = " << u->member_name + << ".template CloneFrom<" << i << ">(_phaser_other." + << field->field->name() + << "()); !_phaser_status.ok()) return _phaser_status;\n"; os << " break;\n"; } os << " }\n"; diff --git a/phaser/compiler/message_gen.h b/phaser/compiler/message_gen.h index 8860c28..2b2ccda 100644 --- a/phaser/compiler/message_gen.h +++ b/phaser/compiler/message_gen.h @@ -57,16 +57,18 @@ class MessageGenerator { const std::string& added_namespace, const std::string& package_name, bool generate_active_message = false, - FrontendStyle frontend_style = FrontendStyle::kProtobuf) + FrontendStyle frontend_style = FrontendStyle::kProtobuf, + bool generate_ros_metadata = false) : message_(message), added_namespace_(added_namespace), package_name_(package_name), generate_active_message_(generate_active_message), - frontend_style_(frontend_style) { + frontend_style_(frontend_style), + generate_ros_metadata_(generate_ros_metadata) { for (int i = 0; i < message_->nested_type_count(); i++) { nested_message_gens_.push_back(std::make_unique( message_->nested_type(i), added_namespace, package_name, - generate_active_message, frontend_style)); + generate_active_message, frontend_style, generate_ros_metadata)); } // Enums for (int i = 0; i < message_->enum_type_count(); i++) { @@ -227,6 +229,7 @@ class MessageGenerator { std::string package_name_; bool generate_active_message_ = false; FrontendStyle frontend_style_ = FrontendStyle::kProtobuf; + bool generate_ros_metadata_ = false; }; } // namespace phaser diff --git a/phaser/options.proto b/phaser/options.proto index d5fcc50..fa4c2e9 100644 --- a/phaser/options.proto +++ b/phaser/options.proto @@ -5,19 +5,18 @@ package phaser; import "google/protobuf/descriptor.proto"; message RosMessageMetadata { - // Original ROS datatype in package/Message form. + // ROS datatype override in package/Message form. By default Phaser removes + // a trailing ".proto" from the protobuf package and appends the message name. string data_type = 1; - // Original ROS source definition. Phaser appends transitive definitions. - string definition = 2; - // Canonical constant declarations used by the ROS MD5 algorithm. - repeated string constants = 3; + repeated string constants = 2; } message RosFieldMetadata { - // Original ROS field type, including any array suffix. Omit when the - // protobuf type maps unambiguously to a ROS type. + // Original ROS field type. Repeated-field array suffixes are inferred from + // the protobuf cardinality and phaser.array_size. Omit the type entirely + // when the protobuf type maps unambiguously to a ROS type. string type = 1; // Original ROS field name. Omit when it is unchanged. @@ -29,9 +28,20 @@ message RosFieldMetadata { // Datatype for the non-protobuf nested ROS message. string nested_data_type = 4; +} - // Source definition for the non-protobuf nested ROS message. - string nested_definition = 5; +message RosEnumMetadata { + // Original ROS primitive type shared by the constants in this enum. + string type = 1; +} + +message RosEnumValueMetadata { + // Exclude a synthetic protobuf value from the ROS constant list. + bool ignore = 1; + + // Original ROS name and textual value when protobuf changed either one. + string name = 2; + string value = 3; } extend google.protobuf.FieldOptions { @@ -45,6 +55,16 @@ extend google.protobuf.FieldOptions { } extend google.protobuf.MessageOptions { - // Original ROS message identity and source definition. + // ROS information that cannot be inferred from the protobuf message. RosMessageMetadata ros_message = 50001; } + +extend google.protobuf.EnumOptions { + // Marks an enum as a group of original ROS message constants. + RosEnumMetadata ros_enum = 50001; +} + +extend google.protobuf.EnumValueOptions { + // Overrides for a ROS constant represented by a protobuf enum value. + RosEnumValueMetadata ros_enum_value = 50001; +} diff --git a/phaser/phaser_library.bzl b/phaser/phaser_library.bzl index 9d98c15..bd86f80 100644 --- a/phaser/phaser_library.bzl +++ b/phaser/phaser_library.bzl @@ -23,7 +23,8 @@ def _phaser_action( add_namespace, target_name, frontend, - enable_active_message): + enable_active_message, + ros_metadata): # The protobuf compiler allow plugins to get arguments specified in the --plugin_out # argument. The args are passed as a comma separated list of key=value pairs followed # by a colon and the output directory. @@ -35,6 +36,8 @@ def _phaser_action( options.append("frontend={}".format(frontend)) if enable_active_message: options.append("active_message=true") + if ros_metadata: + options.append("ros_metadata=true") options_and_out_dir = "--phaser_out={}:{}".format(",".join(options), out_dir) inputs = depset(direct = direct_sources, transitive = transitive_sources) @@ -195,6 +198,7 @@ def _phaser_impl(ctx): ctx.attr.target_name, frontend, ctx.attr.enable_active_message, + ctx.attr.ros_metadata, ) return [DefaultInfo(files = depset(outputs))] @@ -220,6 +224,7 @@ _phaser_gen = rule( "target_name": attr.string(), "frontend": attr.string(default = "protobuf"), "enable_active_message": attr.bool(default = False), + "ros_metadata": attr.bool(default = False), }, implementation = _phaser_impl, ) @@ -248,7 +253,8 @@ def phaser_library( enable_active_message = False, frontend = "protobuf", cc_deps = [], - direct_header_symlinks = True): + direct_header_symlinks = True, + ros_metadata = False): """ Generate a cc_libary for protobuf files specified in deps. @@ -266,6 +272,7 @@ def phaser_library( such as ROS1 message/runtime libraries for intrinsic ROS fields. direct_header_symlinks: create short direct-source header aliases. Disable this when generating multiple frontends from one proto target. + ros_metadata: generate ROS datatype, definition, and MD5 functions. """ if frontend not in ("protobuf", "ros"): fail("phaser_library frontend must be 'protobuf' or 'ros', got: {}".format(frontend)) @@ -281,6 +288,7 @@ def phaser_library( target_name = name, enable_active_message = enable_active_message, frontend = frontend, + ros_metadata = ros_metadata, ) srcs = name + "_srcs" diff --git a/phaser/ros_compile_test.cc b/phaser/ros_compile_test.cc index 16e2730..00d5e37 100644 --- a/phaser/ros_compile_test.cc +++ b/phaser/ros_compile_test.cc @@ -1,14 +1,14 @@ // Compile and runtime fixture for frontend=ros generated messages. -#include "phaser/testdata/RosCompile.phaser.h" -#include "phaser/testdata/RosCompile.pb.h" - #include #include #include #include #include +#include "absl/types/span.h" #include "gtest/gtest.h" +#include "phaser/testdata/RosCompile.pb.h" +#include "phaser/testdata/RosCompile.phaser.h" namespace foo::bar::phaser { namespace { @@ -42,6 +42,49 @@ TEST(RosCompileTest, ScalarConversionAndAssignment) { EXPECT_EQ(msg.color.Get(), RosColor::ROS_COLOR_BLUE); } +TEST(RosCompileTest, GeneratedInternalsDoNotClashWithFieldNames) { + GeneratorNameCollisions message; + message.status = 1; + message.size = 2; + message.buffer = "buffer"; + message.other = true; + message.indent = 5; + message.i.push_back(6); + message.source = "source"; + message.destination = "destination"; + message.message = "message"; + message.input = "input"; + message.output = "output"; + message.tag = 12; + message.field_number = 13; + message.count = 14; + message.data = "data"; + message.value = "value"; + + std::string protobuf; + ASSERT_TRUE(message.SerializeToString(&protobuf)); + GeneratorNameCollisions protobuf_copy; + ASSERT_TRUE(protobuf_copy.ParseFromString(protobuf)); + EXPECT_EQ(protobuf_copy.status.Get(), 1); + EXPECT_EQ(protobuf_copy.size.Get(), 2u); + EXPECT_EQ(protobuf_copy.buffer.Get(), "buffer"); + + std::string ros; + ASSERT_TRUE(message.SerializeToROSString(&ros).ok()); + GeneratorNameCollisions ros_copy; + ASSERT_TRUE( + ros_copy.ParseFromROS(absl::Span(ros.data(), ros.size())) + .ok()); + EXPECT_EQ(ros_copy.other.Get(), true); + EXPECT_EQ(ros_copy.i.size(), 1u); + EXPECT_EQ(ros_copy.i[0], 6); + + GeneratorNameCollisions clone; + ASSERT_TRUE(clone.CloneFrom(message).ok()); + EXPECT_EQ(clone.source.Get(), "source"); + EXPECT_EQ(clone.destination.Get(), "destination"); +} + TEST(RosCompileTest, StringConversionAndAssignment) { RosCompileMessage msg; msg.name = "hello"; @@ -212,7 +255,6 @@ TEST(RosCompileTest, ProxyMoveAssignIsValueSemantics) { EXPECT_EQ(dst.x.Get(), 5); } - TEST(RosCompileTest, MessageCopyAssignUsesCloneFrom) { RosCompileMessage src; src.x = 11; diff --git a/phaser/ros_metadata_protobuf_frontend_test.cc b/phaser/ros_metadata_protobuf_frontend_test.cc index 5097540..9fe2a29 100644 --- a/phaser/ros_metadata_protobuf_frontend_test.cc +++ b/phaser/ros_metadata_protobuf_frontend_test.cc @@ -7,33 +7,38 @@ #include "phaser/runtime/md5.h" #include "phaser/testdata/ros_metadata_protobuf_phaser/phaser/testdata/RosMetadata.phaser.h" -namespace ros_metadata::protobuf_api { +namespace example_msgs::proto::protobuf_api { namespace { TEST(RosMetadataProtobufFrontendTest, MatchesRosMetadata) { - EXPECT_EQ(Bool::RosDataType(), "std_msgs/Bool"); + EXPECT_EQ(Bool::RosDataType(), "example_msgs/Bool"); EXPECT_EQ(Bool::RosDefinition(), "bool data\n"); EXPECT_EQ(Bool::RosMd5(), "8b94c1b53db61fb6aed406028ad6332a"); + EXPECT_EQ(Status::RosDataType(), "example_msgs/Status"); + EXPECT_EQ(Status::RosDefinition(), "int32 OK=0\nint32 BAD=1\nint32 value\n"); + EXPECT_EQ(Status::RosMd5(), + ::phaser::Md5("int32 OK=0\nint32 BAD=1\nint32 value")); + EXPECT_EQ(Wrapper::RosDataType(), "example_msgs/Wrapper"); EXPECT_EQ(Wrapper::RosDefinition(), - "uint8 READY=1\nstd_msgs/Bool child\nint32[3] samples\n" - "example_msgs/Status status\nbool ready\n\n" + "uint8 READY=1\nexample_msgs/Bool child\nint32[3] samples\n" + "example_msgs/Status status\nbool ready\ntime stamp\n" "==================================================================" "==============\n" - "MSG: std_msgs/Bool\n" - "bool data\n\n" + "MSG: example_msgs/Bool\n" + "bool data\n" "==================================================================" "==============\n" "MSG: example_msgs/Status\n" - "uint8 OK=0\nuint8 BAD=1\n"); + "int32 OK=0\nint32 BAD=1\nint32 value\n"); EXPECT_EQ( Wrapper::RosMd5(), ::phaser::Md5("uint8 READY=1\n" "8b94c1b53db61fb6aed406028ad6332a child\n" "int32[3] samples\n" + - ::phaser::Md5("uint8 OK=0\nuint8 BAD=1") + " status\nbool ready")); + Status::RosMd5() + " status\nbool ready\ntime stamp")); } } // namespace -} // namespace ros_metadata::protobuf_api +} // namespace example_msgs::proto::protobuf_api diff --git a/phaser/ros_metadata_ros_frontend_test.cc b/phaser/ros_metadata_ros_frontend_test.cc index ea47e6a..27b5549 100644 --- a/phaser/ros_metadata_ros_frontend_test.cc +++ b/phaser/ros_metadata_ros_frontend_test.cc @@ -7,22 +7,27 @@ #include "phaser/runtime/md5.h" #include "phaser/testdata/ros_metadata_ros_phaser/phaser/testdata/RosMetadata.phaser.h" -namespace ros_metadata::ros_api { +namespace example_msgs::proto::ros_api { namespace { TEST(RosMetadataRosFrontendTest, MatchesRosMetadata) { - EXPECT_EQ(Bool::RosDataType(), "std_msgs/Bool"); + EXPECT_EQ(Bool::RosDataType(), "example_msgs/Bool"); EXPECT_EQ(Bool::RosDefinition(), "bool data\n"); EXPECT_EQ(Bool::RosMd5(), "8b94c1b53db61fb6aed406028ad6332a"); + EXPECT_EQ(Status::RosDataType(), "example_msgs/Status"); + EXPECT_EQ(Status::RosDefinition(), "int32 OK=0\nint32 BAD=1\nint32 value\n"); + EXPECT_EQ(Status::RosMd5(), + ::phaser::Md5("int32 OK=0\nint32 BAD=1\nint32 value")); + EXPECT_EQ(Wrapper::RosDataType(), "example_msgs/Wrapper"); EXPECT_EQ( Wrapper::RosMd5(), ::phaser::Md5("uint8 READY=1\n" "8b94c1b53db61fb6aed406028ad6332a child\n" "int32[3] samples\n" + - ::phaser::Md5("uint8 OK=0\nuint8 BAD=1") + " status\nbool ready")); + Status::RosMd5() + " status\nbool ready\ntime stamp")); } } // namespace -} // namespace ros_metadata::ros_api +} // namespace example_msgs::proto::ros_api diff --git a/phaser/testdata/BUILD b/phaser/testdata/BUILD index f8ee908..adc59dd 100644 --- a/phaser/testdata/BUILD +++ b/phaser/testdata/BUILD @@ -90,13 +90,17 @@ phaser_library( proto_library( name = "ros_metadata_proto", srcs = ["RosMetadata.proto"], - deps = ["//phaser:options_proto"], + deps = [ + "//phaser:options_proto", + "@com_google_protobuf//:timestamp_proto", + ], ) phaser_library( name = "ros_metadata_protobuf_phaser", add_namespace = "protobuf_api", direct_header_symlinks = False, + ros_metadata = True, runtime = "//phaser/runtime:phaser_runtime", deps = [":ros_metadata_proto"], ) @@ -104,8 +108,10 @@ phaser_library( phaser_library( name = "ros_metadata_ros_phaser", add_namespace = "ros_api", + cc_deps = [":ros1_shim"], direct_header_symlinks = False, frontend = "ros", + ros_metadata = True, runtime = "//phaser/runtime:phaser_runtime", deps = [":ros_metadata_proto"], ) diff --git a/phaser/testdata/RosCompile.proto b/phaser/testdata/RosCompile.proto index e3a5a16..43d115f 100644 --- a/phaser/testdata/RosCompile.proto +++ b/phaser/testdata/RosCompile.proto @@ -48,3 +48,24 @@ message RosPackedFixedMessage { repeated double double_values = 6; repeated fixed64 fixed_array = 7 [(phaser.array_size) = 3]; } + +// These names have all been used by generated method parameters or local +// variables. They must remain usable as unmodified ROS frontend field names. +message GeneratorNameCollisions { + int32 status = 1; + uint32 size = 2; + string buffer = 3; + bool other = 4; + int32 indent = 5; + repeated int32 i = 6; + string source = 7; + string destination = 8; + string message = 9; + string input = 10; + string output = 11; + uint32 tag = 12; + uint32 field_number = 13; + uint32 count = 14; + string data = 15; + string value = 16; +} diff --git a/phaser/testdata/RosMetadata.proto b/phaser/testdata/RosMetadata.proto index 474d3e7..cc62ec2 100644 --- a/phaser/testdata/RosMetadata.proto +++ b/phaser/testdata/RosMetadata.proto @@ -1,45 +1,40 @@ syntax = "proto3"; -package ros_metadata; +package example_msgs.proto; import "phaser/options.proto"; +import "google/protobuf/timestamp.proto"; -enum Status { - STATUS_OK = 0; - STATUS_BAD = 1; +message Status { + enum Constants { + option (phaser.ros_enum) = { + type: "int32" + }; + + OK = 0; + BAD = 1; + } + + int32 value = 1; } message Bool { - option (phaser.ros_message) = { - data_type: "std_msgs/Bool" - definition: "bool data\n" - }; - bool data = 1; } message Wrapper { - option (phaser.ros_message) = { - data_type: "example_msgs/Wrapper" - definition: "uint8 READY=1\nstd_msgs/Bool child\nint32[3] samples\nexample_msgs/Status status\nbool ready\n" - constants: "uint8 READY=1" - }; + enum Constants { + option (phaser.ros_enum) = { + type: "uint8" + }; + + CONSTANTS_UNSPECIFIED = 0 [(phaser.ros_enum_value) = { ignore: true }]; + READY = 1; + } Bool child = 1; - repeated int32 samples = 2 [ - (phaser.array_size) = 3, - (phaser.ros_field) = { - type: "int32[3]" - } - ]; - Status status_ = 3 [(phaser.ros_field) = { - type: "example_msgs/Status" - name: "status" - nested_md5_text: "uint8 OK=0\nuint8 BAD=1" - nested_data_type: "example_msgs/Status" - nested_definition: "uint8 OK=0\nuint8 BAD=1\n" - }]; - bool ready_ = 4 [(phaser.ros_field) = { - name: "ready" - }]; + repeated int32 samples = 2 [(phaser.array_size) = 3]; + Status status = 3; + bool ready = 4; + google.protobuf.Timestamp stamp = 5; } From 858f24a1de318de978c8cec128c0428bbcdfc9f9 Mon Sep 17 00:00:00 2001 From: Dave Allison Date: Wed, 19 Aug 2026 14:56:40 -0700 Subject: [PATCH 3/5] Some internal changes --- phaser/compiler/gen.cc | 29 +++++++++++++++++--- phaser/compiler/message_gen.cc | 27 ++++++++++++++++-- phaser/phaser_library.bzl | 50 +++++++++++++++++++++++----------- phaser/stress_test.cc | 25 +++++++++++++++++ 4 files changed, 108 insertions(+), 23 deletions(-) diff --git a/phaser/compiler/gen.cc b/phaser/compiler/gen.cc index f1efb7c..d3e5850 100644 --- a/phaser/compiler/gen.cc +++ b/phaser/compiler/gen.cc @@ -74,6 +74,16 @@ static void WriteToZeroCopyStream( static std::string GeneratedFilename(const std::filesystem::path& package_name, const std::filesystem::path& target_name, std::string filename) { + if (filename.rfind("../", 0) == 0) { + // Bazel represents external sources as ..//. Keep only the + // repository-relative path so it cannot escape target_name. + const size_t repository_end = filename.find('/', 3); + filename = filename.substr(repository_end + 1); + } else if (filename.rfind("external/", 0) == 0) { + // protoc can instead preserve the execroot-relative external// form. + const size_t repository_end = filename.find('/', sizeof("external/") - 1); + filename = filename.substr(repository_end + 1); + } size_t virtual_imports = filename.find("_virtual_imports/"); if (virtual_imports != std::string::npos) { // This is something like: @@ -85,6 +95,17 @@ static std::string GeneratedFilename(const std::filesystem::path& package_name, return package_name / target_name / filename; } +static std::string GeneratedIncludeFilename( + const std::filesystem::path& package_name, + const std::filesystem::path& target_name, const std::string& filename) { + std::string result = GeneratedFilename(package_name, target_name, filename); + if (result.rfind("external/", 0) == 0) { + const size_t repository_end = result.find('/', sizeof("external/") - 1); + result = result.substr(repository_end + 1); + } + return result; +} + bool CodeGenerator::Generate( const google::protobuf::FileDescriptor* file, const std::string& parameter, google::protobuf::compiler::GeneratorContext* generator_context, @@ -254,8 +275,8 @@ void Generator::GenerateHeaders(std::ostream& os, std::string* error) { dep->name() == std::string("phaser/options.proto")) { continue; } - std::string base = GeneratedFilename( - package_name_, target_name_, std::string(dep->name())); + std::string base = GeneratedIncludeFilename(package_name_, target_name_, + std::string(dep->name())); std::filesystem::path p(base); p.replace_extension(".phaser.h"); os << "#include \"" << p.string() << "\"\n"; @@ -285,8 +306,8 @@ void Generator::GenerateHeaders(std::ostream& os, std::string* error) { } void Generator::GenerateSources(std::ostream& os) { - std::filesystem::path p(GeneratedFilename(package_name_, target_name_, - std::string(file_->name()))); + std::filesystem::path p(GeneratedIncludeFilename(package_name_, target_name_, + std::string(file_->name()))); p.replace_extension(".phaser.h"); os << "#include \"" << p.string() << "\"\n"; diff --git a/phaser/compiler/message_gen.cc b/phaser/compiler/message_gen.cc index 5d92df2..d3b8670 100644 --- a/phaser/compiler/message_gen.cc +++ b/phaser/compiler/message_gen.cc @@ -1559,6 +1559,13 @@ void MessageGenerator::GenerateCreators(std::ostream& os, bool decl) { "= ::phaser::Tuning::kPerformance);\n"; os << " void InitDynamicMutable(size_t initial_size = 8192, " "::phaser::Tuning tuning = ::phaser::Tuning::kPerformance);\n"; + os << " static absl::StatusOr<" << MessageName(message_) + << "> TryCreateDynamicMutable(size_t initial_size, " + "std::function(size_t)> alloc, " + "std::function free, " + "std::function(void*, size_t, size_t)> " + "realloc, ::phaser::Tuning tuning = " + "::phaser::Tuning::kPerformance);\n"; os << " static " << MessageName(message_) << " CreateDynamicMutable(size_t initial_size, " "std::function(size_t)> alloc, " @@ -1605,8 +1612,9 @@ void MessageGenerator::GenerateCreators(std::ostream& os, bool decl) { "}\n\n"; os << "// Create a message in a dynamically resized buffer allocated from " "the heap.\n"; - os << MessageName(message_) << " " << MessageName(message_) - << "::CreateDynamicMutable(size_t initial_size, " + os << "absl::StatusOr<" << MessageName(message_) << "> " + << MessageName(message_) + << "::TryCreateDynamicMutable(size_t initial_size, " "std::function(size_t)> alloc, " "std::function free," "std::function(void*, size_t, size_t)> realloc, " @@ -1615,7 +1623,7 @@ void MessageGenerator::GenerateCreators(std::ostream& os, bool decl) { " absl::StatusOr<::toolbelt::PayloadBuffer *> pbs = " "::phaser::NewDynamicBuffer(initial_size, std::move(alloc), " "std::move(realloc), tuning);\n" - " if (!pbs.ok()) abort();\n" + " if (!pbs.ok()) return pbs.status();\n" " ::toolbelt::PayloadBuffer *pb = *pbs;\n" " ::toolbelt::PayloadBuffer::AllocateMainMessage(&pb, " << MessageName(message_) @@ -1635,6 +1643,19 @@ void MessageGenerator::GenerateCreators(std::ostream& os, bool decl) { " return msg;\n" "}\n\n"; + os << MessageName(message_) << " " << MessageName(message_) + << "::CreateDynamicMutable(size_t initial_size, " + "std::function(size_t)> alloc, " + "std::function free," + "std::function(void*, size_t, size_t)> realloc, " + "::phaser::Tuning tuning) " + "{\n" + " auto message = TryCreateDynamicMutable(initial_size, " + "std::move(alloc), std::move(free), std::move(realloc), tuning);\n" + " if (!message.ok()) abort();\n" + " return std::move(*message);\n" + "}\n\n"; + os << MessageName(message_) << " " << MessageName(message_) << "::CreateDynamicMutable(size_t initial_size = 8192, ::phaser::Tuning " "tuning) {\n"; diff --git a/phaser/phaser_library.bzl b/phaser/phaser_library.bzl index bd86f80..464a357 100644 --- a/phaser/phaser_library.bzl +++ b/phaser/phaser_library.bzl @@ -42,15 +42,18 @@ def _phaser_action( inputs = depset(direct = direct_sources, transitive = transitive_sources) - import_paths = [] + import_paths = {} for s in transitive_sources: for f in s.to_list(): + if f.short_path.startswith("../"): + repository_name = f.short_path.split("/", 2)[1] + import_paths["-Iexternal/" + repository_name] = None if not f.is_source: index = f.path.find("_virtual_imports") if index != -1: # Go to first slash after _virtual_imports/ slash = f.path.find("/", index + 17) - import_paths.append("-I" + f.path[:slash]) + import_paths["-I" + f.path[:slash]] = None plugin, _, plugin_manifests = ctx.resolve_command(tools = [ctx.attr.phaser_plugin]) plugin_arg = "--plugin=protoc-gen-phaser={}".format(ctx.executable.phaser_plugin.path) @@ -59,7 +62,7 @@ def _phaser_action( args.add(plugin_arg) args.add(options_and_out_dir) args.add_all(inputs) - args.add_all(import_paths) + args.add_all(import_paths.keys()) args.add("-I.") ctx.actions.run( @@ -82,6 +85,10 @@ def _to_list(value): def _proto_output_base(source_file): file_path = source_file.short_path + if file_path.startswith("../"): + # Strip Bazel's ..// prefix for external-repository sources. + # Otherwise it escapes target_name and collides across frontends. + file_path = file_path.split("/", 2)[2] if "_virtual_imports" in file_path: # For a file that is not in this package, we need to generate the # output in our package. @@ -97,9 +104,9 @@ def _proto_output_base(source_file): def _skip_phaser_generation(source_file): base = _proto_output_base(source_file) - if base == "google/protobuf/descriptor.proto": + if base.endswith("google/protobuf/descriptor.proto"): return True - if base == "phaser/options.proto": + if base.endswith("phaser/options.proto"): return True return False @@ -146,7 +153,7 @@ def _phaser_impl(ctx): fail("phaser_library frontend must be 'protobuf' or 'ros', got: {}".format(frontend)) outputs = [] - + direct_sources = [] transitive_sources = [] cpp_outputs = [] @@ -170,14 +177,14 @@ def _phaser_impl(ctx): out_file.extension == "h" and out in dep[MessageInfo].symlink_headers ): - prefix = paths.join(ctx.attr.target_name, package_name) - symlink_name = out_file.short_path[len(prefix) + 1:] - if symlink_name.startswith(package_name): - # Header is in our package, remove the package name. - # If the header is outside our package (like google/protobuf/any.h), - # we don't want to create a symlink to it becuase it's in - # the right place already. - symlink_name = symlink_name[len(package_name) + 1:] + package_prefix = ctx.label.package + "/" + symlink_name = out + if not ctx.label.package or symlink_name.startswith(package_prefix): + # Header is in our package, remove the package name. If + # the header is outside our package (like + # google/protobuf/any.h), it is already in the right place. + if package_prefix: + symlink_name = symlink_name[len(package_prefix):] symlink = ctx.actions.declare_file(symlink_name) ctx.actions.symlink(output = symlink, target_file = out_file) dep_outs.append(symlink) @@ -253,6 +260,7 @@ def phaser_library( enable_active_message = False, frontend = "protobuf", cc_deps = [], + copts = [], direct_header_symlinks = True, ros_metadata = False): """ @@ -270,6 +278,7 @@ def phaser_library( frontend: generated C++ API style, either "protobuf" (default) or "ros". cc_deps: additional C++ dependencies required by generated headers, such as ROS1 message/runtime libraries for intrinsic ROS fields. + copts: additional C++ compiler options for generated sources. direct_header_symlinks: create short direct-source header aliases. Disable this when generating multiple frontends from one proto target. ros_metadata: generate ROS datatype, definition, and MD5 functions. @@ -277,14 +286,22 @@ def phaser_library( if frontend not in ("protobuf", "ros"): fail("phaser_library frontend must be 'protobuf' or 'ros', got: {}".format(frontend)) - phaser = name + "_phaser" + output_package_name = native.package_name() + repository_name = native.repository_name() + if repository_name.startswith("@@"): + repository_name = repository_name[2:] + elif repository_name.startswith("@"): + repository_name = repository_name[1:] + if repository_name: + output_package_name = paths.join("external", repository_name, output_package_name) + phaser = name + "_phaser" _phaser_gen( name = phaser, deps = deps, add_namespace = add_namespace, direct_header_symlinks = direct_header_symlinks, - package_name = native.package_name(), + package_name = output_package_name, target_name = name, enable_active_message = enable_active_message, frontend = frontend, @@ -316,6 +333,7 @@ def phaser_library( cc_library( name = name, + copts = copts, srcs = [srcs], hdrs = [hdrs], deps = libdeps, diff --git a/phaser/stress_test.cc b/phaser/stress_test.cc index 70f326f..3dc3e60 100644 --- a/phaser/stress_test.cc +++ b/phaser/stress_test.cc @@ -4,6 +4,7 @@ #include +#include #include #include "phaser/runtime/message.h" @@ -82,6 +83,30 @@ TEST(StressTest, CustomAllocSucceeds) { EXPECT_EQ("custom-alloc", msg.s()); } +TEST(StressTest, TryCreateCustomAllocSucceeds) { + auto result = TestMessage::TryCreateDynamicMutable( + 512, ::phaser::test::AllocUntilLimit(64 * 1024), [](void* p) { free(p); }, + [](void* p, size_t, size_t new_size) -> absl::StatusOr { + void* r = realloc(p, new_size); + if (r == nullptr) { + return absl::ResourceExhaustedError("realloc failed"); + } + return r; + }); + ASSERT_TRUE(result.ok()) << result.status(); + TestMessage msg = std::move(*result); + msg.set_s("fallible-custom-alloc"); + EXPECT_EQ("fallible-custom-alloc", msg.s()); +} + +TEST(StressTest, TryCreateReportsInitialAllocationFailure) { + auto result = TestMessage::TryCreateDynamicMutable( + 512, ::phaser::test::AllocUntilLimit(0), [](void* p) { free(p); }, + ::phaser::test::ReallocAlwaysFails()); + EXPECT_FALSE(result.ok()); + EXPECT_EQ(result.status().code(), absl::StatusCode::kResourceExhausted); +} + TEST(StressTest, ReallocFailureAborts) { EXPECT_DEATH( { From a77b497f099ec6a495fefdb8deee9344a7612979 Mon Sep 17 00:00:00 2001 From: Dave Allison Date: Wed, 19 Aug 2026 16:04:31 -0700 Subject: [PATCH 4/5] Add Finalize() method to set payload length --- phaser/compiler/message_gen.cc | 4 ++++ phaser/stress_test.cc | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/phaser/compiler/message_gen.cc b/phaser/compiler/message_gen.cc index d3b8670..d177d95 100644 --- a/phaser/compiler/message_gen.cc +++ b/phaser/compiler/message_gen.cc @@ -1688,6 +1688,10 @@ void MessageGenerator::GenerateCreators(std::ostream& os, bool decl) { } void MessageGenerator::GenerateSizeFunctions(std::ostream& os) { + os << " void Finalize() const {\n" + " const size_t _phaser_size = Size();\n" + " runtime->pb->full_size = static_cast(_phaser_size);\n" + " }\n"; os << " static constexpr size_t BinarySize() { return HeaderSize() + " << binary_size_ << "; }\n"; os << " static constexpr size_t PresenceMaskSize() { return " diff --git a/phaser/stress_test.cc b/phaser/stress_test.cc index 3dc3e60..e744160 100644 --- a/phaser/stress_test.cc +++ b/phaser/stress_test.cc @@ -62,6 +62,17 @@ TEST(StressTest, DynamicExplicitBufferExpansion) { EXPECT_EQ("expand-me", msg.s()); } +TEST(StressTest, FinalizeSetsPayloadSize) { + TestMessage msg(512); + msg.set_s("finalize-me"); + auto* payload = reinterpret_cast<::toolbelt::PayloadBuffer*>(msg.Data()); + payload->full_size = 0; + + msg.Finalize(); + + EXPECT_EQ(payload->full_size, payload->hwm); +} + TEST(StressTest, AllocFailsAtStart) { auto status = ::phaser::NewDynamicBuffer( 1024, ::phaser::test::AllocUntilLimit(0), From 7a84ec32a2ac803cef08be22303f68fef7812522 Mon Sep 17 00:00:00 2001 From: Dave Allison Date: Wed, 19 Aug 2026 23:23:32 -0700 Subject: [PATCH 5/5] Stage Valgrind suppressions for new tests Ensure the ROS metadata and MD5 tests can start under the shared Valgrind configuration. --- phaser/BUILD.bazel | 2 ++ phaser/runtime/BUILD.bazel | 1 + 2 files changed, 3 insertions(+) diff --git a/phaser/BUILD.bazel b/phaser/BUILD.bazel index 4b7c690..191547f 100644 --- a/phaser/BUILD.bazel +++ b/phaser/BUILD.bazel @@ -81,6 +81,7 @@ cc_test( name = "ros_metadata_protobuf_frontend_test", srcs = ["ros_metadata_protobuf_frontend_test.cc"], copts = PHASER_COPTS, + data = ["valgrind.supp"], deps = [ "//phaser/runtime:phaser_runtime", "//phaser/testdata:ros_metadata_protobuf_phaser", @@ -92,6 +93,7 @@ cc_test( name = "ros_metadata_ros_frontend_test", srcs = ["ros_metadata_ros_frontend_test.cc"], copts = PHASER_COPTS, + data = ["valgrind.supp"], deps = [ "//phaser/runtime:phaser_runtime", "//phaser/testdata:ros_metadata_ros_phaser", diff --git a/phaser/runtime/BUILD.bazel b/phaser/runtime/BUILD.bazel index 0d31d38..aaceb53 100644 --- a/phaser/runtime/BUILD.bazel +++ b/phaser/runtime/BUILD.bazel @@ -41,6 +41,7 @@ cc_test( name = "md5_test", srcs = ["md5_test.cc"], copts = PHASER_COPTS, + data = ["//phaser:valgrind.supp"], deps = [ ":phaser_runtime", "@com_google_googletest//:gtest_main",