diff --git a/rosidl_actions/rosidl_actions/__init__.py b/rosidl_actions/rosidl_actions/__init__.py index 1c41e7028..12baa80c5 100644 --- a/rosidl_actions/rosidl_actions/__init__.py +++ b/rosidl_actions/rosidl_actions/__init__.py @@ -26,13 +26,13 @@ def generate_msg_and_srv(generator_arguments_file): extension = os.path.splitext(ros_interface_file)[1] subfolder = os.path.basename(os.path.dirname(ros_interface_file)) if extension == '.action': - services, message = parse_action_file(args['package_name'], ros_interface_file) + action = parse_action_file(args['package_name'], ros_interface_file) # create folder if necessary os.makedirs(os.path.join(args['output_dir'], subfolder), exist_ok=True) generated_folder = os.path.join(args['output_dir'], subfolder) - for service in services: + for service in action.services: srv_file = os.path.join(generated_folder, service.srv_name + '.srv') req_file = os.path.join(generated_folder, service.srv_name + '_Request.msg') rsp_file = os.path.join(generated_folder, service.srv_name + '_Response.msg') @@ -43,6 +43,7 @@ def generate_msg_and_srv(generator_arguments_file): with open(rsp_file, 'w+') as fout: fout.write(str(service.response)) - generated_file = os.path.join(args['output_dir'], subfolder, message.msg_name + '.msg') + generated_file = os.path.join( + args['output_dir'], subfolder, action.feedback.msg_name + '.msg') with open(generated_file, 'w+') as fout: - fout.write(str(message)) + fout.write(str(action.feedback)) diff --git a/rosidl_cmake/cmake/rosidl_generate_action_interfaces.cmake b/rosidl_cmake/cmake/rosidl_generate_action_interfaces.cmake new file mode 100644 index 000000000..9e6610a6c --- /dev/null +++ b/rosidl_cmake/cmake/rosidl_generate_action_interfaces.cmake @@ -0,0 +1,93 @@ +# Copyright 2018 Open Source Robotics Foundation, Inc. +# +# 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. + +# Generate Interfaces for Actions +# +# This executes the extension point ``rosidl_generate_action_interfaces``. +# An extension of this type should expect a list of `.action` files and +# generate new files in response. +# Extensions should create a target that generates the files with the input +# `.action` file as a dependency. +# +# :param target: the _name of the generation target, +# specific generators might use the _name as a prefix for their own +# generation step +# :type target: string +# :param ARGN: a list of include directories where each value might +# be either an absolute path or path relative to the +# CMAKE_INSTALL_PREFIX. +# :type ARGN: list of strings +# :param DEPENDENCY_PACKAGE_NAMES: Packages with interface files generation +# should depend on +# :type DEPENDENCY_PACKAGE_NAMES: list of strings +# :param TARGET_DEPENDENCIES: cmake targets or files the generated target +# should depend on +# :type TARGET_DEPENDENCIES: list of strings +# :param LIBRARY_NAME: the base name of the library, specific generators might +# append their own suffix +# :type LIBRARY_NAME: string +# :param SKIP_INSTALL: if set skip installing the interface files +# :type SKIP_INSTALL: option +# :param ADD_LINTER_TESTS: if set lint the interface files using +# the ``ament_lint`` package +# :type ADD_LINTER_TESTS: option +# +# @public +# +macro(rosidl_generate_action_interfaces target) + cmake_parse_arguments(_ARG_RGAI + "ADD_LINTER_TESTS;SKIP_INSTALL" + "LIBRARY_NAME" "TARGET_DEPENDENCIES" + ${ARGN}) + if(NOT _ARG_RGAI_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "rosidl_generate_action_interfaces() called without any idl " + "files") + endif() + + set(_rgai_idl_files ${_ARG_RGAI_UNPARSED_ARGUMENTS}) + + # Create a custom target + set(_rgai_sub_target "${target}+generate_action_interfaces") + add_custom_target( + ${_rgai_sub_target} ALL + DEPENDS + ${_rgai_idl_files} + ${_ARG_RGAI_TARGET_DEPENDENCIES} + SOURCES + ${_rgai_idl_files} + ) + + # downstream packages need to depend on action_msgs for cancel and status messages + list_append_unique(_ARG_RGAI_DEPENDENCY_PACKAGE_NAMES "action_msgs") + # downstream packages need to depend on builtin_interfaces for builtin_interfaces/Time + list_append_unique(_ARG_RGAI_DEPENDENCY_PACKAGE_NAMES "builtin_interfaces") + ament_export_dependencies(action_msgs) + ament_export_dependencies(builtin_interfaces) + + # A target name that generators may want to use to prefix their own target names + set(rosidl_generate_action_interfaces_TARGET ${target}) + # Give extensions a list of .action files to generate interfaces from + set(rosidl_generate_action_interfaces_IDL_FILES ${_rgai_idl_files}) + # TODO(sloretz) Where is LIBRARY_NAME used? + set(rosidl_generate_action_interfaces_LIBRARY_NAME ${_ARG_RGAI_LIBRARY_NAME}) + # If true the extension should not install anything it generates + set(rosidl_generate_action_interfaces_SKIP_INSTALL ${_ARG_RGAI_SKIP_INSTALL}) + # If true the extension should create tests for language specific linters + set(rosidl_generate_action_interfaces_ADD_LINTER_TESTS ${_ARG_RGAI_ADD_LINTER_TESTS}) + # Packages that generated code should depend on + set(rosidl_generate_action_interfaces_DEPENDENCY_PACKAGE_NAMES ${_ARG_RGAI_DEPENDENCY_PACKAGE_NAMES}) + ament_execute_extensions("rosidl_generate_action_interfaces") + + add_dependencies(${target} ${_rgai_sub_target}) +endmacro() diff --git a/rosidl_cmake/cmake/rosidl_generate_interfaces.cmake b/rosidl_cmake/cmake/rosidl_generate_interfaces.cmake index 3216b0fdc..92a8594ad 100644 --- a/rosidl_cmake/cmake/rosidl_generate_interfaces.cmake +++ b/rosidl_cmake/cmake/rosidl_generate_interfaces.cmake @@ -207,6 +207,32 @@ macro(rosidl_generate_interfaces target) set(rosidl_generate_interfaces_ADD_LINTER_TESTS ${_ARG_ADD_LINTER_TESTS}) ament_execute_extensions("rosidl_generate_interfaces") + if(_action_files) + # Invoke generation for `.action` files + set(_skip_install "") + if(_ARG_SKIP_INSTALL) + set(_skip_install "SKIP_INSTALL") + endif() + set(_add_linter_tests "") + if(_ARG_ADD_LINTER_TESTS) + set(_add_linter_tests "ADD_LINTER_TESTS") + endif() + set(_library_name "") + if(_ARG_LIBRARY_NAME) + set(_library_name "LIBRARY ${_ARG_LIBRARY_NAME}") + endif() + set(_pkg_depends "") + if(_recursive_dependencies) + set(_pkg_depends "DEPENDENCY_PACKAGE_NAMES ${_recursive_dependencies}") + endif() + rosidl_generate_action_interfaces(${target} + ${_skip_install} + ${_add_linter_tests} + ${_library_name} + ${_action_files} + ) + endif() + if(NOT _ARG_SKIP_INSTALL) # install interface files to subfolders based on their extension foreach(_idl_file ${_idl_files}) diff --git a/rosidl_cmake/rosidl_cmake-extras.cmake b/rosidl_cmake/rosidl_cmake-extras.cmake index 1a9e0edff..0ca0e3c38 100644 --- a/rosidl_cmake/rosidl_cmake-extras.cmake +++ b/rosidl_cmake/rosidl_cmake-extras.cmake @@ -28,6 +28,7 @@ macro(_rosidl_cmake_register_package_hook) endmacro() include("${rosidl_cmake_DIR}/rosidl_convert_actions_to_msg_and_srv.cmake") +include("${rosidl_cmake_DIR}/rosidl_generate_action_interfaces.cmake") include("${rosidl_cmake_DIR}/rosidl_generate_interfaces.cmake") include("${rosidl_cmake_DIR}/rosidl_identify_action_idls.cmake") include("${rosidl_cmake_DIR}/rosidl_target_interfaces.cmake") diff --git a/rosidl_generator_c/cmake/register_c.cmake b/rosidl_generator_c/cmake/register_c.cmake index 9ddb1a4fc..8b0d66d10 100644 --- a/rosidl_generator_c/cmake/register_c.cmake +++ b/rosidl_generator_c/cmake/register_c.cmake @@ -19,6 +19,11 @@ macro(rosidl_generator_c_extras BIN GENERATOR_FILES TEMPLATE_DIR) "rosidl_generator_c" "rosidl_generator_c_generate_interfaces.cmake") + ament_register_extension( + "rosidl_generate_action_interfaces" + "rosidl_generator_c" + "rosidl_generator_c_generate_action_interfaces.cmake") + normalize_path(BIN "${BIN}") set(rosidl_generator_c_BIN "${BIN}") diff --git a/rosidl_generator_c/cmake/rosidl_generator_c_generate_action_interfaces.cmake b/rosidl_generator_c/cmake/rosidl_generator_c_generate_action_interfaces.cmake new file mode 100644 index 000000000..860db6465 --- /dev/null +++ b/rosidl_generator_c/cmake/rosidl_generator_c_generate_action_interfaces.cmake @@ -0,0 +1,150 @@ +# Copyright 2018 Open Source Robotics Foundation, Inc. +# +# 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. + +set(_output_path + "${CMAKE_CURRENT_BINARY_DIR}/rosidl_generator_c/${PROJECT_NAME}") +set(_generated_files "") + +foreach(_idl_file ${rosidl_generate_action_interfaces_IDL_FILES}) + get_filename_component(_extension "${_idl_file}" EXT) + get_filename_component(_parent_folder "${_idl_file}" DIRECTORY) + get_filename_component(_parent_folder "${_parent_folder}" NAME) + if(_extension STREQUAL ".action") + set(_allowed_parent_folders "action") + if(NOT _parent_folder IN_LIST _allowed_parent_folders) + message(FATAL_ERROR "Interface file with unknown parent folder: ${_idl_file}") + endif() + else() + message(FATAL_ERROR "Interface file with unknown extension: ${_idl_file}") + endif() + get_filename_component(_msg_name "${_idl_file}" NAME_WE) + string_camel_case_to_lower_case_underscore("${_msg_name}" _header_name) + list(APPEND _generated_files + "${_output_path}/${_parent_folder}/${_header_name}.h" + "${_output_path}/${_parent_folder}/${_header_name}__type_support.h" + ) +endforeach() + +set(_dependency_files "") +set(_dependencies "") +foreach(_pkg_name ${rosidl_generate_action_interfaces_DEPENDENCY_PACKAGE_NAMES}) + foreach(_idl_file ${${_pkg_name}_INTERFACE_FILES}) + get_filename_component(_extension "${_idl_file}" EXT) + if(_extension STREQUAL ".msg") + 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}") + endif() + endforeach() +endforeach() + +set(target_dependencies + "${rosidl_generator_c_BIN}" + ${rosidl_generator_c_GENERATOR_FILES} + "${rosidl_generator_c_TEMPLATE_DIR}/action.h.em" + "${rosidl_generator_c_TEMPLATE_DIR}/action__type_support.h.em" + ${rosidl_generate_action_interfaces_IDL_FILES} + ${_dependency_files}) +foreach(dep ${target_dependencies}) + if(NOT EXISTS "${dep}") + get_property(is_generated SOURCE "${dep}" PROPERTY GENERATED) + if(NOT ${_is_generated}) + message(FATAL_ERROR "Target dependency '${dep}' does not exist") + endif() + endif() +endforeach() + +set(generator_arguments_file "${CMAKE_CURRENT_BINARY_DIR}/rosidl_generator_c__generate_action_interfaces__arguments.json") +rosidl_write_generator_arguments( + "${generator_arguments_file}" + PACKAGE_NAME "${PROJECT_NAME}" + ROS_INTERFACE_FILES "${rosidl_generate_action_interfaces_IDL_FILES}" + ROS_INTERFACE_DEPENDENCIES "${_dependencies}" + OUTPUT_DIR "${_output_path}" + TEMPLATE_DIR "${rosidl_generator_c_TEMPLATE_DIR}" + TARGET_DEPENDENCIES ${target_dependencies} +) + +add_custom_command( + OUTPUT ${_generated_files} + COMMAND ${PYTHON_EXECUTABLE} ${rosidl_generator_c_BIN} + --generator-arguments-file "${generator_arguments_file}" + DEPENDS ${target_dependencies} + COMMENT "Generating C++ type support dispatch for ROS interfaces" + VERBATIM +) + +# generate header to switch between export and import for a specific package +set(_visibility_control_file + "${_output_path}/action/rosidl_generator_c__visibility_control.h") +string(TOUPPER "${PROJECT_NAME}" PROJECT_NAME_UPPER) +configure_file( + "${rosidl_generator_c_TEMPLATE_DIR}/rosidl_generator_c__action_visibility_control.h.in" + "${_visibility_control_file}" + @ONLY +) +list(APPEND _generated_files ${_visibility_control_file}) + +set(_target_suffix "__c__actions") + +if(TARGET ${rosidl_generate_action_interfaces_TARGET}${_target_suffix}) + message(WARNING "Custom target ${rosidl_generate_action_interfaces_TARGET}${_target_suffix} already exists") +else() + add_custom_target( + ${rosidl_generate_action_interfaces_TARGET}${_target_suffix} + DEPENDS + ${_generated_files} + ) +endif() + +add_dependencies( + ${rosidl_generate_action_interfaces_TARGET} + ${rosidl_generate_action_interfaces_TARGET}${_target_suffix} +) + +if(NOT rosidl_generate_action_interfaces_SKIP_INSTALL) + if(NOT _generated_files STREQUAL "") + install( + FILES ${_generated_files} + DESTINATION "include/${PROJECT_NAME}/action" + ) + endif() + ament_export_include_directories(include) +endif() + +if(BUILD_TESTING AND rosidl_generate_action_interfaces_ADD_LINTER_TESTS) + if(NOT _generated_files STREQUAL "") + find_package(ament_cmake_cppcheck REQUIRED) + ament_cppcheck( + TESTNAME "cppcheck_rosidl_generator_c_generate_action_interfaces" + "${_output_path}") + + find_package(ament_cmake_cpplint REQUIRED) + get_filename_component(_cpplint_root "${_output_path}" DIRECTORY) + ament_cpplint( + TESTNAME "cpplint_rosidl_generator_c_generate_action_interfaces" + # the generated code might contain longer lines for templated types + MAX_LINE_LENGTH 999 + ROOT "${_cpplint_root}" + "${_output_path}") + + find_package(ament_cmake_uncrustify REQUIRED) + ament_uncrustify( + TESTNAME "uncrustify_rosidl_generator_c_generate_action_interfaces" + # the generated code might contain longer lines for templated types + MAX_LINE_LENGTH 999 + "${_output_path}") + endif() +endif() diff --git a/rosidl_generator_c/resource/action.h.em b/rosidl_generator_c/resource/action.h.em new file mode 100644 index 000000000..52d94f5bd --- /dev/null +++ b/rosidl_generator_c/resource/action.h.em @@ -0,0 +1,43 @@ +// generated from rosidl_generator_c/resource/action.h.em +// generated code does not contain a copyright notice + +@####################################################################### +@# EmPy template for generating .hp files +@# +@# Context: +@# - spec (rosidl_parser.ActionSpecification) +@# Parsed specification of the .action file +@# - subfolder (string) +@# The subfolder / subnamespace of the message, usually 'action' +@# - get_header_filename_from_msg_name (function) +@####################################################################### +@ +@{ +header_guard_parts = [ + spec.pkg_name, subfolder, + get_header_filename_from_msg_name(spec.action_name) + '_h'] +header_guard_variable = '__'.join([x.upper() for x in header_guard_parts]) + '_' +}@ + +#ifndef @(header_guard_variable) +#define @(header_guard_variable) + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include +#include + +#include <@(spec.pkg_name)/@(subfolder)/@(get_header_filename_from_msg_name(spec.action_name))__feedback.h> +#include <@(spec.pkg_name)/@(subfolder)/@(get_header_filename_from_msg_name(spec.action_name))__goal.h> +#include <@(spec.pkg_name)/@(subfolder)/@(get_header_filename_from_msg_name(spec.action_name))__result.h> +#include "@(spec.pkg_name)/@(subfolder)/@(get_header_filename_from_msg_name(spec.action_name))__type_support.h" + +#ifdef __cplusplus +} +#endif + +#endif // @(header_guard_variable) diff --git a/rosidl_generator_c/resource/action__type_support.h.em b/rosidl_generator_c/resource/action__type_support.h.em new file mode 100644 index 000000000..b9ef63aff --- /dev/null +++ b/rosidl_generator_c/resource/action__type_support.h.em @@ -0,0 +1,47 @@ +// generated from rosidl_generator_c/resource/action__type_support.h.em +// generated code does not contain a copyright notice + +@####################################################################### +@# EmPy template for generating __type_support.h files +@# +@# Context: +@# - spec (rosidl_parser.ActionSpecification) +@# Parsed specification of the .action file +@# - subfolder (string) +@# The subfolder / subnamespace of the message, usually 'action' +@# - get_header_filename_from_msg_name (function) +@####################################################################### +@ +@{ +header_guard_parts = [ + spec.pkg_name, subfolder, + get_header_filename_from_msg_name(spec.action_name) + '__type_support_h'] +header_guard_variable = '__'.join([x.upper() for x in header_guard_parts]) + '_' +}@ + +#ifndef @(header_guard_variable) +#define @(header_guard_variable) + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "rosidl_generator_c/action_type_support_struct.h" +#include "rosidl_typesupport_interface/macros.h" + +#include "@(spec.pkg_name)/@(subfolder)/rosidl_generator_c__visibility_control.h" + +/* *INDENT-OFF* */ +// Forward declare the get type support functions for this type. +ROSIDL_GENERATOR_C_PUBLIC_@(spec.pkg_name)_ACTION +const rosidl_action_type_support_t * +ROSIDL_TYPESUPPORT_INTERFACE__ACTION_SYMBOL_NAME( + rosidl_typesupport_c, @(spec.pkg_name), @(subfolder), @(spec.action_name))(); +/* *INDENT-ON* */ + +#ifdef __cplusplus +} +#endif + +#endif // @(header_guard_variable) diff --git a/rosidl_generator_c/resource/rosidl_generator_c__action_visibility_control.h.in b/rosidl_generator_c/resource/rosidl_generator_c__action_visibility_control.h.in new file mode 100644 index 000000000..103572070 --- /dev/null +++ b/rosidl_generator_c/resource/rosidl_generator_c__action_visibility_control.h.in @@ -0,0 +1,43 @@ +// generated from +// rosidl_generator_c/resource/rosidl_generator_c__action_visibility_control.h.in +// generated code does not contain a copyright notice + +#ifndef @PROJECT_NAME_UPPER@__ACTION__ROSIDL_GENERATOR_C__VISIBILITY_CONTROL_H_ +#define @PROJECT_NAME_UPPER@__ACTION__ROSIDL_GENERATOR_C__VISIBILITY_CONTROL_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +// This logic was borrowed (then namespaced) from the examples on the gcc wiki: +// https://gcc.gnu.org/wiki/Visibility + +#if defined _WIN32 || defined __CYGWIN__ + #ifdef __GNUC__ + #define ROSIDL_GENERATOR_C_EXPORT_@PROJECT_NAME@_ACTION __attribute__ ((dllexport)) + #define ROSIDL_GENERATOR_C_IMPORT_@PROJECT_NAME@_ACTION __attribute__ ((dllimport)) + #else + #define ROSIDL_GENERATOR_C_EXPORT_@PROJECT_NAME@_ACTION __declspec(dllexport) + #define ROSIDL_GENERATOR_C_IMPORT_@PROJECT_NAME@_ACTION __declspec(dllimport) + #endif + #ifdef ROSIDL_GENERATOR_C_BUILDING_DLL_@PROJECT_NAME@_ACTION + #define ROSIDL_GENERATOR_C_PUBLIC_@PROJECT_NAME@_ACTION ROSIDL_GENERATOR_C_EXPORT_@PROJECT_NAME@_ACTION + #else + #define ROSIDL_GENERATOR_C_PUBLIC_@PROJECT_NAME@_ACTION ROSIDL_GENERATOR_C_IMPORT_@PROJECT_NAME@_ACTION + #endif +#else + #define ROSIDL_GENERATOR_C_EXPORT_@PROJECT_NAME@_ACTION __attribute__ ((visibility("default"))) + #define ROSIDL_GENERATOR_C_IMPORT_@PROJECT_NAME@_ACTION + #if __GNUC__ >= 4 + #define ROSIDL_GENERATOR_C_PUBLIC_@PROJECT_NAME@_ACTION __attribute__ ((visibility("default"))) + #else + #define ROSIDL_GENERATOR_C_PUBLIC_@PROJECT_NAME@_ACTION + #endif +#endif + +#ifdef __cplusplus +} +#endif + +#endif // @PROJECT_NAME_UPPER@__ACTION__ROSIDL_GENERATOR_C__VISIBILITY_CONTROL_H_ diff --git a/rosidl_generator_c/rosidl_generator_c/__init__.py b/rosidl_generator_c/rosidl_generator_c/__init__.py index 9d855a689..fa62b96da 100644 --- a/rosidl_generator_c/rosidl_generator_c/__init__.py +++ b/rosidl_generator_c/rosidl_generator_c/__init__.py @@ -18,6 +18,7 @@ from rosidl_cmake import expand_template from rosidl_cmake import get_newest_modification_time from rosidl_cmake import read_generator_arguments +from rosidl_parser import parse_action_file from rosidl_parser import parse_message_file from rosidl_parser import parse_service_file @@ -36,6 +37,10 @@ def generate_c(generator_arguments_file): mapping_srvs = { os.path.join(template_dir, 'srv.h.em'): '%s.h', } + mapping_action = { + os.path.join(template_dir, 'action.h.em'): '%s.h', + os.path.join(template_dir, 'action__type_support.h.em'): '%s__type_support.h', + } for template_file in list(mapping_msgs.keys()) + list(mapping_srvs.keys()): assert os.path.exists(template_file), 'Could not find template: ' + template_file @@ -75,6 +80,17 @@ def generate_c(generator_arguments_file): expand_template( template_file, data, generated_file, minimum_timestamp=latest_target_timestamp) + elif extension == '.action': + spec = parse_action_file(args['package_name'], ros_interface_file) + for template_file, generated_filename in mapping_action.items(): + data = {'spec': spec, 'subfolder': subfolder} + data.update(functions) + generated_file = os.path.join( + args['output_dir'], subfolder, generated_filename % + convert_camel_case_to_lower_case_underscore(spec.action_name)) + expand_template( + template_file, data, generated_file, + minimum_timestamp=latest_target_timestamp) return 0 diff --git a/rosidl_generator_cpp/cmake/register_cpp.cmake b/rosidl_generator_cpp/cmake/register_cpp.cmake index 4b38d7a8b..9a5216e48 100644 --- a/rosidl_generator_cpp/cmake/register_cpp.cmake +++ b/rosidl_generator_cpp/cmake/register_cpp.cmake @@ -19,6 +19,11 @@ macro(rosidl_generator_cpp_extras BIN GENERATOR_FILES TEMPLATE_DIR) "rosidl_generator_cpp" "rosidl_generator_cpp_generate_interfaces.cmake") +ament_register_extension( + "rosidl_generate_action_interfaces" + "rosidl_generator_cpp" + "rosidl_generator_cpp_generate_action_interfaces.cmake") + normalize_path(BIN "${BIN}") set(rosidl_generator_cpp_BIN "${BIN}") diff --git a/rosidl_generator_cpp/cmake/rosidl_generator_cpp_generate_action_interfaces.cmake b/rosidl_generator_cpp/cmake/rosidl_generator_cpp_generate_action_interfaces.cmake new file mode 100644 index 000000000..fbcc43741 --- /dev/null +++ b/rosidl_generator_cpp/cmake/rosidl_generator_cpp_generate_action_interfaces.cmake @@ -0,0 +1,139 @@ +# Copyright 2018 Open Source Robotics Foundation, Inc. +# +# 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. + +set(_output_path + "${CMAKE_CURRENT_BINARY_DIR}/rosidl_generator_cpp/${PROJECT_NAME}") +set(_generated_files "") + +foreach(_idl_file ${rosidl_generate_action_interfaces_IDL_FILES}) + get_filename_component(_extension "${_idl_file}" EXT) + get_filename_component(_parent_folder "${_idl_file}" DIRECTORY) + get_filename_component(_parent_folder "${_parent_folder}" NAME) + if(_extension STREQUAL ".action") + set(_allowed_parent_folders "action") + if(NOT _parent_folder IN_LIST _allowed_parent_folders) + message(FATAL_ERROR "Interface file with unknown parent folder: ${_idl_file}") + endif() + else() + message(FATAL_ERROR "Interface file with unknown extension: ${_idl_file}") + endif() + get_filename_component(_msg_name "${_idl_file}" NAME_WE) + string_camel_case_to_lower_case_underscore("${_msg_name}" _header_name) + list(APPEND _generated_files + "${_output_path}/${_parent_folder}/${_header_name}__struct.hpp" + "${_output_path}/${_parent_folder}/${_header_name}.hpp" + ) +endforeach() + +set(_dependency_files "") +set(_dependencies "") +foreach(_pkg_name ${rosidl_generate_action_interfaces_DEPENDENCY_PACKAGE_NAMES}) + foreach(_idl_file ${${_pkg_name}_INTERFACE_FILES}) + get_filename_component(_extension "${_idl_file}" EXT) + if(_extension STREQUAL ".msg") + 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}") + endif() + endforeach() +endforeach() + +set(target_dependencies + "${rosidl_generator_cpp_BIN}" + ${rosidl_generator_cpp_GENERATOR_FILES} + "${rosidl_generator_cpp_TEMPLATE_DIR}/action__struct.hpp.em" + "${rosidl_generator_cpp_TEMPLATE_DIR}/action.hpp.em" + ${rosidl_generate_action_interfaces_IDL_FILES} + ${_dependency_files}) +foreach(dep ${target_dependencies}) + if(NOT EXISTS "${dep}") + get_property(is_generated SOURCE "${dep}" PROPERTY GENERATED) + if(NOT ${_is_generated}) + message(FATAL_ERROR "Target dependency '${dep}' does not exist") + endif() + endif() +endforeach() + +set(generator_arguments_file "${CMAKE_CURRENT_BINARY_DIR}/rosidl_generator_cpp__generate_action_interfaces__arguments.json") +rosidl_write_generator_arguments( + "${generator_arguments_file}" + PACKAGE_NAME "${PROJECT_NAME}" + ROS_INTERFACE_FILES "${rosidl_generate_action_interfaces_IDL_FILES}" + ROS_INTERFACE_DEPENDENCIES "${_dependencies}" + OUTPUT_DIR "${_output_path}" + TEMPLATE_DIR "${rosidl_generator_cpp_TEMPLATE_DIR}" + TARGET_DEPENDENCIES ${target_dependencies} +) + +add_custom_command( + OUTPUT ${_generated_files} + COMMAND ${PYTHON_EXECUTABLE} ${rosidl_generator_cpp_BIN} + --generator-arguments-file "${generator_arguments_file}" + DEPENDS ${target_dependencies} + COMMENT "Generating C++ type support dispatch for ROS interfaces" + VERBATIM +) + +set(_target_suffix "__cpp__actions") + +if(TARGET ${rosidl_generate_action_interfaces_TARGET}${_target_suffix}) + message(WARNING "Custom target ${rosidl_generate_action_interfaces_TARGET}${_target_suffix} already exists") +else() + add_custom_target( + ${rosidl_generate_action_interfaces_TARGET}${_target_suffix} + DEPENDS + ${_generated_files} + ) +endif() + +add_dependencies( + ${rosidl_generate_action_interfaces_TARGET} + ${rosidl_generate_action_interfaces_TARGET}${_target_suffix} +) + +if(NOT rosidl_generate_action_interfaces_SKIP_INSTALL) + if(NOT _generated_files STREQUAL "") + install( + FILES ${_generated_files} + DESTINATION "include/${PROJECT_NAME}/action" + ) + endif() + ament_export_include_directories(include) +endif() + +if(BUILD_TESTING AND rosidl_generate_action_interfaces_ADD_LINTER_TESTS) + if(NOT _generated_files STREQUAL "") + find_package(ament_cmake_cppcheck REQUIRED) + ament_cppcheck( + TESTNAME "cppcheck_rosidl_generator_cpp_generate_action_interfaces" + "${_output_path}") + + find_package(ament_cmake_cpplint REQUIRED) + get_filename_component(_cpplint_root "${_output_path}" DIRECTORY) + ament_cpplint( + TESTNAME "cpplint_rosidl_generator_cpp_generate_action_interfaces" + # the generated code might contain longer lines for templated types + MAX_LINE_LENGTH 999 + ROOT "${_cpplint_root}" + "${_output_path}") + + find_package(ament_cmake_uncrustify REQUIRED) + ament_uncrustify( + TESTNAME "uncrustify_rosidl_generator_cpp_generate_action_interfaces" + # the generated code might contain longer lines for templated types + MAX_LINE_LENGTH 999 + "${_output_path}") + endif() +endif() diff --git a/rosidl_generator_cpp/resource/action.hpp.em b/rosidl_generator_cpp/resource/action.hpp.em new file mode 100644 index 000000000..549cad240 --- /dev/null +++ b/rosidl_generator_cpp/resource/action.hpp.em @@ -0,0 +1,26 @@ +// generated from rosidl_generator_cpp/resource/action.hpp.em +// generated code does not contain a copyright notice + +@####################################################################### +@# EmPy template for generating .hpp files +@# +@# Context: +@# - spec (rosidl_parser.ActionSpecification) +@# Parsed specification of the .action file +@# - subfolder (string) +@# The subfolder / subnamespace of the message, usually 'action' +@# - get_header_filename_from_msg_name (function) +@####################################################################### +@ +@{ +header_guard_parts = [ + spec.pkg_name, subfolder, + get_header_filename_from_msg_name(spec.action_name) + '_hpp'] +header_guard_variable = '__'.join([x.upper() for x in header_guard_parts]) + '_' +}@ +#ifndef @(header_guard_variable) +#define @(header_guard_variable) + +#include <@(spec.pkg_name)/@(subfolder)/@(get_header_filename_from_msg_name(spec.action_name))__struct.hpp> + +#endif // @(header_guard_variable) diff --git a/rosidl_generator_cpp/resource/action__struct.hpp.em b/rosidl_generator_cpp/resource/action__struct.hpp.em new file mode 100644 index 000000000..75d87ad67 --- /dev/null +++ b/rosidl_generator_cpp/resource/action__struct.hpp.em @@ -0,0 +1,55 @@ +// generated from rosidl_generator_cpp/resource/action__struct.hpp.em +// generated code does not contain a copyright notice + +@####################################################################### +@# EmPy template for generating __struct.hpp files +@# +@# Context: +@# - spec (rosidl_parser.ActionSpecification) +@# Parsed specification of the .action file +@# - subfolder (string) +@# The subfolder / subnamespace of the message, usually 'action' +@# - get_header_filename_from_msg_name (function) +@####################################################################### +@ +@{ +header_guard_parts = [ + spec.pkg_name, subfolder, + get_header_filename_from_msg_name(spec.action_name) + '__struct_hpp'] +header_guard_variable = '__'.join([x.upper() for x in header_guard_parts]) + '_' +}@ +#ifndef @(header_guard_variable) +#define @(header_guard_variable) + +#include +#include +#include +#include <@(spec.pkg_name)/@(subfolder)/@(get_header_filename_from_msg_name(spec.action_name))__feedback.hpp> +#include <@(spec.pkg_name)/@(subfolder)/@(get_header_filename_from_msg_name(spec.action_name))__goal.hpp> +#include <@(spec.pkg_name)/@(subfolder)/@(get_header_filename_from_msg_name(spec.action_name))__result.hpp> + +namespace @(spec.pkg_name) +{ + +namespace @(subfolder) +{ + +struct @(spec.action_name) +{ + using CancelGoalService = action_msgs::srv::CancelGoal; + using GoalStatusMessage = action_msgs::msg::GoalStatusArray; + using GoalRequestService = @(spec.pkg_name)::@(subfolder)::@(spec.action_name)_Goal; + using GoalResultService = @(spec.pkg_name)::@(subfolder)::@(spec.action_name)_Result; + + using Goal = GoalRequestService::Request; + using Result = GoalResultService::Response; + using Feedback = @(spec.pkg_name)::@(subfolder)::@(spec.action_name)_Feedback; +}; + +typedef struct @(spec.action_name) @(spec.action_name); + +} // namespace @(subfolder) + +} // namespace @(spec.pkg_name) + +#endif // @(header_guard_variable) diff --git a/rosidl_generator_cpp/rosidl_generator_cpp/__init__.py b/rosidl_generator_cpp/rosidl_generator_cpp/__init__.py index db31b9a55..3697386b5 100644 --- a/rosidl_generator_cpp/rosidl_generator_cpp/__init__.py +++ b/rosidl_generator_cpp/rosidl_generator_cpp/__init__.py @@ -18,6 +18,7 @@ from rosidl_cmake import expand_template from rosidl_cmake import get_newest_modification_time from rosidl_cmake import read_generator_arguments +from rosidl_parser import parse_action_file from rosidl_parser import parse_message_file from rosidl_parser import parse_service_file @@ -37,10 +38,18 @@ def generate_cpp(generator_arguments_file): os.path.join(template_dir, 'srv__struct.hpp.em'): '%s__struct.hpp', os.path.join(template_dir, 'srv__traits.hpp.em'): '%s__traits.hpp', } + + mapping_actions = { + os.path.join(template_dir, 'action.hpp.em'): '%s.hpp', + os.path.join(template_dir, 'action__struct.hpp.em'): '%s__struct.hpp', + } + for template_file in mapping_msgs.keys(): assert os.path.exists(template_file), 'Could not find template: ' + template_file for template_file in mapping_srvs.keys(): assert os.path.exists(template_file), 'Could not find template: ' + template_file + for template_file in mapping_actions.keys(): + assert os.path.exists(template_file), 'Could not find template: ' + template_file functions = { 'get_header_filename_from_msg_name': convert_camel_case_to_lower_case_underscore, @@ -74,6 +83,18 @@ def generate_cpp(generator_arguments_file): template_file, data, generated_file, minimum_timestamp=latest_target_timestamp) + elif extension == '.action': + spec = parse_action_file(args['package_name'], ros_interface_file) + for template_file, generated_filename in mapping_actions.items(): + data = {'spec': spec, 'subfolder': subfolder} + data.update(functions) + generated_file = os.path.join( + args['output_dir'], subfolder, generated_filename % + convert_camel_case_to_lower_case_underscore(spec.action_name)) + expand_template( + template_file, data, generated_file, + minimum_timestamp=latest_target_timestamp) + return 0 diff --git a/rosidl_parser/rosidl_parser/__init__.py b/rosidl_parser/rosidl_parser/__init__.py index 11b603b65..f034934ce 100644 --- a/rosidl_parser/rosidl_parser/__init__.py +++ b/rosidl_parser/rosidl_parser/__init__.py @@ -700,6 +700,12 @@ def validate_field_types(spec, known_msg_types): elif isinstance(spec, ServiceSpecification): spec_type = 'Service' fields = spec.request.fields + spec.response.fields + elif isinstance(spec, ActionSpecification): + spec_type = 'Action' + fields = [] + for service in spec.services: + fields += service.request.fields + fields += service.response.fields else: assert False, 'Unknown specification type: %s' % type(spec) for field in fields: @@ -709,7 +715,7 @@ def validate_field_types(spec, known_msg_types): if base_type not in known_msg_types: raise UnknownMessageType( "%s interface '%s' contains an unknown field type: %s" % - (spec_type, spec.base_type, field)) + (spec_type, base_type, field)) class ServiceSpecification: @@ -761,6 +767,15 @@ def parse_service_string(pkg_name, srv_name, message_string): return ServiceSpecification(pkg_name, srv_name, request_message, response_message) +class ActionSpecification: + + def __init__(self, pkg_name, action_name, services, feedback_message): + self.pkg_name = pkg_name + self.action_name = action_name + self.services = services + self.feedback = feedback_message + + def parse_action_file(pkg_name, interface_filename): basename = os.path.basename(interface_filename) action_name = os.path.splitext(basename)[0] @@ -833,4 +848,4 @@ def parse_action_string(pkg_name, action_name, action_string): pkg_name, action_name + ACTION_FEEDBACK_MESSAGE_SUFFIX, message_string) # --------------------------------------------------------------------------------------------- - return (services, feedback_msg) + return ActionSpecification(pkg_name, action_name, services, feedback_msg) diff --git a/rosidl_parser/test/test_parse_action_string.py b/rosidl_parser/test/test_parse_action_string.py index d0f36c0c4..4f592902e 100644 --- a/rosidl_parser/test/test_parse_action_string.py +++ b/rosidl_parser/test/test_parse_action_string.py @@ -43,8 +43,9 @@ def test_valid_action_string(): def test_valid_action_string1(): - (services, feedback_msg) = parse_action_string( - 'pkg', 'Foo', 'bool foo\n---\nint8 bar\n---\nbool foo') + spec = parse_action_string('pkg', 'Foo', 'bool foo\n---\nint8 bar\n---\nbool foo') + services = spec.services + feedback_msg = spec.feedback assert len(services) == 2 goal_service, result_service = services # Goal service checks @@ -79,8 +80,10 @@ def test_valid_action_string1(): def test_valid_action_string2(): - (services, feedback_msg) = parse_action_string( + spec = parse_action_string( 'pkg', 'Foo', '#comment---\n \nbool foo\n---\n#comment\n \nint8 bar\n---\nbool foo') + services = spec.services + feedback_msg = spec.feedback assert len(services) == 2 goal_service, result_service = services # Goal service checks @@ -115,10 +118,12 @@ def test_valid_action_string2(): def test_valid_action_string3(): - (services, feedback_msg) = parse_action_string( + spec = parse_action_string( 'pkg', 'Foo', 'bool foo\nstring status\n---\nbool FOO=1\nint8 bar\n---\nbool BAR=1\nbool foo') + services = spec.services + feedback_msg = spec.feedback assert len(services) == 2 goal_service, result_service = services # Goal service checks