diff --git a/.github/workflows/package_xml.yml b/.github/workflows/package_xml.yml new file mode 100644 index 00000000..4bd4a9aa --- /dev/null +++ b/.github/workflows/package_xml.yml @@ -0,0 +1,11 @@ +name: Validate package.xml + +on: + pull_request: + +jobs: + package-xml: + runs-on: ubuntu-latest + name: Validate package.xml + steps: + - uses: gazebo-tooling/action-gz-ci/validate_package_xml@jammy diff --git a/BUILD.bazel b/BUILD.bazel index 9a23f639..4add695d 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -3,28 +3,41 @@ load( "GZ_FEATURES", "GZ_ROOT", "GZ_VISIBILITY", + "gz_configure_file", "gz_configure_header", "gz_export_header", "gz_include_header", ) +load( + "@gz//bazel/lint:lint.bzl", + "add_lint_tests", +) load( "@gz//msgs/tools:gz_msgs_generate.bzl", - "get_proto_headers", - "gz_msgs_generate", + "gz_proto_factory", + "gz_proto_library", ) +load("@rules_license//rules:license.bzl", "license") package( + default_applicable_licenses = [GZ_ROOT + "msgs:license"], default_visibility = GZ_VISIBILITY, features = GZ_FEATURES, ) -licenses(["notice"]) # Apache-2.0 +license( + name = "license", + package_name = "gz-msgs", +) + +licenses(["notice"]) exports_files(["LICENSE"]) gz_configure_header( name = "msgs_config_hh", - src = "include/gz/msgs/config.hh.in", + src = "core/include/gz/msgs/config.hh.in", + out = "include/gz/msgs/config.hh", cmakelists = ["CMakeLists.txt"], package = "msgs", ) @@ -37,43 +50,29 @@ gz_export_header( ) public_headers_no_gen = glob([ - "include/gz/msgs/*.hh", - "include/gz/msgs/detail/*.hh", + "core/include/gz/msgs/*.hh", + "core/include/gz/msgs/convert/*.hh", + "core/include/gz/msgs/detail/*.hh", ]) protos = glob(["proto/gz/msgs/*.proto"]) -gz_include_header( - name = "messagetypes_hh_genrule", - out = "include/gz/msgs/MessageTypes.hh", - hdrs = get_proto_headers(protos), - strip_prefix = ["gz_msgs"], -) - gz_include_header( name = "msgs_hh_genrule", out = "include/gz/msgs.hh", hdrs = public_headers_no_gen + [ "include/gz/msgs/config.hh", "include/gz/msgs/Export.hh", - "include/gz/msgs/MessageTypes.hh", ], ) -public_headers = public_headers_no_gen + [ - "include/gz/msgs/config.hh", - "include/gz/msgs/Export.hh", - "include/gz/msgs/MessageTypes.hh", - "include/gz/msgs.hh", -] - # Custom Gazebo Protoc plugin cc_binary( name = "gz_msgs_gen", srcs = [ - "src/Generator.cc", - "src/Generator.hh", - "src/generator_main.cc", + "core/generator_lite/Generator.cc", + "core/generator_lite/Generator.hh", + "core/generator_lite/generator_main.cc", ], deps = [ "@com_google_protobuf//:protobuf", @@ -91,26 +90,51 @@ proto_library( ], ) -gz_msgs_generate( +gz_proto_library( name = "gzmsgs_cc_proto", + proto_deps = [":gzmsgs_proto"], deps = [ - ":gzmsgs_proto", - "@com_google_protobuf//:any_proto", + "@com_google_protobuf//:protobuf", ], ) +gz_proto_factory( + name = "gzmsgs_proto_factory", + cc_output = "core/src/RegisterMsgs.cc", + hh_output = "include/gz/msgs/MessageTypes.hh", + deps = [":gzmsgs_proto"], +) + +public_headers = public_headers_no_gen + [ + "include/gz/msgs/config.hh", + "include/gz/msgs/Export.hh", + "include/gz/msgs.hh", + "include/gz/msgs/MessageTypes.hh", +] + cc_library( name = "msgs", srcs = [ - "src/Factory.cc", - "src/Filesystem.cc", - "src/Utility.cc", - ":gzmsgs_cc_proto", + "core/src/DynamicFactory.cc", + "core/src/DynamicFactory.hh", + "core/src/Factory.cc", + "core/src/MessageFactory.cc", + "core/src/RegisterMsgs.cc", + "core/src/impl/InstallationDirectories.cc", ], hdrs = public_headers, - includes = ["include"], + copts = [ + "-Wno-duplicate-decl-specifier", + "-fexceptions", + ], + includes = [ + "core/include", + "core/src", + "include", + ], deps = [ ":gzmsgs_cc_proto", + ":gzmsgs_proto_factory", GZ_ROOT + "math", "@com_google_protobuf//:protobuf", "@tinyxml2", @@ -138,3 +162,100 @@ test_sources = glob( "@gtest//:gtest_main", ], ) for src in test_sources] + +cc_test( + name = "INTEGRATION_headers", + srcs = ["test/integration/headers.cc"], + deps = [ + ":gzmsgs_cc_proto", + "@gtest", + "@gtest//:gtest_main", + ], +) + +cc_test( + name = "INTEGRATION_image_msg", + srcs = ["test/integration/image_msg.cc"], + deps = [ + ":gzmsgs_cc_proto", + "@gtest", + "@gtest//:gtest_main", + ], +) + +cc_test( + name = "INTEGRATION_Utility", + srcs = ["test/integration/Utility_TEST.cc"], + deps = [ + ":msgs", + GZ_ROOT + "common/testing", + "@gtest", + "@gtest//:gtest_main", + ], +) + +cc_test( + name = "INTEGRATION_Factory", + srcs = ["test/integration/Factory_TEST.cc"], + data = ["test/desc/stringmsg.desc"], + defines = [ + 'GZ_MSGS_TEST_PATH=\\"msgs/test\\"', + ], + deps = [ + ":msgs", + GZ_ROOT + "common/testing", + "@gtest", + "@gtest//:gtest_main", + ], +) + +cc_test( + name = "INTEGRATION_descriptors", + srcs = ["test/integration/descriptors.cc"], + data = ["test/desc"], + defines = [ + 'GZ_MSGS_TEST_PATH=\\"msgs/test\\"', + ], + deps = [ + ":msgs", + GZ_ROOT + "common/testing", + "@gtest", + "@gtest//:gtest_main", + ], +) + +gz_configure_file( + name = "msgs_yaml", + src = "conf/msgs.yaml.in", + out = "msgs.yaml", + defines = [ + "PROJECT_NAME_NO_VERSION_LOWER=msgs", + "gz_library_path=msgs/cmdmsgs", + ], + package = "msgs", +) + +gz_configure_file( + name = "msgs_rb", + src = "core/src/cmd/cmdmsgs.rb.in", + out = "cmdmsgs.rb", + defines = [ + "library_location=libgz-msgs.so", + ], + package = "msgs", +) + +cc_binary( + name = "gz-msgs", + srcs = ["core/src/gz.cc"], + data = [ + "cmdmsgs.rb", + "msgs.yaml", + ":gzmsgs_proto", + ], + linkshared = True, + linkstatic = True, + deps = [":msgs"], +) + +add_lint_tests() diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e0d9b63..9fe174a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -177,7 +177,7 @@ file( # Install cmake support files install( DIRECTORY cmake/ - DESTINATION "${PROJECT_CMAKE_EXTRAS_INSTALL_DIR}" + DESTINATION "${PROJECT_CMAKE_EXTRAS_RELATIVE_INSTALL_DIR}" ) #============================================================================ diff --git a/Changelog.md b/Changelog.md index e428cf00..11caab94 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,6 +4,51 @@ ## Gazebo Msgs 10.x +### Gazebo Msgs 10.2.0 (2024-05-17) + +1. CameraTrack message for advanced tracking and following. + * [Pull request #440](https://github.com/gazebosim/gz-msgs/pull/440) + +1. Update minimum version of gz-cmake3 + * [Pull request #439](https://github.com/gazebosim/gz-msgs/pull/439) + +1. bazel: lint + * [Pull request #438](https://github.com/gazebosim/gz-msgs/pull/438) + +1. Add package.xml + * [Pull request #432](https://github.com/gazebosim/gz-msgs/pull/432) + +### Gazebo Msgs 10.1.2 (2024-04-08) + +1. Use relative install paths for gz tool data and extra cmake files + * [Pull request #436](https://github.com/gazebosim/gz-msgs/pull/436) + +1. Bazel updates for Harmonic + * [Pull request #397](https://github.com/gazebosim/gz-msgs/pull/397) + +1. Fix MSVC "possible loss of data" warning + * [Pull request #434](https://github.com/gazebosim/gz-msgs/pull/434) + +### Gazebo Msgs 10.1.1 (2024-03-14) + +1. Allow topic and service to construct messages from description files + * [Pull request #428](https://github.com/gazebosim/gz-msgs/pull/428) + +1. Conditionally use cmake_path on cmake < 3.20 + * [Pull request #424](https://github.com/gazebosim/gz-msgs/pull/424) + +1. Remove duplicate find_package(Python3) call + * [Pull request #420](https://github.com/gazebosim/gz-msgs/pull/420) + +1. Bazel: Fix py_binary location + * [Pull request #406](https://github.com/gazebosim/gz-msgs/pull/406) + +1. Fix compiler warning in `generator.cc` + * [Pull request #403](https://github.com/gazebosim/gz-msgs/pull/403) + +1. Set GZ_TOOLS_VER to 2 for consistency with rest of Garden and Harmonic libraries + * [Pull request #391](https://github.com/gazebosim/gz-msgs/pull/391) + ### Gazebo Msgs 10.1.0 (2024-01-22) 1. Add proto message for MaterialColor. diff --git a/conf/CMakeLists.txt b/conf/CMakeLists.txt index 5bae2b3c..e2c4df6a 100644 --- a/conf/CMakeLists.txt +++ b/conf/CMakeLists.txt @@ -24,4 +24,4 @@ configure_file( # Install the yaml configuration files in an unversioned location. install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${GZ_DESIGNATION}${PROJECT_VERSION_MAJOR}.yaml - DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/gz/) + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/gz/) diff --git a/core/generator_lite/Generator.cc b/core/generator_lite/Generator.cc new file mode 100644 index 00000000..1279fac6 --- /dev/null +++ b/core/generator_lite/Generator.cc @@ -0,0 +1,116 @@ +/* + * Copyright 2016 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. + * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4100 4512 4127 4068 4244 4267 4251 4146) +#endif + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "Generator.hh" + +namespace google::protobuf::compiler::cpp { + +///////////////////////////////////////////////// +Generator::Generator(const std::string &/*_name*/) +{ +} + +///////////////////////////////////////////////// +Generator::~Generator() = default; + +///////////////////////////////////////////////// +bool Generator::Generate(const FileDescriptor *_file, + const std::string &/*_parameter*/, + OutputDirectory *_generatorContext, + std::string * /*_error*/) const +{ + std::string delim = ".proto"; + auto headerFilename = _file->name(); + auto sourceFilename = _file->name(); + + { + auto pos = headerFilename.rfind(delim); + headerFilename.replace(pos, delim.size(), ".pb.h"); + } + { + auto pos = sourceFilename.rfind(delim); + sourceFilename.replace(pos, delim.size(), ".pb.h"); + } + + { + auto *output = _generatorContext->OpenForInsert(headerFilename, "includes"); + auto printer = io::Printer(output, '$'); + printer.Print("#include \n", "name", "includes"); + } + + { + auto *output = _generatorContext->OpenForInsert( + headerFilename, "namespace_scope"); + auto printer = io::Printer(output, '$'); + + for (auto i = 0; i < _file->message_type_count(); ++i) + { + const auto *desc = _file->message_type(i); + std::string ptrTypes; + + // Define std::unique_ptr types for our messages + ptrTypes += "typedef std::unique_ptr<" + + desc->name() + "> " + + desc->name() + "UniquePtr;\n"; + + // Define const std::unique_ptr types for our messages + ptrTypes += "typedef std::unique_ptrname() + "> Const" + + desc->name() + "UniquePtr;\n"; + + // Define std::shared_ptr types for our messages + ptrTypes += "typedef std::shared_ptr<" + + desc->name() + "> " + + desc->name() + "SharedPtr;\n"; + + // Define const std::shared_ptr types for our messages + ptrTypes += "typedef std::shared_ptrname() + "> Const" + + desc->name() + "SharedPtr;\n"; + + printer.PrintRaw(ptrTypes.c_str()); + } + } + return true; +} +} // namespace google::protobuf::compiler::cpp diff --git a/core/generator_lite/Generator.hh b/core/generator_lite/Generator.hh new file mode 100644 index 00000000..3e4cabbe --- /dev/null +++ b/core/generator_lite/Generator.hh @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2016 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. + * +*/ + +#ifndef GZ_MSGS_GENERATOR_HH_ +#define GZ_MSGS_GENERATOR_HH_ + +#include +#include + +namespace google { +namespace protobuf { +namespace compiler { +namespace cpp { +/// \cond +/// \brief Google protobuf message generator for gz::msgs +class Generator : public CodeGenerator +{ + /// \brief Constructor + /// \param[in] _name Name value (currently unused) + public: explicit Generator(const std::string &_name); + + /// \brief Destructor + public: virtual ~Generator(); + + /// \brief Generate a message. + /// \param[in] _file File descriptor of the message. + /// \param[in] _parameter Unused string value + /// \param[in] _generatorContext Output directory. + /// \param[in] _error Unused string value + /// \return true if successful, otherwise sets _error and returns false + public: virtual bool Generate(const FileDescriptor *_file, + const std::string &_parameter, + OutputDirectory *_generatorContext, + std::string *_error) const; + +}; +/// \endcond +} +} +} +} +#endif diff --git a/core/generator_lite/generator_main.cc b/core/generator_lite/generator_main.cc new file mode 100644 index 00000000..79008fb6 --- /dev/null +++ b/core/generator_lite/generator_main.cc @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2016 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. + * +*/ + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4251) +#endif + +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "Generator.hh" + +int main(int _argc, char *_argv[]) +{ +#ifdef _MSC_VER + // Don't print a silly message or stick a modal dialog box in my face, + // please. + _set_abort_behavior(0, ~0); +#endif // !_MSC_VER + + google::protobuf::compiler::cpp::Generator + generator("gz-msgs-plugin"); + return google::protobuf::compiler::PluginMain(_argc, _argv, &generator); +} diff --git a/core/include/gz/msgs/convert/StdTypes.hh b/core/include/gz/msgs/convert/StdTypes.hh index e6be07ab..b9a2c6be 100644 --- a/core/include/gz/msgs/convert/StdTypes.hh +++ b/core/include/gz/msgs/convert/StdTypes.hh @@ -48,7 +48,7 @@ inline void Set(gz::msgs::Time *_msg, gz::math::durationToSecNsec(_data); msgs::Time msg; _msg->set_sec(timeSecAndNsecs.first); - _msg->set_nsec(timeSecAndNsecs.second); + _msg->set_nsec(static_cast(timeSecAndNsecs.second)); } inline void Set(std::chrono::steady_clock::duration *_data, diff --git a/core/src/DynamicFactory.cc b/core/src/DynamicFactory.cc index 2ef60f3f..851f338d 100644 --- a/core/src/DynamicFactory.cc +++ b/core/src/DynamicFactory.cc @@ -16,8 +16,12 @@ */ #include +#include #include #include +#include +#include +#include #include #include "DynamicFactory.hh" @@ -101,9 +105,10 @@ void DynamicFactory::LoadDescriptors(const std::string &_paths) if (!ifs.is_open()) { std::cerr << "DynamicFactory(): Unable to open [" << descFile << "]" - << std::endl; - return; + << std::endl; + return; } + google::protobuf::FileDescriptorSet fileDescriptorSet; if (!fileDescriptorSet.ParseFromIstream(&ifs)) { @@ -111,11 +116,12 @@ void DynamicFactory::LoadDescriptors(const std::string &_paths) << descFile << "]" << std::endl; return; } + // Place the real descriptors in the descriptor pool. for (const google::protobuf::FileDescriptorProto &fileDescriptorProto : fileDescriptorSet.file()) { - if (!this->pool.BuildFile(fileDescriptorProto)) + if (!static_cast(this->pool.BuildFile(fileDescriptorProto))) { std::cerr << "DynamicFactory(). Unable to place descriptors from [" << descFile << "] in the descriptor pool" << std::endl; diff --git a/core/src/impl/InstallationDirectories.cc b/core/src/impl/InstallationDirectories.cc new file mode 100644 index 00000000..e24cee12 --- /dev/null +++ b/core/src/impl/InstallationDirectories.cc @@ -0,0 +1,27 @@ +/* + * 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. + * + */ + +#include + +#include + +namespace gz::msgs +{ +inline namespace GZ_MSGS_VERSION_NAMESPACE { +std::string getInstallPrefix() { return {};} +} // namespace +} // namespace gz::msgs diff --git a/package.xml b/package.xml new file mode 100644 index 00000000..7a673e11 --- /dev/null +++ b/package.xml @@ -0,0 +1,26 @@ + + + gz-msgs11 + 11.0.0 + Gazebo Messages: Protobuf messages and functions for robot applications + Carlos Agüero + Apache License 2.0 + https://github.com/gazebosim/gz-msgs + + cmake + + gz-cmake4 + + gz-math8 + gz-tools2 + protobuf-dev + python3-protobuf + python3 + tinyxml2 + + python3-pytest + + + cmake + + diff --git a/proto/gz/msgs/cameratrack.proto b/proto/gz/msgs/cameratrack.proto new file mode 100644 index 00000000..27477801 --- /dev/null +++ b/proto/gz/msgs/cameratrack.proto @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2024 Rudis Laboratories + * + * 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.msgs; +option java_package = "com.gz.msgs"; +option java_outer_classname = "CameraTrackProtos"; + +/// \ingroup gz.msgs +/// \interface CameraTrack +/// \brief Message for camera tracking + +import "gz/msgs/entity.proto"; +import "gz/msgs/header.proto"; +import "gz/msgs/vector3d.proto"; + +message CameraTrack +{ + enum TrackMode + { + /// \brief Defaults to no tracking mode. + NONE = 0; + /// \brief TRACK observes a target from fixed x,y,z world + /// camera point with pgain allowing only for orientation change. + TRACK = 1; + /// \brief FOLLOW moves with a target with an offset x,y,z camera and pgain. + FOLLOW = 2; + /// \brief FOLLOW_FREE_LOOK moves with a target with an offset x,y,z camera + /// and pgain but allows for changing orientation for free looking around. + FOLLOW_FREE_LOOK = 3; + /// \brief FOLLOW_LOOK_AT moves with a target with an offset x,y,z camera + /// and pgain while looking at a specified target. + FOLLOW_LOOK_AT = 4; + /// \brief USE_LAST updates offsets and pgains without changing the + /// fundamental track_mode or target(s) + USE_LAST = 5; + } + + /// \brief Optional header data + Header header = 1; + /// \brief Tracking mode. + TrackMode track_mode = 2; + /// \brief Target entity to follow. + Entity follow_target = 3; + /// \brief Target entity to track. + Entity track_target = 4; + /// \brief Follow offset in local target frame. + Vector3d follow_offset = 5; + /// \brief Track offset in local target frame. + Vector3d track_offset = 6; + /// \brief Follow pgain for following a target. + double follow_pgain = 7; + /// \brief Track pgain for following a target. + double track_pgain = 8; +} diff --git a/protobuf.bzl b/protobuf.bzl deleted file mode 100644 index ac7bb013..00000000 --- a/protobuf.bzl +++ /dev/null @@ -1,235 +0,0 @@ -"""Utility functions for generating protobuf code.""" - -load("@rules_proto//proto:defs.bzl", "ProtoInfo") - -_PROTO_EXTENSION = ".proto" -_VIRTUAL_IMPORTS = "/_virtual_imports/" - -def well_known_proto_libs(): - return [ - "@com_google_protobuf//:any_proto", - "@com_google_protobuf//:api_proto", - "@com_google_protobuf//:compiler_plugin_proto", - "@com_google_protobuf//:descriptor_proto", - "@com_google_protobuf//:duration_proto", - "@com_google_protobuf//:empty_proto", - "@com_google_protobuf//:field_mask_proto", - "@com_google_protobuf//:source_context_proto", - "@com_google_protobuf//:struct_proto", - "@com_google_protobuf//:timestamp_proto", - "@com_google_protobuf//:type_proto", - "@com_google_protobuf//:wrappers_proto", - ] - -def get_proto_root(workspace_root): - """Gets the root protobuf directory. - Args: - workspace_root: context.label.workspace_root - Returns: - The directory relative to which generated include paths should be. - """ - if workspace_root: - return "/{}".format(workspace_root) - else: - return "" - -def _strip_proto_extension(proto_filename): - if not proto_filename.endswith(_PROTO_EXTENSION): - fail('"{}" does not end with "{}"'.format( - proto_filename, - _PROTO_EXTENSION, - )) - return proto_filename[:-len(_PROTO_EXTENSION)] - -def proto_path_to_generated_filename(proto_path, fmt_str): - """Calculates the name of a generated file for a protobuf path. - For example, "examples/protos/helloworld.proto" might map to - "helloworld.pb.h". - Args: - proto_path: The path to the .proto file. - fmt_str: A format string used to calculate the generated filename. For - example, "{}.pb.h" might be used to calculate a C++ header filename. - Returns: - The generated filename. - """ - return fmt_str.format(_strip_proto_extension(proto_path)) - -def get_include_directory(source_file): - """Returns the include directory path for the source_file. I.e. all of the - include statements within the given source_file are calculated relative to - the directory returned by this method. - The returned directory path can be used as the "--proto_path=" argument - value. - Args: - source_file: A proto file. - Returns: - The include directory path for the source_file. - """ - directory = source_file.path - prefix_len = 0 - - if is_in_virtual_imports(source_file): - root, relative = source_file.path.split(_VIRTUAL_IMPORTS, 2) - result = root + _VIRTUAL_IMPORTS + relative.split("/", 1)[0] - return result - - if not source_file.is_source and directory.startswith(source_file.root.path): - prefix_len = len(source_file.root.path) + 1 - - if directory.startswith("external", prefix_len): - external_separator = directory.find("/", prefix_len) - repository_separator = directory.find("/", external_separator + 1) - return directory[:repository_separator] - else: - return source_file.root.path if source_file.root.path else "." - -def get_plugin_args( - plugin, - flags, - dir_out, - generate_mocks, - plugin_name = "PLUGIN"): - """Returns arguments configuring protoc to use a plugin for a language. - Args: - plugin: An executable file to run as the protoc plugin. - flags: The plugin flags to be passed to protoc. - dir_out: The output directory for the plugin. - generate_mocks: A bool indicating whether to generate mocks. - plugin_name: A name of the plugin, it is required to be unique when there - are more than one plugin used in a single protoc command. - Returns: - A list of protoc arguments configuring the plugin. - """ - augmented_flags = list(flags) - if generate_mocks: - augmented_flags.append("generate_mock_code=true") - - augmented_dir_out = dir_out - if augmented_flags: - augmented_dir_out = ",".join(augmented_flags) + ":" + dir_out - - return [ - "--plugin=protoc-gen-{plugin_name}={plugin_path}".format( - plugin_name = plugin_name, - plugin_path = plugin.path, - ), - "--{plugin_name}_out={dir_out}".format( - plugin_name = plugin_name, - dir_out = augmented_dir_out, - ), - ] - -def _get_staged_proto_file(context, source_file): - if source_file.dirname == context.label.package or \ - is_in_virtual_imports(source_file): - # Current target and source_file are in same package - return source_file - else: - # Current target and source_file are in different packages (most - # probably even in different repositories) - copied_proto = context.actions.declare_file(source_file.basename) - context.actions.run_shell( - inputs = [source_file], - outputs = [copied_proto], - command = "cp {} {}".format(source_file.path, copied_proto.path), - mnemonic = "CopySourceProto", - ) - return copied_proto - -def protos_from_context(context): - """Copies proto files to the appropriate location. - Args: - context: The ctx object for the rule. - Returns: - A list of the protos. - """ - protos = [] - for src in context.attr.deps: - for file in src[ProtoInfo].direct_sources: - protos.append(_get_staged_proto_file(context, file)) - return protos - -def includes_from_deps(deps): - """Get includes from rule dependencies.""" - return [ - file - for src in deps - for file in src[ProtoInfo].transitive_imports.to_list() - ] - -def get_proto_arguments(protos, genfiles_dir_path): - """Get the protoc arguments specifying which protos to compile.""" - arguments = [] - for proto in protos: - strip_prefix_len = 0 - if is_in_virtual_imports(proto): - incl_directory = get_include_directory(proto) - if proto.path.startswith(incl_directory): - strip_prefix_len = len(incl_directory) + 1 - elif proto.path.startswith(genfiles_dir_path): - strip_prefix_len = len(genfiles_dir_path) + 1 - - arguments.append(proto.path[strip_prefix_len:]) - - return arguments - -def declare_out_files(protos, context, generated_file_format): - """Declares and returns the files to be generated.""" - - out_file_paths = [] - for proto in protos: - if not is_in_virtual_imports(proto): - out_file_paths.append(proto.basename) - else: - path = proto.path[proto.path.index(_VIRTUAL_IMPORTS) + 1:] - out_file_paths.append(path) - - return [ - context.actions.declare_file( - proto_path_to_generated_filename( - out_file_path, - generated_file_format, - ), - ) - for out_file_path in out_file_paths - ] - -def get_out_dir(protos, context): - """ Returns the calculated value for --_out= protoc argument based on - the input source proto files and current context. - Args: - protos: A list of protos to be used as source files in protoc command - context: A ctx object for the rule. - Returns: - The value of --_out= argument. - """ - at_least_one_virtual = 0 - for proto in protos: - if is_in_virtual_imports(proto): - at_least_one_virtual = True - elif at_least_one_virtual: - fail("Proto sources must be either all virtual imports or all real") - if at_least_one_virtual: - out_dir = get_include_directory(protos[0]) - ws_root = protos[0].owner.workspace_root - if ws_root and out_dir.find(ws_root) >= 0: - out_dir = "".join(out_dir.rsplit(ws_root, 1)) - return struct( - path = out_dir, - import_path = out_dir[out_dir.find(_VIRTUAL_IMPORTS) + 1:], - ) - return struct(path = context.genfiles_dir.path, import_path = None) - -def is_in_virtual_imports(source_file, virtual_folder = _VIRTUAL_IMPORTS): - """Determines if source_file is virtual (is placed in _virtual_imports - subdirectory). The output of all proto_library targets which use - import_prefix and/or strip_import_prefix arguments is placed under - _virtual_imports directory. - Args: - source_file: A proto file. - virtual_folder: The virtual folder name (is set to "_virtual_imports" - by default) - Returns: - True if source_file is located under _virtual_imports, False otherwise. - """ - return not source_file.is_source and virtual_folder in source_file.path diff --git a/test/desc/testing.proto b/test/desc/testing.proto index b2524164..d6927372 100644 --- a/test/desc/testing.proto +++ b/test/desc/testing.proto @@ -2,7 +2,7 @@ syntax = "proto3"; package testing; -message Bytes +message BarBytes { bytes data = 1; } diff --git a/test/desc/testing.proto.bin b/test/desc/testing.proto.bin index ea7cbe8c..20422275 100644 --- a/test/desc/testing.proto.bin +++ b/test/desc/testing.proto.bin @@ -1,16 +1,16 @@ -C -testing/bytes.prototesting" -Bytes -data ( Rdatabproto3 -ˆ -testing/message.prototesting" +´ +testing/message.prototesting" -FooMessage -foo ( Rfoo" +FooMessage +data ( Rdata"3 -BarMessage -foo ( Rfoo" +BarMessage% +foo ( 2.testing.FooMessageRfoo"3 -BazMessage -foo ( Rfoobproto3 \ No newline at end of file +BazMessage% +bar ( 2.testing.BarMessageRbarbproto3 +F +testing/bytes.prototesting" +FooBytes +data ( Rdatabproto3 \ No newline at end of file diff --git a/test/desc/testing/bytes.proto b/test/desc/testing/bytes.proto index b2524164..83eb9da8 100644 --- a/test/desc/testing/bytes.proto +++ b/test/desc/testing/bytes.proto @@ -2,7 +2,7 @@ syntax = "proto3"; package testing; -message Bytes +message FooBytes { bytes data = 1; } diff --git a/test/integration/descriptors.cc b/test/integration/descriptors.cc index 2d7ee0d5..4cc62291 100644 --- a/test/integration/descriptors.cc +++ b/test/integration/descriptors.cc @@ -62,7 +62,7 @@ TEST(FactoryTest, DynamicFactory) gz::msgs::Factory::LoadDescriptors(paths); } EXPECT_EQ(nullptr, gz::msgs::Factory::New("example.msgs.StringMsg")); - EXPECT_EQ(nullptr, gz::msgs::Factory::New("testing.Bytes")); + EXPECT_EQ(nullptr, gz::msgs::Factory::New("testing.BarBytes")); EXPECT_EQ(nullptr, gz::msgs::Factory::New("testing.FooMessage")); EXPECT_EQ(nullptr, gz::msgs::Factory::New("testing.BarMessage")); EXPECT_EQ(nullptr, gz::msgs::Factory::New("testing.BazMessage")); @@ -88,7 +88,7 @@ TEST(FactoryTest, DynamicFactory) } EXPECT_NE(nullptr, gz::msgs::Factory::New("example.msgs.StringMsg")); - EXPECT_NE(nullptr, gz::msgs::Factory::New("testing.Bytes")); + EXPECT_NE(nullptr, gz::msgs::Factory::New("testing.FooBytes")); EXPECT_NE(nullptr, gz::msgs::Factory::New("testing.FooMessage")); EXPECT_NE(nullptr, gz::msgs::Factory::New("testing.BarMessage")); EXPECT_NE(nullptr, gz::msgs::Factory::New("testing.BazMessage")); diff --git a/test/integration/gz_TEST.cc b/test/integration/gz_TEST.cc index 7d5e2fc2..1cea4b7d 100644 --- a/test/integration/gz_TEST.cc +++ b/test/integration/gz_TEST.cc @@ -20,6 +20,7 @@ #include #include #include +#include #ifdef _MSC_VER # define popen _popen @@ -127,7 +128,7 @@ TEST(CmdLine, MsgInfo) } ///////////////////////////////////////////////// -TEST(CmdLine, MsgHelpVsCompletionFlags) +TEST(CmdLine, GZ_UTILS_TEST_DISABLED_ON_WIN32(MsgHelpVsCompletionFlags)) { // Flags in help message auto helpOutput = custom_exec_str(make_exec_string("--help")); diff --git a/tools/BUILD.bazel b/tools/BUILD.bazel index bba0c8c1..359f83e5 100644 --- a/tools/BUILD.bazel +++ b/tools/BUILD.bazel @@ -1,8 +1,12 @@ -load("@gz//bazel/skylark:build_defs.bzl", "gz_py_binary") +load( + "@gz//bazel/skylark:build_defs.bzl", + "GZ_VISIBILITY", + "gz_py_binary", +) gz_py_binary( - name = "gz_msgs_generate_py", - srcs = ["gz_msgs_generate.py"], - main = "gz_msgs_generate.py", - visibility = ["//visibility:public"], + name = "gz_msgs_generate_factory_py", + srcs = ["gz_msgs_generate_factory_lite.py"], + main = "gz_msgs_generate_factory_lite.py", + visibility = GZ_VISIBILITY, ) diff --git a/tools/gz_msgs_generate.bzl b/tools/gz_msgs_generate.bzl index b28024e8..4f60f9ac 100644 --- a/tools/gz_msgs_generate.bzl +++ b/tools/gz_msgs_generate.bzl @@ -1,3 +1,5 @@ +"""Helper functions for generating gz-msgs compatible messages""" + load("@rules_proto//proto:defs.bzl", "ProtoInfo") load( "@gz//bazel/skylark:protobuf.bzl", @@ -7,15 +9,34 @@ load( "protos_from_context", ) -_VIRTUAL_IMPORTS = "/_virtual_imports/" +def _filter_files_impl(ctx): + """Filter the files in DefaultInfo.""" + return [DefaultInfo( + files = depset([ + file + for file in ctx.attr.target.files.to_list() + if file.extension in ctx.attr.extensions + ]), + )] -def _get_detail_directory(hh_file, ctx): - base = hh_file.path[hh_file.path.index(_VIRTUAL_IMPORTS) + 1:] - base = base.split("/") - base.insert(-1, "details") - return ctx.actions.declare_file("/".join(base)) +filter_files = rule( + implementation = _filter_files_impl, + attrs = { + "target": attr.label( + doc = "The source target to filter", + mandatory = True, + ), + "extensions": attr.string_list( + doc = "The extensions of the files to keep eg. ['h']", + mandatory = True, + ), + }, +) def get_proto_headers(protos): + """ + Get headers for a set of proto files + """ out = [] for proto in protos: split = proto.split("/")[1:] @@ -23,42 +44,129 @@ def get_proto_headers(protos): out.append("/".join(split)) return out -def _gz_msgs_generate_impl(ctx): +def _gz_proto_factory_impl(ctx): + """ + Implementation of gz_proto_factory rule + """ protos = protos_from_context(ctx) out_dir = get_out_dir(protos, ctx) - + in_protos = [] include_dirs = depset([get_include_directory(proto) for proto in protos]) + for src in ctx.attr.deps: + for proto in src[ProtoInfo].direct_sources: + in_protos.append(proto) + + in_protos = depset(in_protos).to_list() arguments = [ - "--protoc-exec=" + ctx.executable._protoc.path, - "--gz-generator-bin=" + ctx.executable._gz_gen_bin.path, - "--generate-cpp", - "--output-cpp-path=" + out_dir.path, + "--cc-output=" + ctx.outputs.cc_output.path, + "--hh-output=" + ctx.outputs.hh_output.path, ] - for proto in protos: - arguments.append("--input-path=" + proto.path) - for include_dir in include_dirs.to_list(): arguments.append("--proto-path=" + include_dir) - out_protos = [proto for proto in protos if proto.path.find("gz/msgs") > 0] + arguments.append("--proto-include-path=" + out_dir.path) + arguments.append("--protos") + for proto in in_protos: + arguments.append(proto.path) + + ctx.actions.run( + inputs = protos, + outputs = [ctx.outputs.cc_output, ctx.outputs.hh_output], + arguments = arguments, + executable = ctx.executable.gz_msgs_generate_factory_py, + ) + + compilation_context = cc_common.create_compilation_context( + headers = depset([ctx.outputs.hh_output]), + system_includes = depset([ctx.outputs.hh_output.dirname]), + ) + + return [ + CcInfo(compilation_context = compilation_context), + ] + +_gz_proto_factory = rule( + attrs = { + "deps": attr.label_list( + mandatory = True, + allow_empty = False, + providers = [ProtoInfo], + ), + "cc_output": attr.output(mandatory = True), + "hh_output": attr.output(mandatory = True), + "gz_msgs_generate_factory_py": attr.label( + default = Label("@gz//msgs/tools:gz_msgs_generate_factory_py"), + executable = True, + cfg = "host", + ), + }, + implementation = _gz_proto_factory_impl, +) + +def gz_proto_factory( + deps, + **kwargs): + """ + gz_proto_factory rule implementation wrapper + """ + _gz_proto_factory( + deps = deps, + **kwargs + ) + +def _gz_proto_library_impl(ctx): + """ + Implementation of the gz_proto_library rule + """ + protos = protos_from_context(ctx) + out_dir = get_out_dir(protos, ctx) + + arguments = [] + + in_protos = [] + out_protos = [] + include_dirs = [] + + for src in ctx.attr.deps: + for proto in src[ProtoInfo].direct_sources: + in_protos.append(proto) + include_dirs.append(get_include_directory(proto)) + out_protos.append(proto) + + for proto in src[ProtoInfo].transitive_sources.to_list(): + in_protos.append(proto) + include_dirs.append(get_include_directory(proto)) + + in_protos = depset(in_protos).to_list() + out_protos = depset(out_protos).to_list() + include_dirs = depset(include_dirs).to_list() + + for include_dir in include_dirs: + arguments.append("--proto_path=" + include_dir) + + arguments.append("--plugin=protoc-gen-gzmsgs=" + ctx.executable._gz_gen_bin.path) + arguments.append("--cpp_out=" + out_dir.path) + arguments.append("--gzmsgs_out=" + out_dir.path) + + for proto in in_protos: + arguments.append(proto.path) cc_files = declare_out_files(out_protos, ctx, "{}.pb.cc") hh_files = declare_out_files(out_protos, ctx, "{}.pb.h") - detail_hh_files = [_get_detail_directory(file, ctx) for file in hh_files] - out_files = cc_files + hh_files + detail_hh_files + out_files = cc_files + hh_files ctx.actions.run( - inputs = protos, + inputs = in_protos, outputs = out_files, arguments = arguments, - executable = ctx.executable.gz_msgs_generate_py, - tools = [ctx.executable._protoc, ctx.executable._gz_gen_bin, ctx.executable.gz_msgs_generate_py], + executable = ctx.executable._protoc, + tools = [ctx.executable._protoc, ctx.executable._gz_gen_bin], ) compilation_context = cc_common.create_compilation_context( - headers = depset(out_files), + headers = depset(hh_files), system_includes = depset([out_dir.path]), ) @@ -67,7 +175,7 @@ def _gz_msgs_generate_impl(ctx): CcInfo(compilation_context = compilation_context), ] -_gz_msgs_generate_gen = rule( +_gz_proto_library = rule( attrs = { "deps": attr.label_list( mandatory = True, @@ -84,19 +192,26 @@ _gz_msgs_generate_gen = rule( executable = True, cfg = "host", ), - "gz_msgs_generate_py": attr.label( - default = Label("@gz//msgs/tools:gz_msgs_generate_py"), - executable = True, - cfg = "host", - ), }, - implementation = _gz_msgs_generate_impl, + implementation = _gz_proto_library_impl, ) -def gz_msgs_generate( - deps, +def gz_proto_library( + name, + proto_deps, **kwargs): - _gz_msgs_generate_gen( - deps = deps, + """ + gz_proto_library rule implementation wrapper + """ + _gz_proto_library(name = name + "_pb", deps = proto_deps) + + filter_files(name = name + "_srcs", target = ":" + name + "_pb", extensions = ["cc"]) + filter_files(name = name + "_hdrs", target = ":" + name + "_pb", extensions = ["h"]) + kwargs["deps"].append(":" + name + "_pb") + + native.cc_library( + name = name, + srcs = [name + "_srcs"], + hdrs = [name + "_hdrs"], **kwargs ) diff --git a/tools/gz_msgs_generate_factory_lite.py b/tools/gz_msgs_generate_factory_lite.py new file mode 100755 index 00000000..624b664f --- /dev/null +++ b/tools/gz_msgs_generate_factory_lite.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 +# +# 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. + +import argparse +import os +import re +import sys + +# Create +cc_header = """/* + * 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. + * +*/ + +/* This file was automatically generated. + * Do not edit this directly + */ + +#ifndef GZ_MSGS_MESSAGE_TYPES_HH_ +#define GZ_MSGS_MESSAGE_TYPES_HH_ +{gz_msgs_headers} + +namespace {namespace} {{ +int RegisterAll(); +}} +#endif""" + +# Create factory registration bits +cc_source = """/* + * 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. + * +*/ + +/* This file was automatically generated. + * Do not edit this directly + */ + +#include "gz/msgs/Factory.hh" +#include "gz/msgs/MessageFactory.hh" +#include "{include_path}/MessageTypes.hh" + +#include + +namespace {{ + using NamedFactoryFn = std::pair; + + std::array kFactoryFunctions = {{{{ +{registrations} +}}}}; +}} // namespace +""" + +cc_factory = """ +namespace {namespace} {{ +int RegisterAll() {{ + size_t registered = 0; + for (const auto &entry: kFactoryFunctions) {{ + gz::msgs::Factory::Register(entry.first, entry.second); + registered++; + }} + return registered; +}} + +static int kMessagesRegistered = RegisterAll(); +}} // namespace {namespace} +""" + +register_fn = """ {{"{package_str}.{message_str}", + []()->std::unique_ptr{{return std::make_unique<{message_cpp_type}>();}}}},""" + +def main(argv=sys.argv[1:]): + parser = argparse.ArgumentParser( + description='Generate protobuf factory file', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument( + '--cc-output', + required=True, + help='The path to the generated cpp file') + parser.add_argument( + '--hh-output', + required=True, + help='The path to the generated hh file') + parser.add_argument( + '--proto-path', + required=True, + help='The location of the protos') + parser.add_argument( + '--proto-include-path', + required=True, + help='The location of the protos') + parser.add_argument( + '--protos', + type=str, + nargs='*', + required=True, + help='The list of protos to include' + ) + + args = parser.parse_args(argv) + + package_re = re.compile('^package (.*);$') + message_re = re.compile(r'message (\w*)\s?{?$') + + registrations = dict() + gz_msgs_headers = [] + package = [] + messages = [] + + for proto in args.protos: + try: + with open(proto, 'r') as f: + content = f.readlines() + for line in content: + package_found = package_re.match(line) + if package_found: + package = package_found.group(1).split('.') + + message_found = message_re.match(line) + if message_found: + messages.append(message_found.group(1)) + except: + pass + + if package and messages: + for message in messages: + registrations['_'.join([*package, message])] = register_fn.format( + package_str='.'.join(package), + message_str=message, + message_cpp_type='::'.join([*package, message]) + ) + + split = proto.replace(args.proto_include_path, '') + split = [s for s in split.split("/") if s] + split[-1] = split[-1].replace(".proto", ".pb.h") + gz_msgs_headers.append("#include <" + "/".join(split) + ">") + + namespace = '::'.join(package) + include_path = '/'.join(package) + + with open(os.path.join(args.cc_output), 'w') as f: + f.write((cc_source.format(registrations='\n'.join(registrations.values()), + nRegistrations=len(registrations.values()), + namespace=namespace, + include_path=include_path) + + cc_factory.format(namespace=namespace))) + + with open(os.path.join(args.hh_output), 'w') as f: + f.write(cc_header.format(namespace=namespace, + gz_msgs_headers='\n'.join(gz_msgs_headers))) + +if __name__ == '__main__': + sys.exit(main())