Skip to content

Commit

Permalink
Add rosidl_generator_cpp templates for actions (#312)
Browse files Browse the repository at this point in the history
* Defined rosidl_generator_cpp templates for actions

* Removed unneeded includes in action.hpp.em

* Added logic bits for ActionSpecation

* Fixed action.hpp.em template

* Added support for action files in rosidl_generator_c

* Updates parse_action_string() use case within rosidl_actions.

* Recovering rosidl_generate_action_interfaces

* Deduplicate action target names

* call rosidl_generate_action_interfaces()

* Pass package dependencies to action generation

* Full action pipeline builds for C and C++

* Line length

* Two blank lines between top level definitions

* parse_action_string returns ActionSpecification

* rosidl_generator_cpp creates CMake target instead of library for actions

* rosidl_generator_c creates target for actions not library

* Add builtin_interfaces dep

* Fix more hardcoded stuff

* msg and serv don't depend on action templates

* Addressed peer review comments

* Install visibility file

* Macro to make sure _AMENT variables are set in global scope

* Fix copy paste error in target name variable
  • Loading branch information
Alexis Pojomovsky authored and sloretz committed Nov 13, 2018
1 parent 29c4a9f commit 075fe35
Show file tree
Hide file tree
Showing 17 changed files with 701 additions and 10 deletions.
9 changes: 5 additions & 4 deletions rosidl_actions/rosidl_actions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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))
93 changes: 93 additions & 0 deletions rosidl_cmake/cmake/rosidl_generate_action_interfaces.cmake
Original file line number Diff line number Diff line change
@@ -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()
26 changes: 26 additions & 0 deletions rosidl_cmake/cmake/rosidl_generate_interfaces.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down
1 change: 1 addition & 0 deletions rosidl_cmake/rosidl_cmake-extras.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
5 changes: 5 additions & 0 deletions rosidl_generator_c/cmake/register_c.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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}")

Expand Down
Original file line number Diff line number Diff line change
@@ -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()
43 changes: 43 additions & 0 deletions rosidl_generator_c/resource/action.h.em
Original file line number Diff line number Diff line change
@@ -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 <action>.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 <action_msgs/msg/goal_info.h>
#include <action_msgs/msg/goal_status_array.h>
#include <action_msgs/srv/cancel_goal.h>

#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)

0 comments on commit 075fe35

Please sign in to comment.