diff --git a/rosidl_generator_rs/CMakeLists.txt b/rosidl_generator_rs/CMakeLists.txt new file mode 100644 index 0000000..fd7d1c1 --- /dev/null +++ b/rosidl_generator_rs/CMakeLists.txt @@ -0,0 +1,44 @@ +cmake_minimum_required(VERSION 3.5) + +project(rosidl_generator_rs) + +find_package(ament_cmake REQUIRED) +find_package(ament_cmake_python REQUIRED) +find_package(rosidl_generator_c REQUIRED) +find_package(rosidl_typesupport_interface REQUIRED) +find_package(rosidl_typesupport_introspection_c REQUIRED) + +if("$ENV{ROS_DISTRO}" STRLESS_EQUAL "humble") + find_package(rosidl_cmake REQUIRED) + ament_export_dependencies(rosidl_cmake) +endif() + +ament_export_dependencies(rmw) +ament_export_dependencies(rosidl_generator_c) + +ament_index_register_resource("rosidl_generator_packages") +ament_index_register_resource("rosidl_runtime_packages") + +ament_python_install_package(${PROJECT_NAME}) + +ament_package( + CONFIG_EXTRAS + "rosidl_generator_rs-extras.cmake.in" + "cmake/register_rs.cmake" +) + +install(DIRECTORY cmake + DESTINATION share/${PROJECT_NAME}) +ament_register_extension( + "rosidl_generate_idl_interfaces" + "rosidl_generator_rs" + "${PROJECT_SOURCE_DIR}/cmake/rosidl_generator_rs_generate_interfaces.cmake") + +install( + PROGRAMS bin/rosidl_generator_rs + DESTINATION lib/rosidl_generator_rs +) +install( + DIRECTORY cmake resource + DESTINATION share/${PROJECT_NAME} +) diff --git a/rosidl_generator_rs/bin/rosidl_generator_rs b/rosidl_generator_rs/bin/rosidl_generator_rs new file mode 100755 index 0000000..0bfcc22 --- /dev/null +++ b/rosidl_generator_rs/bin/rosidl_generator_rs @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 + +import argparse +import os +import sys + +try: + from rosidl_generator_rs import generate_rs +except ImportError: + # modifying sys.path and importing the Rust package with the same + # name as this script does not work on Windows + rosidl_generator_rs_root = os.path.dirname(os.path.dirname(__file__)) + rosidl_generator_rs_module = os.path.join( + rosidl_generator_rs_root, 'rosidl_generator_rs', '__init__.py') + if not os.path.exists(rosidl_generator_rs_module): + raise + from importlib.machinery import SourceFileLoader + + loader = SourceFileLoader('rosidl_generator_rs', rosidl_generator_rs_module) + rosidl_generator_rs = loader.load_module() + generate_rs = rosidl_generator_rs.generate_rs + + +def main(argv=sys.argv[1:]): + parser = argparse.ArgumentParser(description='Generate the Rust ROS interfaces.') + parser.add_argument( + '--generator-arguments-file', + required=True, + help='The location of the file containing the generator arguments') + parser.add_argument( + '--typesupport-impls', + required=True, + help='All the available typesupport implementations') + args = parser.parse_args(argv) + + return generate_rs( + args.generator_arguments_file, + args.typesupport_impls, + ) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/rosidl_generator_rs/cmake/custom_command.cmake b/rosidl_generator_rs/cmake/custom_command.cmake new file mode 100644 index 0000000..d16ccd8 --- /dev/null +++ b/rosidl_generator_rs/cmake/custom_command.cmake @@ -0,0 +1,40 @@ +# Copyright 2017 Esteve Fernandez +# +# 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. + +add_custom_command( + OUTPUT + ${_generated_common_rs_files} + ${_generated_msg_rs_files} + ${_generated_srv_rs_files} + ${_generated_action_rs_files} + COMMAND ${PYTHON_EXECUTABLE} ${rosidl_generator_rs_BIN} + --generator-arguments-file "${generator_arguments_file}" + --typesupport-impls "${_typesupport_impls}" + DEPENDS ${target_dependencies} + COMMENT "Generating Rust code for ROS interfaces" + VERBATIM +) + +if(TARGET ${rosidl_generate_interfaces_TARGET}${_target_suffix}) + message(WARNING "Custom target ${rosidl_generate_interfaces_TARGET}${_target_suffix} already exists") +else() + add_custom_target( + ${rosidl_generate_interfaces_TARGET}${_target_suffix} ALL + DEPENDS + ${_generated_common_rs_files} + ${_generated_msg_rs_files} + ${_generated_srv_rs_files} + ${_generated_action_rs_files} + ) +endif() diff --git a/rosidl_generator_rs/cmake/register_rs.cmake b/rosidl_generator_rs/cmake/register_rs.cmake new file mode 100644 index 0000000..1dbfcda --- /dev/null +++ b/rosidl_generator_rs/cmake/register_rs.cmake @@ -0,0 +1,30 @@ +# Copyright 2016-2017 Esteve Fernandez +# +# 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. + +macro(rosidl_generator_rs_extras BIN GENERATOR_FILES TEMPLATE_DIR) + find_package(ament_cmake_core QUIET REQUIRED) + ament_register_extension( + "rosidl_generate_idl_interfaces" + "rosidl_generator_rs" + "rosidl_generator_rs_generate_interfaces.cmake") + + normalize_path(BIN "${BIN}") + set(rosidl_generator_rs_BIN "${BIN}") + + normalize_path(GENERATOR_FILES "${GENERATOR_FILES}") + set(rosidl_generator_rs_GENERATOR_FILES "${GENERATOR_FILES}") + + normalize_path(TEMPLATE_DIR "${TEMPLATE_DIR}") + set(rosidl_generator_rs_TEMPLATE_DIR "${TEMPLATE_DIR}") +endmacro() diff --git a/rosidl_generator_rs/cmake/rosidl_generator_rs_generate_interfaces.cmake b/rosidl_generator_rs/cmake/rosidl_generator_rs_generate_interfaces.cmake new file mode 100644 index 0000000..af42061 --- /dev/null +++ b/rosidl_generator_rs/cmake/rosidl_generator_rs_generate_interfaces.cmake @@ -0,0 +1,164 @@ +# Copyright 2016-2017 Esteve Fernandez +# +# 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. + +if(NOT WIN32) + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined") + elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-undefined,error") + endif() +endif() + +set(_output_path + "${CMAKE_CURRENT_BINARY_DIR}/rosidl_generator_rs/${PROJECT_NAME}") +set(_generated_common_rs_files "") + +set(_generated_msg_rs_files "") +set(_generated_srv_rs_files "") +set(_generated_action_rs_files "") + +set(_has_msg FALSE) +set(_has_srv FALSE) +set(_has_action FALSE) + +foreach(_idl_file ${rosidl_generate_interfaces_ABS_IDL_FILES}) + get_filename_component(_parent_folder "${_idl_file}" DIRECTORY) + get_filename_component(_parent_folder "${_parent_folder}" NAME) + get_filename_component(_module_name "${_idl_file}" NAME_WE) + + if(_parent_folder STREQUAL "msg") + set(_has_msg TRUE) + set(_idl_files ${_idl_files} ${_idl_file}) + elseif(_parent_folder STREQUAL "srv") + set(_has_srv TRUE) + set(_idl_files ${_idl_files} ${_idl_file}) + elseif(_parent_folder STREQUAL "action") + set(_has_action TRUE) + set(_idl_files ${_idl_files} ${_idl_file}) + else() + message(FATAL_ERROR "Interface file with unknown parent folder: ${_idl_file}") + endif() +endforeach() + +list(APPEND _generated_common_rs_files + "${_output_path}/rust/src/lib.rs" + "${_output_path}/rust/build.rs" + "${_output_path}/rust/Cargo.toml" +) + +if(${_has_msg}) + list(APPEND _generated_msg_rs_files + "${_output_path}/rust/src/msg.rs" + ) +endif() + +if(${_has_srv}) + list(APPEND _generated_srv_rs_files + "${_output_path}/rust/src/srv.rs" + ) +endif() + +if(${_has_action}) + list(APPEND _generated_action_rs_files + "${_output_path}/rust/src/action.rs" + ) +endif() + +set(_dependency_files "") +set(_dependencies "") +foreach(_pkg_name ${rosidl_generate_interfaces_DEPENDENCY_PACKAGE_NAMES}) + foreach(_idl_file ${${_pkg_name}_IDL_FILES}) + set(_abs_idl_file "${${_pkg_name}_DIR}/../${_idl_file}") + normalize_path(_abs_idl_file "${_abs_idl_file}") + list(APPEND _dependency_files "${_abs_idl_file}") + list(APPEND _dependencies "${_pkg_name}:${_abs_idl_file}") + endforeach() +endforeach() + +set(target_dependencies + "${rosidl_generator_rs_BIN}" + ${rosidl_generator_rs_GENERATOR_FILES} + "${rosidl_generator_rs_TEMPLATE_DIR}/action.rs.em" + "${rosidl_generator_rs_TEMPLATE_DIR}/msg_idiomatic.rs.em" + "${rosidl_generator_rs_TEMPLATE_DIR}/msg_rmw.rs.em" + "${rosidl_generator_rs_TEMPLATE_DIR}/msg.rs.em" + "${rosidl_generator_rs_TEMPLATE_DIR}/srv_idiomatic.rs.em" + "${rosidl_generator_rs_TEMPLATE_DIR}/srv_rmw.rs.em" + "${rosidl_generator_rs_TEMPLATE_DIR}/srv.rs.em" + ${rosidl_generate_interfaces_ABS_IDL_FILES} + ${_idl_files} + ${_dependency_files}) +foreach(dep ${target_dependencies}) + if(NOT EXISTS "${dep}") + message(FATAL_ERROR "Target dependency '${dep}' does not exist") + endif() +endforeach() + +set(generator_arguments_file "${CMAKE_CURRENT_BINARY_DIR}/rosidl_generator_rs__arguments.json") +rosidl_write_generator_arguments( + "${generator_arguments_file}" + PACKAGE_NAME "${PROJECT_NAME}" + IDL_TUPLES "${rosidl_generate_interfaces_IDL_TUPLES}" + ROS_INTERFACE_FILES "${_idl_files}" + ROS_INTERFACE_DEPENDENCIES "${_dependencies}" + OUTPUT_DIR "${_output_path}" + TEMPLATE_DIR "${rosidl_generator_rs_TEMPLATE_DIR}" + TARGET_DEPENDENCIES ${target_dependencies} +) + +file(READ ${generator_arguments_file} contents) +string(REPLACE "\n}" + ",\n \"package_version\": \"${${PROJECT_NAME}_VERSION}\"\n}" contents ${contents}) +file(WRITE ${generator_arguments_file} ${contents}) + +file(MAKE_DIRECTORY "${_output_path}") + +set(_target_suffix "__rs") + +# needed to avoid multiple calls to the Rust generator trick copied from +# https://github.com/ros2/rosidl/blob/master/rosidl_generator_py/cmake/rosidl_generator_py_generate_interfaces.cmake +set(_subdir "${CMAKE_CURRENT_BINARY_DIR}/${rosidl_generate_interfaces_TARGET}${_target_suffix}") +file(MAKE_DIRECTORY "${_subdir}") +file(READ "${rosidl_generator_rs_DIR}/custom_command.cmake" _custom_command) +file(WRITE "${_subdir}/CMakeLists.txt" "${_custom_command}") +add_subdirectory("${_subdir}" ${rosidl_generate_interfaces_TARGET}${_target_suffix}) + +add_dependencies(${rosidl_generate_interfaces_TARGET} ${rosidl_generate_interfaces_TARGET}${_target_suffix}) + +set_property( + SOURCE + ${_generated_common_rs_files} + ${_generated_msg_rs_files} + ${_generated_srv_rs_files} + ${_generated_action_rs_files} + PROPERTY GENERATED 1) + +set(_rsext_suffix "__rsext") +if(NOT rosidl_generate_interfaces_SKIP_INSTALL) + ament_index_register_resource("rust_packages") + install( + DIRECTORY "${_output_path}/rust" + DESTINATION "share/${PROJECT_NAME}" + ) +endif() + +if(BUILD_TESTING AND rosidl_generate_interfaces_ADD_LINTER_TESTS) + if( + NOT _generated_msg_rs_files STREQUAL "" OR + NOT _generated_srv_rs_files STREQUAL "" OR + NOT _generated_action_rs_files STREQUAL "" + ) + # TODO(esteve): add linters for Rust files + endif() +endif() diff --git a/rosidl_generator_rs/package.xml b/rosidl_generator_rs/package.xml new file mode 100644 index 0000000..bd6124c --- /dev/null +++ b/rosidl_generator_rs/package.xml @@ -0,0 +1,42 @@ + + + + rosidl_generator_rs + 0.4.1 + Generate the ROS interfaces in Rust. + Esteve Fernandez + Apache License 2.0 + Esteve Fernandez + + ament_cmake + ros_environment + + rosidl_runtime_rs + + ament_cmake + ros_environment + rosidl_cmake + rosidl_pycommon + rosidl_runtime_rs + rosidl_typesupport_c + rosidl_typesupport_interface + + + rosidl_generator_c + + rosidl_parser + + ament_cmake_gtest + ament_lint_auto + ament_lint_common + rosidl_cmake + rosidl_pycommon + rosidl_generator_c + + rosidl_generator_packages + rosidl_runtime_packages + + + ament_cmake + + diff --git a/rosidl_generator_rs/resource/Cargo.toml.em b/rosidl_generator_rs/resource/Cargo.toml.em new file mode 100644 index 0000000..7c8f7fe --- /dev/null +++ b/rosidl_generator_rs/resource/Cargo.toml.em @@ -0,0 +1,20 @@ +[package] +name = "@(package_name)" +version = "@(package_version)" +edition = "2021" + +[dependencies] +rosidl_runtime_rs = "0.4" +serde = { version = "1", optional = true, features = ["derive"] } +serde-big-array = { version = "0.5.1", optional = true } +@[for dep in dependency_packages]@ +@(dep) = "*" +@[end for]@ + +[features] +@{ +serde_features = ["dep:serde", "dep:serde-big-array", "rosidl_runtime_rs/serde"] +for dep in dependency_packages: + serde_features.append("{}/serde".format(dep)) +}@ +serde = @(serde_features) diff --git a/rosidl_generator_rs/resource/action.rs.em b/rosidl_generator_rs/resource/action.rs.em new file mode 100644 index 0000000..5f463f3 --- /dev/null +++ b/rosidl_generator_rs/resource/action.rs.em @@ -0,0 +1,205 @@ +@{ +from rosidl_parser.definition import ( + ACTION_FEEDBACK_MESSAGE_SUFFIX, + ACTION_FEEDBACK_SUFFIX, + ACTION_GOAL_SERVICE_SUFFIX, + ACTION_GOAL_SUFFIX, + ACTION_RESULT_SERVICE_SUFFIX, + ACTION_RESULT_SUFFIX, + SERVICE_REQUEST_MESSAGE_SUFFIX, + SERVICE_RESPONSE_MESSAGE_SUFFIX, +) + +action_msg_specs = [] + +for subfolder, action in action_specs: + action_msg_specs.append((subfolder, action.goal)) + action_msg_specs.append((subfolder, action.result)) + action_msg_specs.append((subfolder, action.feedback)) + action_msg_specs.append((subfolder, action.feedback_message)) + +action_srv_specs = [] + +for subfolder, action in action_specs: + action_srv_specs.append((subfolder, action.send_goal_service)) + action_srv_specs.append((subfolder, action.get_result_service)) +}@ + +pub mod rmw { +@{ +TEMPLATE( + 'msg_rmw.rs.em', + package_name=package_name, interface_path=interface_path, + msg_specs=action_msg_specs, + get_rs_name=get_rs_name, get_rmw_rs_type=get_rmw_rs_type, + pre_field_serde=pre_field_serde, + get_idiomatic_rs_type=get_idiomatic_rs_type, + constant_value_to_rs=constant_value_to_rs) + +TEMPLATE( + 'srv_rmw.rs.em', + package_name=package_name, interface_path=interface_path, + srv_specs=action_srv_specs, + get_rs_name=get_rs_name, get_rmw_rs_type=get_rmw_rs_type, + pre_field_serde=pre_field_serde, + get_idiomatic_rs_type=get_idiomatic_rs_type, + constant_value_to_rs=constant_value_to_rs) +}@ +} // mod rmw + +@{ +TEMPLATE( + 'msg_idiomatic.rs.em', + package_name=package_name, interface_path=interface_path, + msg_specs=action_msg_specs, + get_rs_name=get_rs_name, get_rmw_rs_type=get_rmw_rs_type, + pre_field_serde=pre_field_serde, + get_idiomatic_rs_type=get_idiomatic_rs_type, + constant_value_to_rs=constant_value_to_rs) +}@ + +@{ +TEMPLATE( + 'srv_idiomatic.rs.em', + package_name=package_name, interface_path=interface_path, + srv_specs=action_srv_specs, + get_rs_name=get_rs_name, get_rmw_rs_type=get_rmw_rs_type, + pre_field_serde=pre_field_serde, + get_idiomatic_rs_type=get_idiomatic_rs_type, + constant_value_to_rs=constant_value_to_rs) +}@ + +@[for subfolder, action_spec in action_specs] + +@{ +type_name = action_spec.namespaced_type.name +}@ + +#[link(name = "@(package_name)__rosidl_typesupport_c")] +extern "C" { + fn rosidl_typesupport_c__get_action_type_support_handle__@(package_name)__@(subfolder)__@(type_name)() -> *const std::ffi::c_void; +} + +// Corresponds to @(package_name)__@(subfolder)__@(type_name) +pub struct @(type_name); + +impl rosidl_runtime_rs::Action for @(type_name) { + type Goal = crate::@(subfolder)::@(type_name)@(ACTION_GOAL_SUFFIX); + type Result = crate::@(subfolder)::@(type_name)@(ACTION_RESULT_SUFFIX); + type Feedback = crate::@(subfolder)::@(type_name)@(ACTION_FEEDBACK_SUFFIX); + + fn get_type_support() -> *const std::ffi::c_void { + // SAFETY: No preconditions for this function. + unsafe { rosidl_typesupport_c__get_action_type_support_handle__@(package_name)__@(subfolder)__@(type_name)() } + } +} + +impl rosidl_runtime_rs::ActionImpl for @(type_name) { + type GoalStatusMessage = action_msgs::msg::rmw::GoalStatusArray; + type FeedbackMessage = crate::@(subfolder)::rmw::@(type_name)@(ACTION_FEEDBACK_MESSAGE_SUFFIX); + + type SendGoalService = crate::@(subfolder)::rmw::@(type_name)@(ACTION_GOAL_SERVICE_SUFFIX); + type CancelGoalService = action_msgs::srv::rmw::CancelGoal; + type GetResultService = crate::@(subfolder)::rmw::@(type_name)@(ACTION_RESULT_SERVICE_SUFFIX); + + fn create_goal_request( + goal_id: &[u8; 16], + goal: crate::@(subfolder)::rmw::@(type_name)@(ACTION_GOAL_SUFFIX), + ) -> crate::@(subfolder)::rmw::@(type_name)@(ACTION_GOAL_SERVICE_SUFFIX)@(SERVICE_REQUEST_MESSAGE_SUFFIX) { + crate::@(subfolder)::rmw::@(type_name)@(ACTION_GOAL_SERVICE_SUFFIX)@(SERVICE_REQUEST_MESSAGE_SUFFIX) { + goal_id: unique_identifier_msgs::msg::rmw::UUID { uuid: *goal_id }, + goal, + } + } + + fn get_goal_request_uuid( + request: &crate::@(subfolder)::rmw::@(type_name)@(ACTION_GOAL_SERVICE_SUFFIX)@(SERVICE_REQUEST_MESSAGE_SUFFIX), + ) -> &[u8; 16] { + &request.goal_id.uuid + } + + fn create_goal_response( + accepted: bool, + stamp: (i32, u32), + ) -> crate::@(subfolder)::rmw::@(type_name)@(ACTION_GOAL_SERVICE_SUFFIX)@(SERVICE_RESPONSE_MESSAGE_SUFFIX) { + crate::@(subfolder)::rmw::@(type_name)@(ACTION_GOAL_SERVICE_SUFFIX)@(SERVICE_RESPONSE_MESSAGE_SUFFIX) { + accepted, + stamp: builtin_interfaces::msg::rmw::Time { + sec: stamp.0, + nanosec: stamp.1, + }, + } + } + + fn get_goal_response_accepted( + response: &crate::@(subfolder)::rmw::@(type_name)@(ACTION_GOAL_SERVICE_SUFFIX)@(SERVICE_RESPONSE_MESSAGE_SUFFIX), + ) -> bool { + response.accepted + } + + fn get_goal_response_stamp( + response: &crate::@(subfolder)::rmw::@(type_name)@(ACTION_GOAL_SERVICE_SUFFIX)@(SERVICE_RESPONSE_MESSAGE_SUFFIX), + ) -> (i32, u32) { + (response.stamp.sec, response.stamp.nanosec) + } + + fn create_feedback_message( + goal_id: &[u8; 16], + feedback: crate::@(subfolder)::rmw::@(type_name)@(ACTION_FEEDBACK_SUFFIX), + ) -> crate::@(subfolder)::rmw::@(type_name)@(ACTION_FEEDBACK_MESSAGE_SUFFIX) { + let mut message = crate::@(subfolder)::rmw::@(type_name)@(ACTION_FEEDBACK_MESSAGE_SUFFIX)::default(); + message.goal_id.uuid = *goal_id; + message.feedback = feedback; + message + } + + fn get_feedback_message_uuid( + feedback: &crate::@(subfolder)::rmw::@(type_name)@(ACTION_FEEDBACK_MESSAGE_SUFFIX), + ) -> &[u8; 16] { + &feedback.goal_id.uuid + } + + fn get_feedback_message_feedback( + feedback: &crate::@(subfolder)::rmw::@(type_name)@(ACTION_FEEDBACK_MESSAGE_SUFFIX), + ) -> &crate::@(subfolder)::rmw::@(type_name)@(ACTION_FEEDBACK_SUFFIX) { + &feedback.feedback + } + + fn create_result_request( + goal_id: &[u8; 16], + ) -> crate::@(subfolder)::rmw::@(type_name)@(ACTION_RESULT_SERVICE_SUFFIX)@(SERVICE_REQUEST_MESSAGE_SUFFIX) { + crate::@(subfolder)::rmw::@(type_name)@(ACTION_RESULT_SERVICE_SUFFIX)@(SERVICE_REQUEST_MESSAGE_SUFFIX) { + goal_id: unique_identifier_msgs::msg::rmw::UUID { uuid: *goal_id }, + } + } + + fn get_result_request_uuid( + request: &crate::@(subfolder)::rmw::@(type_name)@(ACTION_RESULT_SERVICE_SUFFIX)@(SERVICE_REQUEST_MESSAGE_SUFFIX), + ) -> &[u8; 16] { + &request.goal_id.uuid + } + + fn create_result_response( + status: i8, + result: crate::@(subfolder)::rmw::@(type_name)@(ACTION_RESULT_SUFFIX), + ) -> crate::@(subfolder)::rmw::@(type_name)@(ACTION_RESULT_SERVICE_SUFFIX)@(SERVICE_RESPONSE_MESSAGE_SUFFIX) { + crate::@(subfolder)::rmw::@(type_name)@(ACTION_RESULT_SERVICE_SUFFIX)@(SERVICE_RESPONSE_MESSAGE_SUFFIX) { + status, + result, + } + } + + fn get_result_response_result( + response: &crate::@(subfolder)::rmw::@(type_name)@(ACTION_RESULT_SERVICE_SUFFIX)@(SERVICE_RESPONSE_MESSAGE_SUFFIX), + ) -> &crate::@(subfolder)::rmw::@(type_name)@(ACTION_RESULT_SUFFIX) { + &response.result + } + + fn get_result_response_status( + response: &crate::@(subfolder)::rmw::@(type_name)@(ACTION_RESULT_SERVICE_SUFFIX)@(SERVICE_RESPONSE_MESSAGE_SUFFIX), + ) -> i8 { + response.status + } +} + +@[end for] diff --git a/rosidl_generator_rs/resource/build.rs.em b/rosidl_generator_rs/resource/build.rs.em new file mode 100644 index 0000000..d861d0e --- /dev/null +++ b/rosidl_generator_rs/resource/build.rs.em @@ -0,0 +1,10 @@ +use std::path::Path; + +fn main() { + let lib_dir = Path::new("../../../lib") + .canonicalize() + .expect("Could not find '../../../lib'"); + // This allows building Rust packages that depend on message crates without + // sourcing the install directory first. + println!("cargo:rustc-link-search={}", lib_dir.display()); +} diff --git a/rosidl_generator_rs/resource/lib.rs.em b/rosidl_generator_rs/resource/lib.rs.em new file mode 100644 index 0000000..1ef7792 --- /dev/null +++ b/rosidl_generator_rs/resource/lib.rs.em @@ -0,0 +1,15 @@ +#![allow(non_camel_case_types)] +#![allow(clippy::derive_partial_eq_without_eq)] +#![allow(clippy::upper_case_acronyms)] + +@[if len(msg_specs) > 0]@ +pub mod msg; +@[end if]@ + +@[if len(srv_specs) > 0]@ +pub mod srv; +@[end if]@ + +@[if len(action_specs) > 0]@ +pub mod action; +@[end if]@ diff --git a/rosidl_generator_rs/resource/msg.rs.em b/rosidl_generator_rs/resource/msg.rs.em new file mode 100644 index 0000000..3f7d10e --- /dev/null +++ b/rosidl_generator_rs/resource/msg.rs.em @@ -0,0 +1,23 @@ +pub mod rmw { +@{ +TEMPLATE( + 'msg_rmw.rs.em', + package_name=package_name, interface_path=interface_path, + msg_specs=msg_specs, + get_rs_name=get_rs_name, get_rmw_rs_type=get_rmw_rs_type, + pre_field_serde=pre_field_serde, + get_idiomatic_rs_type=get_idiomatic_rs_type, + constant_value_to_rs=constant_value_to_rs) +}@ +} // mod rmw + +@{ +TEMPLATE( + 'msg_idiomatic.rs.em', + package_name=package_name, interface_path=interface_path, + msg_specs=msg_specs, + get_rs_name=get_rs_name, get_rmw_rs_type=get_rmw_rs_type, + pre_field_serde=pre_field_serde, + get_idiomatic_rs_type=get_idiomatic_rs_type, + constant_value_to_rs=constant_value_to_rs) +}@ diff --git a/rosidl_generator_rs/resource/msg_idiomatic.rs.em b/rosidl_generator_rs/resource/msg_idiomatic.rs.em new file mode 100644 index 0000000..e6ec288 --- /dev/null +++ b/rosidl_generator_rs/resource/msg_idiomatic.rs.em @@ -0,0 +1,238 @@ +@{ +from rosidl_parser.definition import AbstractGenericString +from rosidl_parser.definition import Array +from rosidl_parser.definition import BasicType +from rosidl_parser.definition import BoundedSequence +from rosidl_parser.definition import BoundedString +from rosidl_parser.definition import NamedType +from rosidl_parser.definition import NamespacedType +from rosidl_parser.definition import UnboundedSequence +from rosidl_parser.definition import UnboundedString +from rosidl_parser.definition import UnboundedWString +}@ + +@# ################################################# +@# ############ Idiomatic message types ############ +@# ################################################# +@# These types use standard Rust containers where possible. +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +@[for subfolder, msg_spec in msg_specs]@ +@{ +type_name = msg_spec.structure.namespaced_type.name +}@ + +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[derive(Clone, Debug, PartialEq, PartialOrd)] +pub struct @(type_name) { +@[for member in msg_spec.structure.members]@ + @(pre_field_serde(member.type))pub @(get_rs_name(member.name)): @(get_idiomatic_rs_type(member.type)), +@[end for]@ +} + +@[if msg_spec.constants]@ +impl @(type_name) { +@[for constant in msg_spec.constants]@ +@{ +comments = getattr(constant, 'get_comment_lines', lambda: [])() +}@ +@[ for line in comments]@ +@[ if line]@ + /// @(line) +@[ else]@ + /// +@[ end if]@ +@[ end for]@ +@[ if isinstance(constant.type, BasicType)]@ + pub const @(get_rs_name(constant.name)): @(get_rmw_rs_type(constant.type)) = @(constant_value_to_rs(constant.type, constant.value)); +@[ elif isinstance(constant.type, AbstractGenericString)]@ + pub const @(get_rs_name(constant.name)): &'static str = @(constant_value_to_rs(constant.type, constant.value)); +@[ else]@ +@{assert False, 'Unhandled constant type: ' + str(constant.type)}@ +@[ end if]@ +@[end for]@ +} +@[end if] + +impl Default for @(type_name) { + fn default() -> Self { +@# This has the benefit of automatically setting the right default values + ::from_rmw_message(crate::@(subfolder)::rmw::@(type_name)::default()) + } +} + +impl rosidl_runtime_rs::Message for @(type_name) { + type RmwMsg = crate::@(subfolder)::rmw::@(type_name); + + fn into_rmw_message(msg_cow: std::borrow::Cow<'_, Self>) -> std::borrow::Cow<'_, Self::RmwMsg> { + match msg_cow { + std::borrow::Cow::Owned(msg) => std::borrow::Cow::Owned(Self::RmwMsg { +@[for member in msg_spec.structure.members]@ +@# +@# +@# == Array == +@[ if isinstance(member.type, Array)]@ +@[ if isinstance(member.type.value_type, UnboundedString) or isinstance(member.type.value_type, UnboundedWString)]@ + @(get_rs_name(member.name)): msg.@(get_rs_name(member.name)) + .map(|elem| elem.as_str().into()), +@[ elif isinstance(member.type.value_type, NamedType) or isinstance(member.type.value_type, NamespacedType)]@ + @(get_rs_name(member.name)): msg.@(get_rs_name(member.name)) + .map(|elem| @(get_idiomatic_rs_type(member.type.value_type))::into_rmw_message(std::borrow::Cow::Owned(elem)).into_owned()), +@[ elif isinstance(member.type.value_type, BasicType)]@ + @(get_rs_name(member.name)): msg.@(get_rs_name(member.name)), +@[ else]@ + @(get_rs_name(member.name)): msg.@(get_rs_name(member.name)).clone(), +@[ end if]@ +@# +@# +@# == UnboundedString + UnboundedWString == +@[ elif isinstance(member.type, UnboundedString) or isinstance(member.type, UnboundedWString)]@ + @(get_rs_name(member.name)): msg.@(get_rs_name(member.name)).as_str().into(), +@# +@# +@# == UnboundedSequence == +@[ elif isinstance(member.type, UnboundedSequence)]@ +@[ if isinstance(member.type.value_type, UnboundedString) or isinstance(member.type.value_type, UnboundedWString)]@ + @(get_rs_name(member.name)): msg.@(get_rs_name(member.name)) + .into_iter() + .map(|elem| elem.as_str().into()) + .collect(), +@[ elif isinstance(member.type.value_type, NamedType) or isinstance(member.type.value_type, NamespacedType)]@ + @(get_rs_name(member.name)): msg.@(get_rs_name(member.name)) + .into_iter() + .map(|elem| @(get_idiomatic_rs_type(member.type.value_type))::into_rmw_message(std::borrow::Cow::Owned(elem)).into_owned()) + .collect(), +@[ else]@ + @(get_rs_name(member.name)): msg.@(get_rs_name(member.name)).into(), +@[ end if]@ +@# +@# +@# == NamedType + NamespacedType == +@[ elif isinstance(member.type, NamedType) or isinstance(member.type, NamespacedType)]@ + @(get_rs_name(member.name)): @(get_idiomatic_rs_type(member.type))::into_rmw_message(std::borrow::Cow::Owned(msg.@(get_rs_name(member.name)))).into_owned(), +@# +@# +@# == Bounded and basic types == +@[ else]@ + @(get_rs_name(member.name)): msg.@(get_rs_name(member.name)), +@[ end if]@ +@[end for]@ + }), + std::borrow::Cow::Borrowed(msg) => std::borrow::Cow::Owned(Self::RmwMsg { +@[for member in msg_spec.structure.members]@ +@# +@# +@# == Array == +@[ if isinstance(member.type, Array)]@ +@[ if isinstance(member.type.value_type, UnboundedString) or isinstance(member.type.value_type, UnboundedWString)]@ + @(get_rs_name(member.name)): msg.@(get_rs_name(member.name)) + .iter() + .map(|elem| elem.as_str().into()) + .collect::>() + .try_into() + .unwrap(), +@[ elif isinstance(member.type.value_type, NamedType) or isinstance(member.type.value_type, NamespacedType)]@ + @(get_rs_name(member.name)): msg.@(get_rs_name(member.name)) + .iter() + .map(|elem| @(get_idiomatic_rs_type(member.type.value_type))::into_rmw_message(std::borrow::Cow::Borrowed(elem)).into_owned()) + .collect::>() + .try_into() + .unwrap(), +@[ elif isinstance(member.type.value_type, BasicType)]@ + @(get_rs_name(member.name)): msg.@(get_rs_name(member.name)), +@[ else]@ + @(get_rs_name(member.name)): msg.@(get_rs_name(member.name)).clone(), +@[ end if]@ +@# +@# +@# == UnboundedString + UnboundedWString == +@[ elif isinstance(member.type, UnboundedString) or isinstance(member.type, UnboundedWString)]@ + @(get_rs_name(member.name)): msg.@(get_rs_name(member.name)).as_str().into(), +@# +@# +@# == UnboundedSequence == +@[ elif isinstance(member.type, UnboundedSequence)]@ +@[ if isinstance(member.type.value_type, UnboundedString) or isinstance(member.type.value_type, UnboundedWString)]@ + @(get_rs_name(member.name)): msg.@(get_rs_name(member.name)) + .iter() + .map(|elem| elem.as_str().into()) + .collect(), +@[ elif isinstance(member.type.value_type, NamedType) or isinstance(member.type.value_type, NamespacedType)]@ + @(get_rs_name(member.name)): msg.@(get_rs_name(member.name)) + .iter() + .map(|elem| @(get_idiomatic_rs_type(member.type.value_type))::into_rmw_message(std::borrow::Cow::Borrowed(elem)).into_owned()) + .collect(), +@[ else]@ + @(get_rs_name(member.name)): msg.@(get_rs_name(member.name)).as_slice().into(), +@[ end if]@ +@# +@# +@# == NamedType + NamespacedType == +@[ elif isinstance(member.type, NamedType) or isinstance(member.type, NamespacedType)]@ + @(get_rs_name(member.name)): @(get_idiomatic_rs_type(member.type))::into_rmw_message(std::borrow::Cow::Borrowed(&msg.@(get_rs_name(member.name)))).into_owned(), +@# +@# +@# == BasicType == +@[ elif isinstance(member.type, BasicType)]@ + @(get_rs_name(member.name)): msg.@(get_rs_name(member.name)), +@# +@# +@# == Bounded types == +@[ else]@ + @(get_rs_name(member.name)): msg.@(get_rs_name(member.name)).clone(), +@[ end if]@ +@[end for]@ + }) + } + } + + fn from_rmw_message(msg: Self::RmwMsg) -> Self { + Self { +@[for member in msg_spec.structure.members]@ +@# +@# +@# == Array == +@[ if isinstance(member.type, Array)]@ +@[ if isinstance(member.type.value_type, UnboundedString) or isinstance(member.type.value_type, UnboundedWString)]@ + @(get_rs_name(member.name)): msg.@(get_rs_name(member.name)) + .map(|elem| elem.to_string()), +@[ elif isinstance(member.type.value_type, NamedType) or isinstance(member.type.value_type, NamespacedType)]@ + @(get_rs_name(member.name)): msg.@(get_rs_name(member.name)) + .map(@(get_idiomatic_rs_type(member.type.value_type))::from_rmw_message), +@[ else]@ + @(get_rs_name(member.name)): msg.@(get_rs_name(member.name)), +@[ end if]@ +@# +@# +@# == UnboundedSequence == +@[ elif isinstance(member.type, UnboundedSequence)]@ + @(get_rs_name(member.name)): msg.@(get_rs_name(member.name)) + .into_iter() +@[ if isinstance(member.type.value_type, UnboundedString) or isinstance(member.type.value_type, UnboundedWString)]@ + .map(|elem| elem.to_string()) +@[ elif isinstance(member.type.value_type, NamedType) or isinstance(member.type.value_type, NamespacedType)]@ + .map(@(get_idiomatic_rs_type(member.type.value_type))::from_rmw_message) +@[ end if]@ + .collect(), +@# +@# +@# == UnboundedString + UnboundedWString == +@[ elif isinstance(member.type, UnboundedString) or isinstance(member.type, UnboundedWString)]@ + @(get_rs_name(member.name)): msg.@(get_rs_name(member.name)).to_string(), +@# +@# +@# == NamedType + NamespacedType == +@[ elif isinstance(member.type, NamedType) or isinstance(member.type, NamespacedType)]@ + @(get_rs_name(member.name)): @(get_idiomatic_rs_type(member.type))::from_rmw_message(msg.@(get_rs_name(member.name))), +@# +@# +@# == Bounded and basic types == +@[ else]@ + @(get_rs_name(member.name)): msg.@(get_rs_name(member.name)), +@[ end if]@ +@[end for]@ + } + } +} + +@[end for] diff --git a/rosidl_generator_rs/resource/msg_rmw.rs.em b/rosidl_generator_rs/resource/msg_rmw.rs.em new file mode 100644 index 0000000..fbedd6d --- /dev/null +++ b/rosidl_generator_rs/resource/msg_rmw.rs.em @@ -0,0 +1,112 @@ +@{ +from rosidl_parser.definition import AbstractGenericString +from rosidl_parser.definition import Array +from rosidl_parser.definition import BasicType +from rosidl_parser.definition import BoundedSequence +from rosidl_parser.definition import BoundedString +from rosidl_parser.definition import NamedType +from rosidl_parser.definition import NamespacedType +from rosidl_parser.definition import UnboundedSequence +from rosidl_parser.definition import UnboundedString +from rosidl_parser.definition import UnboundedWString +}@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +@[for subfolder, msg_spec in msg_specs]@ +@{ +type_name = msg_spec.structure.namespaced_type.name +}@ + +#[link(name = "@(package_name)__rosidl_typesupport_c")] +extern "C" { + fn rosidl_typesupport_c__get_message_type_support_handle__@(package_name)__@(subfolder)__@(type_name)() -> *const std::ffi::c_void; +} + +#[link(name = "@(package_name)__rosidl_generator_c")] +extern "C" { + fn @(package_name)__@(subfolder)__@(type_name)__init(msg: *mut @(type_name)) -> bool; + fn @(package_name)__@(subfolder)__@(type_name)__Sequence__init(seq: *mut rosidl_runtime_rs::Sequence<@(type_name)>, size: usize) -> bool; + fn @(package_name)__@(subfolder)__@(type_name)__Sequence__fini(seq: *mut rosidl_runtime_rs::Sequence<@(type_name)>); + fn @(package_name)__@(subfolder)__@(type_name)__Sequence__copy(in_seq: &rosidl_runtime_rs::Sequence<@(type_name)>, out_seq: *mut rosidl_runtime_rs::Sequence<@(type_name)>) -> bool; +} + +@# Drop is not needed, since the default drop glue does the same as fini here: +@# it just calls the drop/fini functions of all fields +// Corresponds to @(package_name)__@(subfolder)__@(type_name) +#[repr(C)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[derive(Clone, Debug, PartialEq, PartialOrd)] +pub struct @(type_name) { +@[for member in msg_spec.structure.members]@ + @(pre_field_serde(member.type))pub @(get_rs_name(member.name)): @(get_rmw_rs_type(member.type)), +@[end for]@ +} + +@[if msg_spec.constants]@ +impl @(type_name) { +@[for constant in msg_spec.constants]@ +@{ +comments = getattr(constant, 'get_comment_lines', lambda: [])() +}@ +@[ for line in comments]@ +@[ if line]@ + /// @(line) +@[ else]@ + /// +@[ end if]@ +@[ end for]@ +@[ if isinstance(constant.type, BasicType)]@ + pub const @(get_rs_name(constant.name)): @(get_rmw_rs_type(constant.type)) = @(constant_value_to_rs(constant.type, constant.value)); +@[ elif isinstance(constant.type, AbstractGenericString)]@ + pub const @(get_rs_name(constant.name)): &'static str = @(constant_value_to_rs(constant.type, constant.value)); +@[ else]@ +@{assert False, 'Unhandled constant type: ' + str(constant.type)}@ +@[ end if]@ +@[end for]@ +} +@[end if] + +impl Default for @(type_name) { + fn default() -> Self { + unsafe { +@# // SAFETY: This is safe since a zeroed bit pattern always forms a valid message. + let mut msg = std::mem::zeroed(); +@# // SAFETY: This is safe since the precondititons for init() are fulfilled by giving it a zeroed message. + if !@(package_name)__@(subfolder)__@(type_name)__init(&mut msg as *mut _) { + panic!("Call to @(package_name)__@(subfolder)__@(type_name)__init() failed"); + } + msg + } + } +} + +impl rosidl_runtime_rs::SequenceAlloc for @(type_name) { + fn sequence_init(seq: &mut rosidl_runtime_rs::Sequence, size: usize) -> bool { + // SAFETY: This is safe since the pointer is guaranteed to be valid/initialized. + unsafe { @(package_name)__@(subfolder)__@(type_name)__Sequence__init(seq as *mut _, size) } + } + fn sequence_fini(seq: &mut rosidl_runtime_rs::Sequence) { + // SAFETY: This is safe since the pointer is guaranteed to be valid/initialized. + unsafe { @(package_name)__@(subfolder)__@(type_name)__Sequence__fini(seq as *mut _) } + } + fn sequence_copy(in_seq: &rosidl_runtime_rs::Sequence, out_seq: &mut rosidl_runtime_rs::Sequence) -> bool { + // SAFETY: This is safe since the pointer is guaranteed to be valid/initialized. + unsafe { @(package_name)__@(subfolder)__@(type_name)__Sequence__copy(in_seq, out_seq as *mut _) } + } +} + +impl rosidl_runtime_rs::Message for @(type_name) { + type RmwMsg = Self; + fn into_rmw_message(msg_cow: std::borrow::Cow<'_, Self>) -> std::borrow::Cow<'_, Self::RmwMsg> { msg_cow } + fn from_rmw_message(msg: Self::RmwMsg) -> Self { msg } +} + +impl rosidl_runtime_rs::RmwMessage for @(type_name) where Self: Sized { + const TYPE_NAME: &'static str = "@(package_name)/@(subfolder)/@(type_name)"; + fn get_type_support() -> *const std::ffi::c_void { + // SAFETY: No preconditions for this function. + unsafe { rosidl_typesupport_c__get_message_type_support_handle__@(package_name)__@(subfolder)__@(type_name)() } + } +} + +@[end for] diff --git a/rosidl_generator_rs/resource/srv.rs.em b/rosidl_generator_rs/resource/srv.rs.em new file mode 100644 index 0000000..dd99e8e --- /dev/null +++ b/rosidl_generator_rs/resource/srv.rs.em @@ -0,0 +1,23 @@ +@{ +TEMPLATE( + 'srv_idiomatic.rs.em', + package_name=package_name, interface_path=interface_path, + srv_specs=srv_specs, + get_rs_name=get_rs_name, get_rmw_rs_type=get_rmw_rs_type, + pre_field_serde=pre_field_serde, + get_idiomatic_rs_type=get_idiomatic_rs_type, + constant_value_to_rs=constant_value_to_rs) +} + +pub mod rmw { +@{ +TEMPLATE( + 'srv_rmw.rs.em', + package_name=package_name, interface_path=interface_path, + srv_specs=srv_specs, + get_rs_name=get_rs_name, get_rmw_rs_type=get_rmw_rs_type, + pre_field_serde=pre_field_serde, + get_idiomatic_rs_type=get_idiomatic_rs_type, + constant_value_to_rs=constant_value_to_rs) +}@ +} // mod rmw diff --git a/rosidl_generator_rs/resource/srv_idiomatic.rs.em b/rosidl_generator_rs/resource/srv_idiomatic.rs.em new file mode 100644 index 0000000..660f1a6 --- /dev/null +++ b/rosidl_generator_rs/resource/srv_idiomatic.rs.em @@ -0,0 +1,44 @@ +@{ +req_res_specs = [] + +for subfolder, service in srv_specs: + req_res_specs.append((subfolder, service.request_message)) + req_res_specs.append((subfolder, service.response_message)) +}@ + +@{ +TEMPLATE( + 'msg_idiomatic.rs.em', + package_name=package_name, interface_path=interface_path, + msg_specs=req_res_specs, + get_rs_name=get_rs_name, get_rmw_rs_type=get_rmw_rs_type, + pre_field_serde=pre_field_serde, + get_idiomatic_rs_type=get_idiomatic_rs_type, + constant_value_to_rs=constant_value_to_rs) +}@ + +@[for subfolder, srv_spec in srv_specs] + +@{ +type_name = srv_spec.namespaced_type.name +}@ + +#[link(name = "@(package_name)__rosidl_typesupport_c")] +extern "C" { + fn rosidl_typesupport_c__get_service_type_support_handle__@(package_name)__@(subfolder)__@(type_name)() -> *const std::ffi::c_void; +} + +// Corresponds to @(package_name)__@(subfolder)__@(type_name) +pub struct @(type_name); + +impl rosidl_runtime_rs::Service for @(type_name) { + type Request = crate::@(subfolder)::@(type_name)_Request; + type Response = crate::@(subfolder)::@(type_name)_Response; + + fn get_type_support() -> *const std::ffi::c_void { + // SAFETY: No preconditions for this function. + unsafe { rosidl_typesupport_c__get_service_type_support_handle__@(package_name)__@(subfolder)__@(type_name)() } + } +} + +@[end for] diff --git a/rosidl_generator_rs/resource/srv_rmw.rs.em b/rosidl_generator_rs/resource/srv_rmw.rs.em new file mode 100644 index 0000000..6ba55f1 --- /dev/null +++ b/rosidl_generator_rs/resource/srv_rmw.rs.em @@ -0,0 +1,44 @@ +@{ +req_res_specs = [] + +for subfolder, service in srv_specs: + req_res_specs.append((subfolder, service.request_message)) + req_res_specs.append((subfolder, service.response_message)) +}@ + +@{ +TEMPLATE( + 'msg_rmw.rs.em', + package_name=package_name, interface_path=interface_path, + msg_specs=req_res_specs, + get_rs_name=get_rs_name, get_rmw_rs_type=get_rmw_rs_type, + pre_field_serde=pre_field_serde, + get_idiomatic_rs_type=get_idiomatic_rs_type, + constant_value_to_rs=constant_value_to_rs) +}@ + +@[for subfolder, srv_spec in srv_specs] + +@{ +type_name = srv_spec.namespaced_type.name +}@ + + #[link(name = "@(package_name)__rosidl_typesupport_c")] + extern "C" { + fn rosidl_typesupport_c__get_service_type_support_handle__@(package_name)__@(subfolder)__@(type_name)() -> *const std::ffi::c_void; + } + + // Corresponds to @(package_name)__@(subfolder)__@(type_name) + pub struct @(type_name); + + impl rosidl_runtime_rs::Service for @(type_name) { + type Request = crate::@(subfolder)::rmw::@(type_name)_Request; + type Response = crate::@(subfolder)::rmw::@(type_name)_Response; + + fn get_type_support() -> *const std::ffi::c_void { + // SAFETY: No preconditions for this function. + unsafe { rosidl_typesupport_c__get_service_type_support_handle__@(package_name)__@(subfolder)__@(type_name)() } + } + } + +@[end for] diff --git a/rosidl_generator_rs/rosidl_generator_rs-extras.cmake.in b/rosidl_generator_rs/rosidl_generator_rs-extras.cmake.in new file mode 100644 index 0000000..14ba02d --- /dev/null +++ b/rosidl_generator_rs/rosidl_generator_rs-extras.cmake.in @@ -0,0 +1,8 @@ +# generated from rosidl_generator_rs/rosidl_generator_rs-extras.cmake +find_package(rosidl_typesupport_c REQUIRED) +include("${CMAKE_CURRENT_LIST_DIR}/register_rs.cmake") +rosidl_generator_rs_extras( + "${rosidl_generator_rs_DIR}/../../../lib/rosidl_generator_rs/rosidl_generator_rs" + "${rosidl_generator_rs_DIR}/../../../@PYTHON_INSTALL_DIR@/rosidl_generator_rs/__init__.py" + "${rosidl_generator_rs_DIR}/../resource" +) diff --git a/rosidl_generator_rs/rosidl_generator_rs/__init__.py b/rosidl_generator_rs/rosidl_generator_rs/__init__.py new file mode 100644 index 0000000..b7850a6 --- /dev/null +++ b/rosidl_generator_rs/rosidl_generator_rs/__init__.py @@ -0,0 +1,374 @@ +# Copyright 2016-2017 Esteve Fernandez +# +# 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 os +import pathlib + +from pathlib import Path + +if os.environ['ROS_DISTRO'] <= 'humble': + import rosidl_cmake as rosidl_pycommon +else: + import rosidl_pycommon + +from rosidl_parser.definition import AbstractGenericString +from rosidl_parser.definition import AbstractNestedType +from rosidl_parser.definition import AbstractSequence +from rosidl_parser.definition import AbstractString +from rosidl_parser.definition import AbstractWString +from rosidl_parser.definition import Action +from rosidl_parser.definition import Array +from rosidl_parser.definition import BasicType +from rosidl_parser.definition import BoundedSequence +from rosidl_parser.definition import BoundedString +from rosidl_parser.definition import BoundedWString +from rosidl_parser.definition import IdlContent +from rosidl_parser.definition import IdlLocator +from rosidl_parser.definition import Message +from rosidl_parser.definition import NamespacedType +from rosidl_parser.definition import Service +from rosidl_parser.definition import UnboundedSequence +from rosidl_parser.definition import UnboundedString +from rosidl_parser.definition import UnboundedWString + +from rosidl_parser.parser import parse_idl_file + + +# Taken from http://stackoverflow.com/a/6425628 +def convert_lower_case_underscore_to_camel_case(word): + return ''.join(x.capitalize() or '_' for x in word.split('_')) + + +def generate_rs(generator_arguments_file, typesupport_impls): + args = rosidl_pycommon.read_generator_arguments(generator_arguments_file) + package_name = args['package_name'] + + # expand init modules for each directory + modules = {} + idl_content = IdlContent() + dependency_packages = set() + + (Path(args['output_dir']) / 'rust/src').mkdir(parents=True, exist_ok=True) + + for dep_tuple in args.get('ros_interface_dependencies', []): + dep_parts = dep_tuple.split(':', 1) + assert len(dep_parts) == 2 + if dep_parts[0] != package_name: + dependency_packages.add(dep_parts[0]) + + for idl_tuple in args.get('idl_tuples', []): + idl_parts = idl_tuple.rsplit(':', 1) + assert len(idl_parts) == 2 + + idl_rel_path = pathlib.Path(idl_parts[1]) + idl_stems = modules.setdefault(str(idl_rel_path.parent), set()) + idl_stems.add(idl_rel_path.stem) + + locator = IdlLocator(*idl_parts) + idl_file = parse_idl_file(locator) + idl_content.elements += idl_file.content.elements + + typesupport_impls = typesupport_impls.split(';') + + template_dir = args['template_dir'] + + mapping_msgs = { + os.path.join(template_dir, 'msg.rs.em'): ['rust/src/%s.rs'], + } + + mapping_srvs = { + os.path.join(template_dir, 'srv.rs.em'): ['rust/src/%s.rs'], + } + + mapping_actions = { + os.path.join(template_dir, 'action.rs.em'): ['rust/src/%s.rs'], + } + + # Ensure the required templates exist + for template_file in mapping_msgs.keys(): + assert os.path.exists(template_file), \ + 'Messages template file %s not found' % template_file + for template_file in mapping_srvs.keys(): + assert os.path.exists(template_file), \ + 'Services template file %s not found' % template_file + for template_file in mapping_actions.keys(): + assert os.path.exists(template_file), \ + 'Actions template file %s not found' % template_file + + data = { + 'pre_field_serde': pre_field_serde, + 'get_rmw_rs_type': make_get_rmw_rs_type(args['package_name']), + 'get_rs_name': get_rs_name, + 'get_idiomatic_rs_type': make_get_idiomatic_rs_type(args['package_name']), + 'constant_value_to_rs': constant_value_to_rs, + 'value_to_rs': value_to_rs, + 'convert_camel_case_to_lower_case_underscore': + rosidl_pycommon.convert_camel_case_to_lower_case_underscore, + 'convert_lower_case_underscore_to_camel_case': + convert_lower_case_underscore_to_camel_case, + 'msg_specs': [], + 'srv_specs': [], + 'action_specs': [], + 'package_name': args['package_name'], + 'typesupport_impls': typesupport_impls, + 'interface_path': idl_rel_path, + } + + latest_target_timestamp = rosidl_pycommon.get_newest_modification_time( + args['target_dependencies']) + + for message in idl_content.get_elements_of_type(Message): + data['msg_specs'].append(('msg', message)) + + for service in idl_content.get_elements_of_type(Service): + data['srv_specs'].append(('srv', service)) + + for action in idl_content.get_elements_of_type(Action): + data['action_specs'].append(('action', action)) + + if data['msg_specs']: + for template_file, generated_filenames in mapping_msgs.items(): + for generated_filename in generated_filenames: + generated_file = os.path.join(args['output_dir'], + generated_filename % 'msg') + rosidl_pycommon.expand_template( + os.path.join(template_dir, template_file), + data.copy(), + generated_file, + minimum_timestamp=latest_target_timestamp) + + if data['srv_specs']: + for template_file, generated_filenames in mapping_srvs.items(): + for generated_filename in generated_filenames: + generated_file = os.path.join(args['output_dir'], + generated_filename % 'srv') + rosidl_pycommon.expand_template( + os.path.join(template_dir, template_file), + data.copy(), + generated_file, + minimum_timestamp=latest_target_timestamp) + + if data['action_specs']: + for template_file, generated_filenames in mapping_actions.items(): + for generated_filename in generated_filenames: + generated_file = os.path.join(args['output_dir'], + generated_filename % 'action') + rosidl_pycommon.expand_template( + os.path.join(template_dir, template_file), + data.copy(), + generated_file, + minimum_timestamp=latest_target_timestamp) + + rosidl_pycommon.expand_template( + os.path.join(template_dir, 'lib.rs.em'), + data.copy(), + os.path.join(args['output_dir'], 'rust/src/lib.rs'), + minimum_timestamp=latest_target_timestamp) + + cargo_toml_data = { + 'dependency_packages': dependency_packages, + 'package_name': args['package_name'], + 'package_version': args['package_version'], + } + rosidl_pycommon.expand_template( + os.path.join(template_dir, 'Cargo.toml.em'), + cargo_toml_data, + os.path.join(args['output_dir'], 'rust/Cargo.toml'), + minimum_timestamp=latest_target_timestamp) + + rosidl_pycommon.expand_template( + os.path.join(template_dir, 'build.rs.em'), + {}, + os.path.join(args['output_dir'], 'rust/build.rs'), + minimum_timestamp=latest_target_timestamp) + + return 0 + +def get_rs_name(name): + keywords = [ + # strict keywords + 'as', 'break', 'const', 'continue', 'crate', 'else', 'enum', 'extern', 'false', 'fn', 'for', 'if', 'for', + 'impl', 'in', 'let', 'loop', 'match', 'mod', 'move', 'mut', 'pub', 'ref', 'return', 'self', 'Self', 'static', + 'struct', 'super', 'trait', 'true', 'type', 'unsafe', 'use', 'where', 'while', + # Edition 2018+ + 'async', 'await', 'dyn', + # Reserved + 'abstract', 'become', 'box', 'do', 'final', 'macro', 'override', 'priv', 'typeof', 'unsized', 'virtual', + 'yield', 'try' + ] + # If the field name is a reserved keyword in Rust append an underscore + return name if not name in keywords else name + '_' + +def escape_string(s): + s = s.replace('\\', '\\\\') + s = s.replace("'", "\\'") + return s + + +def value_to_rs(type_, value): + assert type_.is_primitive_type() + assert value is not None + + if not type_.is_array: + return primitive_value_to_rs(type_, value) + + rs_values = [] + for single_value in value: + rs_value = primitive_value_to_rs(type_, single_value) + rs_values.append(rs_value) + return '{%s}' % ', '.join(rs_values) + + +def primitive_value_to_rs(type_, value): + assert type_.is_primitive_type() + assert value is not None + + if type_.type == 'bool': + return 'true' if value else 'false' + + if type_.type in [ + 'byte', + 'char', + 'wchar', + 'int8', + 'uint8', + 'int16', + 'uint16', + 'int32', + 'uint32', + 'int64', + 'uint64', + 'float64', + ]: + return str(value) + + if type_.type == 'float32': + return '%sf' % value + + if type_.type == 'string': + return '"%s"' % escape_string(value) + + assert False, "unknown primitive type '%s'" % type_ + + +def constant_value_to_rs(type_, value): + assert value is not None + + if isinstance(type_, BasicType): + if type_.typename == 'boolean': + return 'true' if value else 'false' + elif type_.typename == 'float32': + return '%sf' % value + return str(value) + + if isinstance(type_, AbstractGenericString): + return '"%s"' % escape_string(value) + + assert False, "unknown constant type '%s'" % type_ + +# Type hierarchy: +# +# AbstractType +# - AbstractNestableType +# - AbstractGenericString +# - AbstractString +# - BoundedString +# - UnboundedString +# - AbstractWString +# - BoundedWString +# - UnboundedWString +# - BasicType +# - NamedType +# - NamespacedType +# - AbstractNestedType +# - Array +# - AbstractSequence +# - BoundedSequence +# - UnboundedSequence + + +def pre_field_serde(type_): + if isinstance(type_, Array) and type_.size > 32: + return '#[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))]\n ' + else: + return '' + + +def make_get_idiomatic_rs_type(package_name): + get_rmw_rs_type = make_get_rmw_rs_type(package_name) + def get_idiomatic_rs_type(type_): + if isinstance(type_, UnboundedString) or isinstance(type_, UnboundedWString): + return 'std::string::String' + elif isinstance(type_, UnboundedSequence): + return 'Vec<{}>'.format(get_idiomatic_rs_type(type_.value_type)) + elif isinstance(type_, NamespacedType): + return '::'.join(type_.namespaced_name()).replace(package_name, 'crate') + elif isinstance(type_, Array): + return '[{}; {}]'.format(get_idiomatic_rs_type(type_.value_type), type_.size) + else: + return get_rmw_rs_type(type_) + return get_idiomatic_rs_type + +def make_get_rmw_rs_type(package_name): + def get_rmw_rs_type(type_): + if isinstance(type_, NamespacedType): + parts = list(type_.namespaced_name()) + parts.insert(-1, 'rmw') + return '::'.join(parts).replace(package_name, 'crate') + elif isinstance(type_, BasicType): + if type_.typename == 'boolean': + return 'bool' + elif type_.typename in ['byte', 'octet']: + return 'u8' + elif type_.typename == 'char': + return 'u8' + elif type_.typename == 'wchar': + return 'u16' + elif type_.typename == 'float': + return 'f32' + elif type_.typename == 'double': + return 'f64' + elif type_.typename == 'int8': + return 'i8' + elif type_.typename == 'uint8': + return 'u8' + elif type_.typename == 'int16': + return 'i16' + elif type_.typename == 'uint16': + return 'u16' + elif type_.typename == 'int32': + return 'i32' + elif type_.typename == 'uint32': + return 'u32' + elif type_.typename == 'int64': + return 'i64' + elif type_.typename == 'uint64': + return 'u64' + elif isinstance(type_, UnboundedString): + return 'rosidl_runtime_rs::String' + elif isinstance(type_, UnboundedWString): + return 'rosidl_runtime_rs::WString' + elif isinstance(type_, BoundedString): + return 'rosidl_runtime_rs::BoundedString<{}>'.format(type_.maximum_size) + elif isinstance(type_, BoundedWString): + return 'rosidl_runtime_rs::BoundedWString<{}>'.format(type_.maximum_size) + elif isinstance(type_, Array): + return '[{}; {}]'.format(get_rmw_rs_type(type_.value_type), type_.size) + elif isinstance(type_, UnboundedSequence): + return 'rosidl_runtime_rs::Sequence<{}>'.format(get_rmw_rs_type(type_.value_type)) + elif isinstance(type_, BoundedSequence): + return 'rosidl_runtime_rs::BoundedSequence<{}, {}>'.format(get_rmw_rs_type(type_.value_type), type_.maximum_size) + + assert False, "unknown type '%s'" % type_.typename + return get_rmw_rs_type