diff --git a/CMakeLists.txt b/CMakeLists.txt index 59ef8735..a6c5c418 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,12 +20,6 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) gz_configure_project(VERSION_SUFFIX CONFIG_EXTRAS "gz-msgs-extras.cmake.in") -# Install cmake support files -install( - DIRECTORY cmake/ - DESTINATION "${PROJECT_CMAKE_EXTRAS_INSTALL_DIR}" -) - #============================================================================ # Set project-specific options #============================================================================ @@ -114,16 +108,18 @@ endif() # Find Python find_package(Python3 REQUIRED COMPONENTS Interpreter) -if(USE_SYSTEM_PATHS_FOR_PYTHON_INSTALLATION) - if(USE_DIST_PACKAGES_FOR_PYTHON) - string(REPLACE "site-packages" "dist-packages" GZ_PYTHON_INSTALL_PATH ${Python3_SITELIB}) +if(NOT GZ_PYTHON_INSTALL_PATH) + if(USE_SYSTEM_PATHS_FOR_PYTHON_INSTALLATION) + if(USE_DIST_PACKAGES_FOR_PYTHON) + string(REPLACE "site-packages" "dist-packages" GZ_PYTHON_INSTALL_PATH ${Python3_SITELIB}) + else() + # Python3_SITELIB might use dist-packages in some platforms + string(REPLACE "dist-packages" "site-packages" GZ_PYTHON_INSTALL_PATH ${Python3_SITELIB}) + endif() else() - # Python3_SITELIB might use dist-packages in some platforms - string(REPLACE "dist-packages" "site-packages" GZ_PYTHON_INSTALL_PATH ${Python3_SITELIB}) + # If not a system installation, respect local paths + set(GZ_PYTHON_INSTALL_PATH ${GZ_LIB_INSTALL_DIR}/python) endif() -else() - # If not a system installation, respect local paths - set(GZ_PYTHON_INSTALL_PATH ${GZ_LIB_INSTALL_DIR}/python) endif() #============================================================================ # Configure the build @@ -153,6 +149,22 @@ add_subdirectory(python) #============================================================================ gz_create_packages() +#============================================================================ +# Install cmake extras for downstream users +#============================================================================ +# Necessary to populate generator expressions +file( + GENERATE + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/gz-cmake/gz-msgs-extras.cmake" + INPUT "${CMAKE_CURRENT_BINARY_DIR}/gz-cmake/gz-msgs-extras.cmake" +) + +# Install cmake support files +install( + DIRECTORY cmake/ + DESTINATION "${PROJECT_CMAKE_EXTRAS_INSTALL_DIR}" +) + #============================================================================ # Create documentation #============================================================================ @@ -163,6 +175,7 @@ configure_file(${CMAKE_SOURCE_DIR}/tutorials.md.in ${CMAKE_BINARY_DIR}/tutorials gz_create_docs( API_MAINPAGE_MD "${CMAKE_BINARY_DIR}/api.md" TUTORIALS_MAINPAGE_MD "${CMAKE_BINARY_DIR}/tutorials.md" + IMAGE_PATH_DIRS "${CMAKE_SOURCE_DIR}/tutorials/files" TAGFILES "${GZ-MATH_DOXYGEN_TAGFILE} = ${GZ-MATH_API_URL}" ) diff --git a/Changelog.md b/Changelog.md index eb461c0e..7854ca71 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,10 +1,93 @@ ## Gazebo Msgs 10.x -### Gazebo Msgs 10.0.0 (202x-xx-xx) +### Gazebo Msgs 10.0.0 (2023-09-29) + +1. Add missing `` header + * [Pull request #382](https://github.com/gazebosim/gz-msgs/pull/382) + +1. Documentation fixes + * [Pull request #381](https://github.com/gazebosim/gz-msgs/pull/381) + +1. Generate messages in downstream builds + * [Pull request #339](https://github.com/gazebosim/gz-msgs/pull/339) + * [Pull request #356](https://github.com/gazebosim/gz-msgs/pull/356) + * [Pull request #359](https://github.com/gazebosim/gz-msgs/pull/359) + * [Pull request #361](https://github.com/gazebosim/gz-msgs/pull/361) + * [Pull request #368](https://github.com/gazebosim/gz-msgs/pull/368) + * [Pull request #374](https://github.com/gazebosim/gz-msgs/pull/374) + * [Pull request #377](https://github.com/gazebosim/gz-msgs/pull/377) + * [Pull request #379](https://github.com/gazebosim/gz-msgs/pull/379) + * [Pull request #386](https://github.com/gazebosim/gz-msgs/pull/386) + * [Pull request #384](https://github.com/gazebosim/gz-msgs/pull/384) + +1. Add python message generation + * [Pull request #362](https://github.com/gazebosim/gz-msgs/pull/362) + * [Pull request #364](https://github.com/gazebosim/gz-msgs/pull/364) + +1. Added AirSpeedSensor msgs to Sensor msg + * [Pull request #365](https://github.com/gazebosim/gz-msgs/pull/365) + +1. Add covariance fields to imu message + * [Pull request #333](https://github.com/gazebosim/gz-msgs/pull/333) + +1. Remove ignition + * [Pull request #367](https://github.com/gazebosim/gz-msgs/pull/367) + * [Pull request #335](https://github.com/gazebosim/gz-msgs/pull/335) + +1. Infrastructure + * [Pull request #370](https://github.com/gazebosim/gz-msgs/pull/370) + * [Pull request #369](https://github.com/gazebosim/gz-msgs/pull/369) + * [Pull request #363](https://github.com/gazebosim/gz-msgs/pull/363) + * [Pull request #331](https://github.com/gazebosim/gz-msgs/pull/331) + +1. Added cubemap_uri to sky + * [Pull request #306](https://github.com/gazebosim/gz-msgs/pull/306) + +1. Add id and visibility flags field to projector msg + * [Pull request #345](https://github.com/gazebosim/gz-msgs/pull/345) + +1. Add new discovery types + * [Pull request #322](https://github.com/gazebosim/gz-msgs/pull/322) + +1. ⬆️ Bump main to 10.0.0~pre1 + * [Pull request #292](https://github.com/gazebosim/gz-msgs/pull/292) ## Gazebo Msgs 9.x +### Gazebo Msgs 9.5.0 (2023-08-25) + +1. Remove deprecated flag output_to_genfiles + * [Pull request #347](https://github.com/gazebosim/gz-msgs/pull/347) + +1. GzProtobuf: Do not require version 3 + * [Pull request #346](https://github.com/gazebosim/gz-msgs/pull/346) + +1. Fix typos + * [Pull request #344](https://github.com/gazebosim/gz-msgs/pull/344) + * [Pull request #314](https://github.com/gazebosim/gz-msgs/pull/314) + +1. Few clangtidy fixes + * [Pull request #343](https://github.com/gazebosim/gz-msgs/pull/343) + +1. Remove unused load in gz_msgs_generate + * [Pull request #342](https://github.com/gazebosim/gz-msgs/pull/342) + +1. Remove unused ignstrtok variable + * [Pull request #340](https://github.com/gazebosim/gz-msgs/pull/340) + +1. Adds a message that allows loading environments via a topic + * [Pull request #320](https://github.com/gazebosim/gz-msgs/pull/320) + +1. Rename COPYING to LICENSE + * [Pull request #330](https://github.com/gazebosim/gz-msgs/pull/330) + +1. Infrastructure + * [Pull request #370](https://github.com/gazebosim/gz-msgs/pull/370) + * [Pull request #369](https://github.com/gazebosim/gz-msgs/pull/369) + * [Pull request #331](https://github.com/gazebosim/gz-msgs/pull/331) + + ### Gazebo Msgs 9.4.0 (2023-03-10) 1. Support for bazel in garden diff --git a/README.md b/README.md index 16f15e02..2d6b2346 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ framework, a set of libraries designed to rapidly develop robot applications. ## Installation -See the [installation tutorial](https://gazebosim.org/api/msgs/7.0/install.html). +See the [installation tutorial](https://gazebosim.org/api/msgs/10/install.html). ## Known issue of command line tools @@ -29,7 +29,7 @@ line tools from `gz-tools` may not work correctly. A workaround for a single package is to define the environment variable `GZ_CONFIG_PATH` to point to the location of the Gazebo library installation, where the YAML file for the package is found, such as -``` +```{.sh} export GZ_CONFIG_PATH=/usr/local/share/gz ``` @@ -38,7 +38,7 @@ installations from source are in different locations, only one can be specified. Another workaround for working with multiple Gazebo libraries on the command line is using symbolic links to each library's YAML file. -``` +```{.sh} mkdir ~/.gz/tools/configs -p cd ~/.gz/tools/configs/ ln -s /usr/local/share/gz/fuel8.yaml . diff --git a/cmake/gz_msgs_generate.cmake b/cmake/gz_msgs_generate.cmake index 366ca11c..679b9cae 100644 --- a/cmake/gz_msgs_generate.cmake +++ b/cmake/gz_msgs_generate.cmake @@ -8,6 +8,7 @@ # FACTORY_GEN_SCRIPT - Location of the factory generator script # PROTO_PATH - Base directory of the proto files # DEPENDENCY_DESCRIPTIONS - .gz_desc files for each dependency +# DLLEXPORT_DECL - Visibilty macro to apply to messages # OUTPUT_DIRECTORY - CMake binary directoy to place generated artifacts # OUTPUT_SOURCES - Variable to contain list of generated source files # OUTPUT_HEADERS - Variable to contain list of generated header files @@ -21,6 +22,7 @@ function(gz_msgs_generate_messages_impl) # Inputs PROTO_PACKAGE MSGS_GEN_SCRIPT GZ_PROTOC_PLUGIN FACTORY_GEN_SCRIPT PROTO_PATH DEPENDENCY_DESCRIPTIONS + DLLEXPORT_DECL OUTPUT_DIRECTORY # Outputs OUTPUT_SOURCES @@ -57,6 +59,8 @@ function(gz_msgs_generate_messages_impl) # Cpp Specific arguments GENERATE_CPP + DLLEXPORT_DECL + ${generate_messages_DLLEXPORT_DECL} OUTPUT_CPP_HH_VAR ${generate_messages_OUTPUT_HEADERS} OUTPUT_DETAIL_CPP_HH_VAR diff --git a/cmake/gz_msgs_protoc.cmake b/cmake/gz_msgs_protoc.cmake index d929cf8b..077e266c 100644 --- a/cmake/gz_msgs_protoc.cmake +++ b/cmake/gz_msgs_protoc.cmake @@ -8,6 +8,7 @@ # PROTO_PACKAGE - Protobuf package the file belongs to (e.g. "gz.msgs") # PROTOC_EXEC - Path to protoc # GZ_PROTOC_PLUGIN - Path to the gazebo-specific protobuf compiler executable +# DLLEXPORT_DECL - DLL visibility macro (eg GZ_MSGS_VISIBLE) # INPUT_PROTO - Path to the input .proto file # OUTPUT_CPP_DIR - Path where C++ files are saved # OUTPUT_PYTON_DIR - Path where Python files are saved @@ -25,6 +26,7 @@ function(gz_msgs_protoc) PROTO_PACKAGE PROTOC_EXEC GZ_PROTOC_PLUGIN + DLLEXPORT_DECL INPUT_PROTO OUTPUT_CPP_DIR OUTPUT_CPP_HH_VAR @@ -96,6 +98,12 @@ function(gz_msgs_protoc) ) endif() + if(gz_msgs_protoc_DLLEXPORT_DECL) + list(APPEND GENERATE_ARGS + --dllexport-decl "${gz_msgs_protoc_DLLEXPORT_DECL}" + ) + endif() + if(${gz_msgs_protoc_GENERATE_CPP}) list(APPEND GENERATE_ARGS --generate-cpp diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 3227d900..2bbe9114 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -35,6 +35,8 @@ gz_msgs_generate_messages_impl( $ INPUT_PROTOS ${proto_files} + DLLEXPORT_DECL + "GZ_MSGS_VISIBLE" PROTO_PACKAGE "gz.msgs" PROTO_PATH diff --git a/core/include/gz/msgs/PointCloudPackedUtils.hh b/core/include/gz/msgs/PointCloudPackedUtils.hh index f746f8c0..37074aaf 100644 --- a/core/include/gz/msgs/PointCloudPackedUtils.hh +++ b/core/include/gz/msgs/PointCloudPackedUtils.hh @@ -24,6 +24,7 @@ #include #include +#include #include #include #include diff --git a/core/include/gz/msgs/convert/Pose.hh b/core/include/gz/msgs/convert/Pose.hh index b9ff158c..73efa7e9 100644 --- a/core/include/gz/msgs/convert/Pose.hh +++ b/core/include/gz/msgs/convert/Pose.hh @@ -40,8 +40,17 @@ inline void Set(gz::msgs::Pose *_msg, const gz::math::Pose3d &_data) inline void Set(gz::math::Pose3d *_data, const gz::msgs::Pose &_msg) { - auto pos = Convert(_msg.position()); - auto orientation = Convert(_msg.orientation()); + gz::math::Vector3d pos; + gz::math::Quaterniond orientation; + + if (_msg.has_position()) + pos = Convert(_msg.position()); + + // This bit is critical. If orientation hasn't been set in the message, + // then we want the quaternion to default to identity. + if (_msg.has_orientation()) + orientation = Convert(_msg.orientation()); + _data->Set(pos, orientation); } diff --git a/core/src/DynamicFactory.cc b/core/src/DynamicFactory.cc index b497500b..a8a7dac3 100644 --- a/core/src/DynamicFactory.cc +++ b/core/src/DynamicFactory.cc @@ -113,6 +113,16 @@ void DynamicFactory::LoadDescriptors(const std::string &_paths) for (const google::protobuf::FileDescriptorProto &fileDescriptorProto : fileDescriptorSet.file()) { + // If the descriptor already exists in the database, then skip it. + // This may happen as gz_desc files can potentially contain the + // transitive message definitions + google::protobuf::FileDescriptorProto checkDescriptorProto; + if (this->db.FindFileByName( + fileDescriptorProto.name(), &checkDescriptorProto)) + { + continue; + } + if (!static_cast(pool.BuildFile(fileDescriptorProto))) { std::cerr << "DynamicFactory(). Unable to place descriptors from [" diff --git a/core/src/DynamicFactory.hh b/core/src/DynamicFactory.hh index 342f3edb..afe4c4c4 100644 --- a/core/src/DynamicFactory.hh +++ b/core/src/DynamicFactory.hh @@ -30,6 +30,7 @@ #pragma warning(pop) #endif +#include #include #include #include diff --git a/core/src/MessageFactory.cc b/core/src/MessageFactory.cc index 3c16b772..45743dc1 100644 --- a/core/src/MessageFactory.cc +++ b/core/src/MessageFactory.cc @@ -28,6 +28,8 @@ #include "gz/msgs/MessageFactory.hh" #include +static constexpr const char * kGzMsgsPrefix = "gz.msgs."; + namespace gz::msgs { @@ -53,35 +55,57 @@ MessageFactory::MessagePtr MessageFactory::New( { std::string type; - // Convert "gz.msgs." to "gz_msgs.". + // Convert "gz_msgs." prefix if (_msgType.find("gz_msgs.") == 0) { - type = "gz.msgs." + _msgType.substr(8); + type = kGzMsgsPrefix + _msgType.substr(8); } + // Convert ".gz_msgs." prefix else if (_msgType.find(".gz_msgs.") == 0) { - type = "gz.msgs." + _msgType.substr(9); + type = kGzMsgsPrefix + _msgType.substr(9); } - // Convert ".gz.msgs." to "gz_msgs.". + // Convert ".gz.msgs." prefix else if (_msgType.find(".gz.msgs.") == 0) { - type = "gz.msgs." + _msgType.substr(9); + type = kGzMsgsPrefix + _msgType.substr(9); } else { type = _msgType; } - if (msgMap.find(type) != msgMap.end()) + auto getMessagePtr = [this](const std::string &_type) { - // Create a new message if a FactoryFn has been assigned to the message type - return msgMap[type](); - } - else + MessageFactory::MessagePtr ret = nullptr; + if (msgMap.find(_type) != msgMap.end()) + { + // Create a new message via FactoryFn + ret = msgMap[_type](); + } + else + { + // Create a new message via dynamic descriptors + ret = dynamicFactory->New(_type); + } + return ret; + }; + + auto ret = getMessagePtr(type); + + // Message was not found in either static or dynamic message types, + // try again adding the gz.msgs prefix + if (nullptr == ret) { - // Check if we have the message descriptor. - return dynamicFactory->New(type); + ret = getMessagePtr(kGzMsgsPrefix + type); + if (nullptr != ret) + { + std::cerr << "Message (" << kGzMsgsPrefix + type + << ") was retrieved with non-fully qualified name. " + << "This behavior is deprecated in msgs10" << std::endl; + } } + return ret; } ///////////////////////////////////////////////// @@ -91,7 +115,12 @@ MessageFactory::MessagePtr MessageFactory::New( std::unique_ptr msg = New(_msgType); if (msg) { - google::protobuf::TextFormat::ParseFromString(_args, msg.get()); + if (!google::protobuf::TextFormat::ParseFromString(_args, msg.get())) + { + // The user-provided string was invalid, + // return nullptr rather than an empty message. + msg.reset(); + } } return msg; } diff --git a/examples/generating_custom_msgs/CMakeLists.txt b/examples/generating_custom_msgs/CMakeLists.txt index 79e6eecd..5aad3e6f 100644 --- a/examples/generating_custom_msgs/CMakeLists.txt +++ b/examples/generating_custom_msgs/CMakeLists.txt @@ -15,7 +15,10 @@ set(GZ_MSGS_VER ${gz-msgs10_VERSION_MAJOR}) # Example of custom messages that depend on gz.msgs set(MSGS_PROTOS - ${CMAKE_CURRENT_SOURCE_DIR}/proto/gz/custom_msgs/vector3d.proto) + ${CMAKE_CURRENT_SOURCE_DIR}/proto/gz/custom_msgs/foo.proto + ${CMAKE_CURRENT_SOURCE_DIR}/proto/gz/custom_msgs/bar.proto + ${CMAKE_CURRENT_SOURCE_DIR}/proto/gz/custom_msgs/baz.proto +) gz_msgs_generate_messages( # The cmake target to be generated for libraries/executables to link @@ -32,7 +35,6 @@ gz_msgs_generate_messages( add_executable(${PROJECT_NAME} main.cc) -# Automatically uses whole-archive linking to get all the messages available target_link_libraries(${PROJECT_NAME} PUBLIC ${PROJECT_NAME}-msgs) install(TARGETS ${PROJECT_NAME} diff --git a/examples/generating_custom_msgs/README.md b/examples/generating_custom_msgs/README.md index bf72be2e..ef06b099 100644 --- a/examples/generating_custom_msgs/README.md +++ b/examples/generating_custom_msgs/README.md @@ -57,10 +57,3 @@ gz_msgs_generate_messages( # List of message targets this library imports from DEPENDENCIES gz_msgs_gen) ``` - -Be sure to link all dependent message targets: - -``` -# Automatically uses whole-archive linking to get all the messages available -target_link_messages(TARGET ${PROJECT_NAME} PUBLIC MSG_TARGETS custom_msgs_gen gz_msgs_gen) -``` diff --git a/examples/generating_custom_msgs/main.cc b/examples/generating_custom_msgs/main.cc index b123770c..665a655b 100644 --- a/examples/generating_custom_msgs/main.cc +++ b/examples/generating_custom_msgs/main.cc @@ -1,34 +1,68 @@ -#include -#include +#include +#include +#include #include // A simple example that demonstrates the use of the message factory // -// Usage: -// Print text description of original and custom Vector3d msgs. // ./generating_custom_messages int main(int argc, char** argv) { (void) argc; (void) argv; - // Print the text description of the original message - gz::msgs::Vector3d original; + + gz::custom_msgs::BazStamped msg; + + // msg has header and baz field + auto *header = msg.mutable_header(); + auto *baz = msg.mutable_baz(); + { - auto descriptor = original.GetDescriptor(); - auto fileDescriptor = descriptor->file(); - std::cout << "Name: " << descriptor->full_name() << std::endl; - std::cout << "File: " << fileDescriptor->name() << std::endl << std::endl; - std::cout << descriptor->DebugString() << std::endl; + // Populate the header with something + header->mutable_stamp()->set_sec(100); + header->mutable_stamp()->set_nsec(100); + } + + { + // Add a frame_id to the header + auto map_entry = header->add_data(); + map_entry->set_key("frame_id"); + map_entry->add_value("gz_custom_msgs"); } - // Print the text description of the custom message - gz::custom_msgs::Vector3d custom; { - auto descriptor = custom.GetDescriptor(); + // Add an arbitrary array value to the header + auto map_entry = header->add_data(); + map_entry->set_key("array"); + map_entry->add_value("a"); + map_entry->add_value("b"); + map_entry->add_value("c"); + } + + // baz has foo and bar field; + auto *foo = baz->mutable_foo(); + auto *bar = baz->mutable_bar(); + + { + // Set the values of our custom sub-messges + foo->set_value(1.0); + bar->set_value(1.0); + } + + { + // Print the text descriptor of a message + auto descriptor = msg.GetDescriptor(); auto fileDescriptor = descriptor->file(); + std::cout << "Message definition: " << std::endl; std::cout << "Name: " << descriptor->full_name() << std::endl; std::cout << "File: " << fileDescriptor->name() << std::endl << std::endl; std::cout << descriptor->DebugString() << std::endl; } + + { + // Print the populated values of a message + std::cout << "===============================" << std::endl; + std::cout << "Pouplated Message: \n" << msg.DebugString() << std::endl; + } } diff --git a/examples/generating_custom_msgs/proto/gz/custom_msgs/bar.proto b/examples/generating_custom_msgs/proto/gz/custom_msgs/bar.proto new file mode 100644 index 00000000..b839f6e2 --- /dev/null +++ b/examples/generating_custom_msgs/proto/gz/custom_msgs/bar.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; +package gz.custom_msgs; + +import "gz/msgs/header.proto"; + +message Bar +{ + double value = 1; +} + +message BarStamped +{ + gz.msgs.Header header = 1; + gz.custom_msgs.Bar bar = 2; +} diff --git a/examples/generating_custom_msgs/proto/gz/custom_msgs/baz.proto b/examples/generating_custom_msgs/proto/gz/custom_msgs/baz.proto new file mode 100644 index 00000000..5802af40 --- /dev/null +++ b/examples/generating_custom_msgs/proto/gz/custom_msgs/baz.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; +package gz.custom_msgs; + +import "gz/msgs/header.proto"; + +import "gz/custom_msgs/foo.proto"; +import "gz/custom_msgs/bar.proto"; + +message Baz +{ + gz.custom_msgs.Foo foo = 1; + gz.custom_msgs.Bar bar = 2; +} + +message BazStamped +{ + gz.msgs.Header header = 1; + gz.custom_msgs.Baz baz = 2; +} diff --git a/examples/generating_custom_msgs/proto/gz/custom_msgs/foo.proto b/examples/generating_custom_msgs/proto/gz/custom_msgs/foo.proto new file mode 100644 index 00000000..0d3b36f1 --- /dev/null +++ b/examples/generating_custom_msgs/proto/gz/custom_msgs/foo.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; +package gz.custom_msgs; + +import "gz/msgs/header.proto"; + +message Foo +{ + double value = 1; +} + +message FooStamped +{ + gz.msgs.Header header = 1; + gz.custom_msgs.Foo foo = 2; +} diff --git a/examples/generating_custom_msgs/proto/gz/custom_msgs/vector3d.proto b/examples/generating_custom_msgs/proto/gz/custom_msgs/vector3d.proto deleted file mode 100644 index 61c356b0..00000000 --- a/examples/generating_custom_msgs/proto/gz/custom_msgs/vector3d.proto +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2023 Open Source Robotics Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * -*/ - -syntax = "proto3"; -package gz.custom_msgs; - -import "gz/msgs/header.proto"; - -/// An example of a custom Vector3d with fields a, b, and c -message Vector3d -{ - /// \brief Optional header data - gz.msgs.Header header = 1; - - double a = 2; - double b = 3; - double c = 4; -} diff --git a/gz-msgs-extras.cmake.in b/gz-msgs-extras.cmake.in index 1842ebda..914f779a 100644 --- a/gz-msgs-extras.cmake.in +++ b/gz-msgs-extras.cmake.in @@ -23,7 +23,7 @@ include(${@PROJECT_NAME@_DIR}/gz_msgs_generate.cmake) set(@PROJECT_NAME@_INSTALL_PATH "${@PROJECT_NAME@_DIR}/@PROJECT_CMAKE_EXTRAS_PATH_TO_PREFIX@") cmake_path(NORMAL_PATH @PROJECT_NAME@_INSTALL_PATH OUTPUT_VARIABLE @PROJECT_NAME@_INSTALL_PATH) -set(PROTOC_NAME "@PROJECT_NAME@_protoc_plugin") +set(PROTOC_NAME "$") set(PROTO_SCRIPT_NAME "@PROJECT_NAME@_generate.py") set(FACTORY_SCRIPT_NAME "@PROJECT_NAME@_generate_factory.py") diff --git a/test/integration/Factory_TEST.cc b/test/integration/Factory_TEST.cc index 63cabc33..7f8a08fe 100644 --- a/test/integration/Factory_TEST.cc +++ b/test/integration/Factory_TEST.cc @@ -25,23 +25,25 @@ #include "gz/msgs/serialized_map.pb.h" #include "gz/msgs/Factory.hh" -using namespace gz; - namespace { -static constexpr const char * kMsgsTestPath = GZ_MSGS_TEST_PATH; +constexpr const char * kMsgsTestPath = GZ_MSGS_TEST_PATH; #ifdef _WIN32 -static constexpr char kEnvironmentVariableSeparator = ';'; +constexpr char kEnvironmentVariableSeparator = ';'; #else -static constexpr char kEnvironmentVariableSeparator = ':'; +constexpr char kEnvironmentVariableSeparator = ':'; #endif } // namespace + +using Factory = gz::msgs::Factory; +using Vector3d = gz::msgs::Vector3d; + ///////////////////////////////////////////////// TEST(FactoryTest, Type) { std::vector types; - msgs::Factory::Types(types); + Factory::Types(types); EXPECT_FALSE(types.empty()); EXPECT_TRUE(std::find(types.begin(), types.end(), std::string("gz.msgs.Vector3d")) != @@ -51,44 +53,74 @@ TEST(FactoryTest, Type) ///////////////////////////////////////////////// TEST(FactoryTest, New) { - auto msg = msgs::Factory::New("gz_msgs.Vector3d"); - + // Preferred call, using periods, fully qualified + auto msg = Factory::New("gz.msgs.Vector3d"); ASSERT_TRUE(msg.get() != nullptr); - msg->set_x(1.0); - msg->set_y(2.0); - msg->set_z(3.0); - - auto msgFilled = msgs::Factory::New( - "gz_msgs.Vector3d", "x: 1.0, y: 2.0, z: 3.0"); - ASSERT_TRUE(msgFilled.get() != nullptr); - - EXPECT_DOUBLE_EQ(msg->x(), msgFilled->x()); - EXPECT_DOUBLE_EQ(msg->y(), msgFilled->y()); - EXPECT_DOUBLE_EQ(msg->z(), msgFilled->z()); + // Various other supported ways of specifying gz.msgs + msg = Factory::New("gz_msgs.Vector3d"); + EXPECT_TRUE(msg.get() != nullptr); - msg = msgs::Factory::New("gz.msgs.Vector3d"); + msg = Factory::New(".gz.msgs.Vector3d"); EXPECT_TRUE(msg.get() != nullptr); - msg = msgs::Factory::New(".gz.msgs.Vector3d"); + msg = Factory::New(".gz_msgs.Vector3d"); EXPECT_TRUE(msg.get() != nullptr); } ///////////////////////////////////////////////// -TEST(FactoryTest, NewDynamicFactory) +TEST(FactoryTest, NewWithWellFormedData) { - std::string paths; + gz::msgs::Vector3d msg; + msg.set_x(1.0); + msg.set_y(2.0); + msg.set_z(3.0); + + auto msgFilled = Factory::New( + "gz.msgs.Vector3d", "x: 1.0, y: 2.0, z: 3.0"); + ASSERT_TRUE(nullptr != msgFilled); + + EXPECT_DOUBLE_EQ(msg.x(), msgFilled->x()); + EXPECT_DOUBLE_EQ(msg.y(), msgFilled->y()); + EXPECT_DOUBLE_EQ(msg.z(), msgFilled->z()); +} + - auto msg = msgs::Factory::New("example.msgs.StringMsg"); +///////////////////////////////////////////////// +TEST(FactoryTest, NewWithMalformedData) +{ + /// Passing bad data to New results in a nullptr + auto msgFilled = Factory::New( + "gz.msgs.Vector3d", "x: 1.0, y: asdf, z: 3.0"); + ASSERT_TRUE(nullptr == msgFilled); +} + +///////////////////////////////////////////////// +TEST(FactoryTest, DeprecatedNonFullyQualified) +{ + auto msg = Factory::New("StringMsg"); + EXPECT_TRUE(msg.get() != nullptr); +} + +///////////////////////////////////////////////// +TEST(FactoryTest, DynamicMessageNoEnv) +{ + // Message won't be found because descriptors aren't loaded + auto msg = Factory::New("example.msgs.StringMsg"); EXPECT_TRUE(msg.get() == nullptr); +} +///////////////////////////////////////////////// +TEST(FactoryTest, DynamicMessageWithEnv) +{ + std::string paths; std::filesystem::path test_path(kMsgsTestPath); paths += (test_path / "desc").string(); paths += kEnvironmentVariableSeparator; paths += test_path.string(); - msgs::Factory::LoadDescriptors(paths); - msg = msgs::Factory::New("example.msgs.StringMsg"); + Factory::LoadDescriptors(paths); + auto msg = Factory::New("example.msgs.StringMsg"); EXPECT_TRUE(msg.get() != nullptr); } @@ -96,12 +128,12 @@ TEST(FactoryTest, NewDynamicFactory) TEST(FactoryTest, NewAllRegisteredTypes) { std::vector types; - msgs::Factory::Types(types); + Factory::Types(types); EXPECT_FALSE(types.empty()); for (const auto &type : types) { - auto msg = msgs::Factory::New(type); + auto msg = Factory::New(type); ASSERT_NE(nullptr, msg.get()) << type; } } @@ -117,69 +149,69 @@ TEST(FactoryTest, MultipleMessagesInAProto) }; std::vector types; - msgs::Factory::Types(types); + Factory::Types(types); EXPECT_FALSE(types.empty()); - for (auto type : typesInSameFile) + for (const auto *type : typesInSameFile) { // Check all types are registered EXPECT_NE(std::find(types.begin(), types.end(), std::string(type)), types.end()) << type; // Check all types can be newed - auto msg = msgs::Factory::New(type); + auto msg = Factory::New(type); EXPECT_NE(nullptr, msg.get()) << type; } // Compile-time check that pointer types exist { - msgs::SerializedEntityMapUniquePtr ptr{nullptr}; + gz::msgs::SerializedEntityMapUniquePtr ptr{nullptr}; EXPECT_EQ(nullptr, ptr); } { - msgs::ConstSerializedEntityMapUniquePtr ptr{nullptr}; + gz::msgs::ConstSerializedEntityMapUniquePtr ptr{nullptr}; EXPECT_EQ(nullptr, ptr); } { - msgs::SerializedEntityMapSharedPtr ptr{nullptr}; + gz::msgs::SerializedEntityMapSharedPtr ptr{nullptr}; EXPECT_EQ(nullptr, ptr); } { - msgs::ConstSerializedEntityMapSharedPtr ptr{nullptr}; + gz::msgs::ConstSerializedEntityMapSharedPtr ptr{nullptr}; EXPECT_EQ(nullptr, ptr); } { - msgs::SerializedStateMapUniquePtr ptr{nullptr}; + gz::msgs::SerializedStateMapUniquePtr ptr{nullptr}; EXPECT_EQ(nullptr, ptr); } { - msgs::ConstSerializedStateMapUniquePtr ptr{nullptr}; + gz::msgs::ConstSerializedStateMapUniquePtr ptr{nullptr}; EXPECT_EQ(nullptr, ptr); } { - msgs::SerializedStateMapSharedPtr ptr{nullptr}; + gz::msgs::SerializedStateMapSharedPtr ptr{nullptr}; EXPECT_EQ(nullptr, ptr); } { - msgs::ConstSerializedStateMapSharedPtr ptr{nullptr}; + gz::msgs::ConstSerializedStateMapSharedPtr ptr{nullptr}; EXPECT_EQ(nullptr, ptr); } { - msgs::SerializedStepMapUniquePtr ptr{nullptr}; + gz::msgs::SerializedStepMapUniquePtr ptr{nullptr}; EXPECT_EQ(nullptr, ptr); } { - msgs::ConstSerializedStepMapUniquePtr ptr{nullptr}; + gz::msgs::ConstSerializedStepMapUniquePtr ptr{nullptr}; EXPECT_EQ(nullptr, ptr); } { - msgs::SerializedStepMapSharedPtr ptr{nullptr}; + gz::msgs::SerializedStepMapSharedPtr ptr{nullptr}; EXPECT_EQ(nullptr, ptr); } { - msgs::ConstSerializedStepMapSharedPtr ptr{nullptr}; + gz::msgs::ConstSerializedStepMapSharedPtr ptr{nullptr}; EXPECT_EQ(nullptr, ptr); } } diff --git a/test/integration/Utility_TEST.cc b/test/integration/Utility_TEST.cc index 890fc6ce..8c40d89f 100644 --- a/test/integration/Utility_TEST.cc +++ b/test/integration/Utility_TEST.cc @@ -122,6 +122,23 @@ TEST(UtilityTest, ConvertMsgPoseToMath) EXPECT_TRUE(math::equal(v.Rot().W(), 0.27059805007309851)); } +///////////////////////////////////////////////// +TEST(UtilityTest, ConvertMsgPoseToMathIdentity) +{ + // Setting position but not orientation should still + // result in a valid identity quaternion + msgs::Pose msg; + msg.mutable_position()->set_x(1.0); + msg.mutable_position()->set_y(2.0); + msg.mutable_position()->set_z(2.0); + + math::Pose3d v = msgs::Convert(msg); + EXPECT_TRUE(math::equal(v.Rot().W(), 1.0)); + EXPECT_TRUE(math::equal(v.Rot().X(), 0.0)); + EXPECT_TRUE(math::equal(v.Rot().Y(), 0.0)); + EXPECT_TRUE(math::equal(v.Rot().Z(), 0.0)); +} + ///////////////////////////////////////////////// TEST(MsgsTest, ConvertMathColorToMsgs) { diff --git a/tools/gz_msgs_generate.py b/tools/gz_msgs_generate.py index 35f4e590..1805e2f3 100755 --- a/tools/gz_msgs_generate.py +++ b/tools/gz_msgs_generate.py @@ -57,6 +57,9 @@ def main(argv=sys.argv[1:]): '--dependency-proto-descs', nargs='*', help='The location of proto descriptor files these messages depend on') + parser.add_argument( + '--dllexport-decl', + help='The DLL visibility macro to use, if not set, no macro will be used') args = parser.parse_args(argv) # First generate the base cpp files @@ -71,7 +74,11 @@ def main(argv=sys.argv[1:]): if args.generate_cpp: cmd += [f'--plugin=protoc-gen-gzmsgs={args.gz_generator_bin}'] - cmd += [f'--cpp_out=dllexport_decl=GZ_MSGS_VISIBLE:{args.output_cpp_path}'] + if args.dllexport_decl: + cmd += [f'--cpp_out=dllexport_decl={args.dllexport_decl}:{args.output_cpp_path}'] + else: + cmd += [f'--cpp_out={args.output_cpp_path}'] + cmd += [f'--gzmsgs_out={args.output_cpp_path}'] if args.generate_python: cmd += [f'--python_out={args.output_python_path}'] diff --git a/tutorials.md.in b/tutorials.md.in index f7ce0c62..2eafe332 100644 --- a/tutorials.md.in +++ b/tutorials.md.in @@ -8,6 +8,7 @@ Gazebo @GZ_DESIGNATION_CAP@ library and how to use the library effectively. 1. \subpage install "Installation" 2. \subpage cppgetstarted "C++ Get Started" +3. \subpage messagegeneration "Message Generation" ## License diff --git a/tutorials/files/gz_msgs_factory.svg b/tutorials/files/gz_msgs_factory.svg new file mode 100644 index 00000000..b0357bff --- /dev/null +++ b/tutorials/files/gz_msgs_factory.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tutorials/files/gz_msgs_protoc.svg b/tutorials/files/gz_msgs_protoc.svg new file mode 100644 index 00000000..e5882b37 --- /dev/null +++ b/tutorials/files/gz_msgs_protoc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tutorials/message_generation.md b/tutorials/message_generation.md new file mode 100644 index 00000000..ab3a420e --- /dev/null +++ b/tutorials/message_generation.md @@ -0,0 +1,284 @@ +\page messagegeneration Message Generation + +## Overview + +This tutorial describes how Gazebo messages are generated and you can create +custom messages that can be used with the simulator and command line tools. + +Gazebo messages use [Protobuf](https://protobuf.dev) to define the structures +that can be easily serialized for use in communication through the Gazebo +software stack. Protobuf is a language-neutral framework for serializing +structured data, with the advantage of generating native language bindings +so that messages are easily used from your language of choice. + +## Message Definitions + +### File structure + +Gazebo message definitions are stored in the +[proto](https://github.com/gazebosim/gz-msgs/tree/gz-msgs10/proto) folder. + +Messages may additionally belong to a `package`, which allows for convenient +namespacing or grouping of common messages. +Currently, all `gz-msgs` definitions reside in the `gz.msgs` package, which +generates the corresponding `gz::msgs` namespace in C++ and `gz.msgs` in +Python. + +### Proto files + +Messages are defined in files with the extension `.proto`. + +The definitions use the `proto` language, which has two major version, of which +Gazebo uses the `proto3` version. + +The full language guide for the `proto` language is available [here](https://protobuf.dev/programming-guides/proto3/) + +A typical message definition looks something like this: + +```{proto} +/// First non-comment line must define using the proto3 syntax. +syntax = "proto3"; + +/// Define the package name we are using +package gz.msgs; + +/// Import common message definitions +import "gz/msgs/header.proto"; +import "gz/msgs/vector3d.proto"; + +message FooMessage +{ + /// Message header data (timestamp) + /// Since this definition is also in gz.msgs, the fully-qualified + /// name isn't necessary here, but left in for explicitness. + gz.msgs.Header header = 1; + + /// Define a field using one of the protobuf scalar types + string name = 2; + + /// Define a field using on the Gazebo message types + gz.msgs.Vector3d position = 3; +} +``` + +Note that each field has a corresponding *field number*. +Each field must be given an ID between `1` and `536,870,911`, with the following +restrctions (from the [language guide](https://protobuf.dev/programming-guides/proto3/#assigning)): + +* The given number must be unique among all fields for that message. +* Field numbers `19,000` to `19,999` are reserved for the Protocol Buffers implementation. + The protocol buffer compiler will complain if you use one of these reserved field numbers in your message. +* You cannot use any previously reserved field numbers or any field numbers that have been allocated to extensions. + + + +## The Message Generation Pipeline + +Once the proto messages have been defined, there are several steps that take +place to make the messages usable across the Gazebo stack. + +To begin with, each message definition is passed through the `gz_msgs_protoc` +function: + +\image html files/gz_msgs_protoc.svg + +For each file: + +* `gz_msgs_protoc` accepts the arguments necessary to find all the supporting +scripts and executables: + * `gz_msgs_generate.py` - Top-level python script entrypoint for generating messages. + * `protoc` - The protobuf compiler + * `gz-msgs-protoc-compiler` - The Gazebo messages extension compiler. +* The protobuf compiler generates `.pb.c` , `.pb.h`, and `_pb2.py` file + for each message found in the `.proto` file. +* The generation python script moves the generated `.pb.h` file into a + `details/` directory to hide some of the protobuf implementation from + downstream users. +* The `gz-msgs-protoc-compiler` generates a new `.pb.h` file with Gazebo-specific definitions +* `gz_msgs_generate.py` additionally generates a `.pbindex` file to be used by later + steps in the process. + + +After individual message files have been generated, all of the definitions +are grouped and processed via `gz_msgs_factory` + +\image html files/gz_msgs_factory.svg + +For the collection, `gz_msgs_factory` generates: + +* A `register.cc` file that can be used to statically register all of the + messages passed as arguments. +* A `MessageTypes.hh` file that enumerates all messages available in the + collection. +* A `.gz_desc` file that allows the messages to be dynamically loaded by the + various Gazebo tools. + + +## Custom Message Generation + +Now that we understand the components of the message generation pipeline, +we can use them in our own custom package. + +The `cmake` functionality is exported from the `gz-msgs` library, via the `gz-cmake` [`extras` functionality](https://github.com/gazebosim/gz-cmake/pull/345). +To make the functions available, simply `find_package(gz-msgs10)` in your `CMakeLists.txt`: + +```cmake +cmake_minimum_required(VERSION 3.10.2 FATAL_ERROR) +project(my_custom_package VERSION 0.0.1) +find_package(gz-cmake3 REQUIRED) +find_package(gz-msgs10 REQUIRED) +``` + +Next, create a directory for your custom message definitions: + +```sh +mkdir -p proto/gz/custom_msgs +touch proto/gz/custom_msgs/foo.proto +touch proto/gz/custom_msgs/bar.proto +touch proto/gz/custom_msgs/baz.proto +``` + +Give the message files some content: + +`proto/gz/custom_msgs/foo.proto`: + +```proto +syntax = "proto3"; +package gz.custom_msgs; + +message Foo +{ + double value = 1; +} +``` + +`proto/gz/custom_msgs/bar.proto`: + +```proto +syntax = "proto3"; +package gz.custom_msgs; + +message Bar +{ + double value = 1; +} +``` + +`proto/gz/custom_msgs/baz.proto`: + +```proto +syntax = "proto3"; +package gz.custom_msgs; + +import "gz/msgs/header.proto"; + +import "gz/custom_msgs/foo.proto"; +import "gz/custom_msgs/bar.proto"; + +message Baz +{ + gz.custom_msgs.Foo foo = 1; + gz.custom_msgs.Bar bar = 2; +} + +message BazStamped +{ + gz.msgs.Header header = 1; + gz.custom_msgs.Baz baz = 2; +} +``` + + +Then, back in the `CMakeLists.txt` file, generate the message library. + +```cmake +gz_msgs_generate_messages( + # The cmake target to be generated for libraries/executables to link + TARGET msgs + # The protobuf package to generate (Typically based on the path) + PROTO_PACKAGE "gz.custom_msgs" + # The path to the base directory of the proto files + # All import paths should be relative to this (eg gz/custom_msgs/vector3d.proto) + MSGS_PATH ${CMAKE_CURRENT_SOURCE_DIR}/proto + # List of proto files to generate + MSGS_PROTOS ${MSGS_PROTOS} + DEPENDENCIES gz-msgs${GZ_MSGS_VER}::gz-msgs${GZ_MSGS_VER} +) +``` + +### Using Custom messages + +There are two primary ways that Gazebo messages are used as part of an application. +Messages are either known at compile time and linked into the application, or +they are loaded dynamically at runtime. + +#### Compile time message use + +When messages are known at compile-time, they can be directly used. +This allow for the messages to be used without any sort of reflection/introspection. + +Include the headers and interact with the messages using the generated +bindings according to the [language guide](https://protobuf.dev/reference/cpp/cpp-generated/) + +```cpp +#include + +int main(int argc, char** argv) +{ + + gz::custom_msgs::BazStamped msg; + + // msg has header and baz field + auto *header = msg.mutable_header(); + auto *baz = msg.mutable_baz(); +} +``` + +The corresponding CMake for this: + +``` +add_executable(${PROJECT_NAME} main.cc) +target_link_libraries(${PROJECT_NAME} PUBLIC ${PROJECT_NAME}-msgs) +``` + +#### Run time message use + +Alternatively, there may be cases where all message definitions are not known +at the time that the executable/library of interest is being built. +A common case for this is with command line tools, where only a limited +set of messages are available at the time the tool is built, and more +user-defined messages are generated later. + +For example, when custom messages are generated, they aren't initially visible +to the `gz msgs` or `gz topic` command line tools: + +```sh +$ gz msg -l | grep "custom_msgs" | wc -l +0 + +$ gz topic -t /foo -m gz.custom_msgs.Foo -p 'value: 1.0' +Unable to create message of type[gz.custom_msgs.Foo] with data[value: 1.0]. +``` + +To use the new messages, point the `GZ_DESCRIPTOR_PATH` environment variable to +the location of the `build` folder or where you have choosen to install the +`.gz_desc` file: + +```sh +$ cd build/ + +$ export GZ_DESCRIPTOR_PATH=`pwd` + +$ gz msg -l | grep "custom_msgs" | wc -l +6 + +$ gz msg --info gz.custom_msgs.Foo +Name: gz.custom_msgs.Foo +File: gz/custom_msgs/foo.proto + +message Foo { + double value = 1 [json_name = "value"]; +} + +$ gz topic -t /foo -m gz.custom_msgs.Foo -p 'value: 1.0' +```