diff --git a/rosidl_generator_py/cmake/custom_command.cmake b/rosidl_generator_py/cmake/custom_command.cmake index 558716d1..62fd7627 100644 --- a/rosidl_generator_py/cmake/custom_command.cmake +++ b/rosidl_generator_py/cmake/custom_command.cmake @@ -19,11 +19,11 @@ # a different CMake subdirectory, and this command is invoked after an # add_subdirectory() call. add_custom_command( - OUTPUT ${_generated_extension_files} ${_generated_msg_py_files} ${_generated_msg_c_files} ${_generated_srv_py_files} ${_generated_srv_c_files} ${_generated_action_py_files} ${_generated_action_c_files} + OUTPUT ${_generated_extension_files} ${_generated_py_files} ${_generated_c_files} COMMAND ${PYTHON_EXECUTABLE} ${rosidl_generator_py_BIN} --generator-arguments-file "${generator_arguments_file}" --typesupport-impls "${_typesupport_impls}" - DEPENDS ${target_dependencies} ${rosidl_generate_interfaces_TARGET} ${extra_generator_dependencies} + DEPENDS ${target_dependencies} ${rosidl_generate_interfaces_TARGET} COMMENT "Generating Python code for ROS interfaces" VERBATIM ) @@ -35,11 +35,7 @@ else() ${rosidl_generate_interfaces_TARGET}${_target_suffix} DEPENDS ${_generated_extension_files} - ${_generated_msg_py_files} - ${_generated_msg_c_files} - ${_generated_srv_py_files} - ${_generated_srv_c_files} - ${_generated_action_py_files} - ${_generated_action_c_files} + ${_generated_py_files} + ${_generated_c_files} ) endif() diff --git a/rosidl_generator_py/cmake/register_py.cmake b/rosidl_generator_py/cmake/register_py.cmake index 937b3916..f3f21156 100644 --- a/rosidl_generator_py/cmake/register_py.cmake +++ b/rosidl_generator_py/cmake/register_py.cmake @@ -15,15 +15,10 @@ macro(rosidl_generator_py_extras BIN GENERATOR_FILES TEMPLATE_DIR) find_package(ament_cmake_core QUIET REQUIRED) ament_register_extension( - "rosidl_generate_interfaces" + "rosidl_generate_idl_interfaces" "rosidl_generator_py" "rosidl_generator_py_generate_interfaces.cmake") - ament_register_extension( - "rosidl_generate_action_interfaces" - "rosidl_generator_py" - "rosidl_generator_py_generate_action_interfaces.cmake") - normalize_path(BIN "${BIN}") set(rosidl_generator_py_BIN "${BIN}") diff --git a/rosidl_generator_py/cmake/rosidl_generator_py_generate_action_interfaces.cmake b/rosidl_generator_py/cmake/rosidl_generator_py_generate_action_interfaces.cmake deleted file mode 100644 index 177592d4..00000000 --- a/rosidl_generator_py/cmake/rosidl_generator_py_generate_action_interfaces.cmake +++ /dev/null @@ -1,241 +0,0 @@ -# Copyright 2019 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. - -find_package(rmw_implementation_cmake REQUIRED) -find_package(rmw REQUIRED) -find_package(rosidl_generator_c REQUIRED) -find_package(rosidl_typesupport_c REQUIRED) -find_package(rosidl_typesupport_interface REQUIRED) - -find_package(PythonInterp 3.5 REQUIRED) - -find_package(python_cmake_module REQUIRED) -find_package(PythonExtra MODULE REQUIRED) - -set(_typesupport_impls "rosidl_typesupport_c") - -set(_output_path - "${CMAKE_CURRENT_BINARY_DIR}/rosidl_generator_py/${PROJECT_NAME}") - -set(_generated_action_py_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}" _module_name) - list(APPEND _generated_action_py_files - "${_output_path}/${_parent_folder}/_${_module_name}.py" - ) -endforeach() - -foreach(_typesupport_impl ${_typesupport_impls}) - set(_generated_extension_${_typesupport_impl}_files "") -endforeach() -set(_generated_extension_files "") - -if(NOT _generated_action_py_files STREQUAL "") - foreach(_typesupport_impl ${_typesupport_impls}) - list(APPEND _generated_extension_${_typesupport_impl}_files "${_output_path}/_${PROJECT_NAME}_action_s.ep.${_typesupport_impl}.c") - list(APPEND _generated_extension_files "${_generated_extension_${_typesupport_impl}_files}") - endforeach() - list(GET _generated_action_py_files 0 _action_file) - get_filename_component(_parent_folder "${_action_file}" DIRECTORY) - list(APPEND _generated_action_py_files "${_parent_folder}/__init__.py") -endif() - -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_py_BIN}" - ${rosidl_generator_py_GENERATOR_FILES} - "${rosidl_generator_py_TEMPLATE_DIR}/_action.py.em" - "${rosidl_generator_py_TEMPLATE_DIR}/_msg_pkg_typesupport_entry_point.c.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_py__generate_actions__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_py_TEMPLATE_DIR}" - TARGET_DEPENDENCIES ${target_dependencies} -) -# make sure that actions generation run sequentially with messages/services generation -set(extra_generator_dependencies "${rosidl_generate_interfaces_TARGET}__py") - -set(_target_suffix "__action_py") - -# move custom command into a subdirectory to avoid multiple invocations on Windows -set(_subdir "${CMAKE_CURRENT_BINARY_DIR}/${rosidl_generate_interfaces_TARGET}${_target_suffix}") -file(MAKE_DIRECTORY "${_subdir}") -file(READ "${rosidl_generator_py_DIR}/custom_command.cmake" _custom_command) -file(WRITE "${_subdir}/CMakeLists.txt" "${_custom_command}") -add_subdirectory("${_subdir}" ${rosidl_generate_interfaces_TARGET}${_target_suffix}) -set_property( - SOURCE - ${_generated_extension_files} ${_generated_action_py_files} - PROPERTY GENERATED 1) - -macro(set_properties _build_type) - set_target_properties(${_target_name} PROPERTIES - COMPILE_OPTIONS "${_extension_compile_flags}" - PREFIX "" - LIBRARY_OUTPUT_DIRECTORY${_build_type} ${_output_path} - RUNTIME_OUTPUT_DIRECTORY${_build_type} ${_output_path} - OUTPUT_NAME "${PROJECT_NAME}_action_s__${_typesupport_impl}${PythonExtra_EXTENSION_SUFFIX}" - SUFFIX "${PythonExtra_EXTENSION_EXTENSION}") -endmacro() - -foreach(_typesupport_impl ${_typesupport_impls}) - find_package(${_typesupport_impl} REQUIRED) - - set(_pyext_suffix "__pyext") - set(_target_name "${PROJECT_NAME}_action__${_typesupport_impl}${_pyext_suffix}") - - add_library(${_target_name} SHARED - ${_generated_extension_${_typesupport_impl}_files} - ) - add_dependencies( - ${_target_name} - ${rosidl_generate_interfaces_TARGET}${_target_suffix} - ${rosidl_generate_interfaces_TARGET}__rosidl_typesupport_c - ) - - set(_extension_compile_flags "") - set(_PYTHON_EXECUTABLE ${PYTHON_EXECUTABLE}) - if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") - set(_extension_compile_flags -Wall -Wextra) - endif() - if(WIN32 AND "${CMAKE_BUILD_TYPE}" STREQUAL "Debug") - set(PYTHON_EXECUTABLE ${PYTHON_EXECUTABLE_DEBUG}) - endif() - set_properties("") - if(WIN32) - set_properties("_DEBUG") - set_properties("_MINSIZEREL") - set_properties("_RELEASE") - set_properties("_RELWITHDEBINFO") - endif() - target_link_libraries( - ${_target_name} - ${PythonExtra_LIBRARIES} - ${rosidl_generate_interfaces_TARGET}__${_typesupport_impl} - ${rosidl_generate_interfaces_TARGET}__${_typesupport_impl}__generate_actions - ) - - target_include_directories(${_target_name} - PUBLIC - ${CMAKE_CURRENT_BINARY_DIR}/rosidl_generator_c - ${CMAKE_CURRENT_BINARY_DIR}/rosidl_generator_py - ${PythonExtra_INCLUDE_DIRS} - ) - - rosidl_target_interfaces(${_target_name} - ${rosidl_generate_interfaces_TARGET} rosidl_typesupport_c) - - ament_target_dependencies(${_target_name} - "rosidl_generator_c" - "rosidl_typesupport_c" - "rosidl_typesupport_interface" - ) - foreach(_pkg_name ${rosidl_generate_interfaces_DEPENDENCY_PACKAGE_NAMES}) - ament_target_dependencies(${_target_name} - ${_pkg_name} - ) - endforeach() - - add_dependencies(${_target_name} - ${rosidl_generate_interfaces_TARGET}__${_typesupport_impl} - ${rosidl_generate_interfaces_TARGET}__${_typesupport_impl}__generate_actions - ) - ament_target_dependencies(${_target_name} - "rosidl_generator_c" - "rosidl_generator_py" - "${rosidl_generate_interfaces_TARGET}__rosidl_generator_c" - ) - set(PYTHON_EXECUTABLE ${_PYTHON_EXECUTABLE}) - - if(NOT rosidl_generate_interfaces_SKIP_INSTALL) - install(TARGETS ${_target_name} - DESTINATION "${PYTHON_INSTALL_DIR}/${PROJECT_NAME}") - endif() -endforeach() - -if(NOT rosidl_generate_action_interfaces_SKIP_INSTALL) - if(NOT _generated_action_py_files STREQUAL "") - list(GET _generated_action_py_files 0 _action_file) - get_filename_component(_action_package_dir "${_action_file}" DIRECTORY) - get_filename_component(_action_package_dir "${_action_package_dir}" NAME) - if(NOT _action_package_dir STREQUAL "") - install(FILES ${_generated_action_py_files} - DESTINATION "${PYTHON_INSTALL_DIR}/${PROJECT_NAME}/${_action_package_dir}" - ) - endif() - endif() -endif() - -if(BUILD_TESTING AND rosidl_generate_action_interfaces_ADD_LINTER_TESTS) - if(NOT _generated_action_py_files STREQUAL "") - find_package(ament_cmake_flake8 REQUIRED) - ament_flake8( - TESTNAME "flake8_rosidl_generated_py_generate_actions" - # the generated code might contain longer lines for templated types - MAX_LINE_LENGTH 999 - "${_output_path}") - - find_package(ament_cmake_pep257 REQUIRED) - ament_pep257( - TESTNAME "pep257_rosidl_generated_py_generate_actions" - "${_output_path}") - - find_package(ament_cmake_uncrustify REQUIRED) - ament_uncrustify( - TESTNAME "uncrustify_rosidl_generated_py_generate_actions" - # the generated code might contain longer lines for templated types - MAX_LINE_LENGTH 0 - "${_output_path}") - endif() -endif() diff --git a/rosidl_generator_py/cmake/rosidl_generator_py_generate_interfaces.cmake b/rosidl_generator_py/cmake/rosidl_generator_py_generate_interfaces.cmake index 5c0d6773..eb1b6bec 100644 --- a/rosidl_generator_py/cmake/rosidl_generator_py_generate_interfaces.cmake +++ b/rosidl_generator_py/cmake/rosidl_generator_py_generate_interfaces.cmake @@ -1,4 +1,4 @@ -# Copyright 2014-2016 Open Source Robotics Foundation, Inc. +# Copyright 2014-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. @@ -34,83 +34,45 @@ endif() set(_output_path "${CMAKE_CURRENT_BINARY_DIR}/rosidl_generator_py/${PROJECT_NAME}") set(_generated_extension_files "") -set(_generated_msg_py_files "") -set(_generated_msg_c_files "") -set(_generated_srv_py_files "") -set(_generated_srv_c_files "") -set(_generated_action_py_files "") -set(_generated_action_c_files "") +set(_generated_py_files "") +set(_generated_c_files "") foreach(_typesupport_impl ${_typesupport_impls}) set(_generated_extension_${_typesupport_impl}_files "") endforeach() -foreach(_idl_file ${rosidl_generate_interfaces_IDL_FILES}) - get_filename_component(_parent_folder "${_idl_file}" DIRECTORY) +foreach(_abs_idl_file ${rosidl_generate_interfaces_ABS_IDL_FILES}) + get_filename_component(_parent_folder "${_abs_idl_file}" DIRECTORY) get_filename_component(_parent_folder "${_parent_folder}" NAME) - get_filename_component(_msg_name1 "${_idl_file}" NAME_WE) - get_filename_component(_ext "${_idl_file}" EXT) - string_camel_case_to_lower_case_underscore("${_msg_name1}" _module_name) - - if(_parent_folder STREQUAL "msg") - list(APPEND _generated_msg_py_files - "${_output_path}/${_parent_folder}/_${_module_name}.py" - ) - list(APPEND _generated_msg_c_files - "${_output_path}/${_parent_folder}/_${_module_name}_s.c" - ) - elseif(_parent_folder STREQUAL "srv") - if("_${_module_name}_s.c" MATCHES "(.*)__response(.*)" OR "_${_module_name}_s.c" MATCHES "(.*)__request(.*)") - list(APPEND _generated_srv_c_files - "${_output_path}/${_parent_folder}/_${_module_name}_s.c" - ) - endif() - list(APPEND _generated_srv_py_files - "${_output_path}/${_parent_folder}/_${_module_name}.py" - ) - elseif(_parent_folder STREQUAL "action") - # C files generated for .msg, _Request.msg and _Response.msg but not .srv - if(_ext STREQUAL ".msg") - list(APPEND _generated_action_c_files - "${_output_path}/${_parent_folder}/_${_module_name}_s.c" - ) - endif() - list(APPEND _generated_action_py_files - "${_output_path}/${_parent_folder}/_${_module_name}.py" - ) - else() - message(FATAL_ERROR "Interface file with unknown parent folder: ${_idl_file}") - endif() + get_filename_component(_idl_name "${_abs_idl_file}" NAME_WE) + string_camel_case_to_lower_case_underscore("${_idl_name}" _module_name) + list(APPEND _generated_py_files + "${_output_path}/${_parent_folder}/_${_module_name}.py") + list(APPEND _generated_c_files + "${_output_path}/${_parent_folder}/_${_module_name}_s.c") endforeach() file(MAKE_DIRECTORY "${_output_path}") file(WRITE "${_output_path}/__init__.py" "") -if(NOT _generated_msg_py_files STREQUAL "") - list(GET _generated_msg_py_files 0 _msg_file) - get_filename_component(_parent_folder "${_msg_file}" DIRECTORY) - list(APPEND _generated_msg_py_files - "${_parent_folder}/__init__.py" - ) -endif() - -if(NOT _generated_srv_py_files STREQUAL "") - list(GET _generated_srv_py_files 0 _srv_file) - get_filename_component(_parent_folder "${_srv_file}" DIRECTORY) - list(APPEND _generated_srv_py_files - "${_parent_folder}/__init__.py" - ) -endif() - -if(NOT _generated_action_py_files STREQUAL "") - list(GET _generated_action_py_files 0 _action_file) - get_filename_component(_parent_folder "${_action_file}" DIRECTORY) - list(APPEND _generated_action_py_files - "${_parent_folder}/__init__.py" - ) -endif() +# collect relative paths of directories containing to-be-installed Python modules +# add __init__.py files where necessary +set(_generated_py_dirs "") +foreach(_generated_py_file ${_generated_py_files}) + get_filename_component(_parent_folder "${_generated_py_file}" DIRECTORY) + set(_init_module "${_parent_folder}/__init__.py") + list(FIND _generated_py_files "${_init_module}" _index) + if(_index EQUAL -1) + list(APPEND _generated_py_files "${_init_module}") + + string(LENGTH "${_output_path}" _length) + math(EXPR _index "${_length} + 1") + string(SUBSTRING "${_parent_folder}" ${_index} -1 _relative_directory) + list(APPEND _generated_py_dirs "${_relative_directory}") + endif() +endforeach() -if(NOT _generated_msg_c_files STREQUAL "" OR NOT _generated_srv_c_files STREQUAL "" OR NOT _generated_action_c_files STREQUAL "") +if(NOT _generated_c_files STREQUAL "") foreach(_typesupport_impl ${_typesupport_impls}) list(APPEND _generated_extension_${_typesupport_impl}_files "${_output_path}/_${PROJECT_NAME}_s.ep.${_typesupport_impl}.c") list(APPEND _generated_extension_files "${_generated_extension_${_typesupport_impl}_files}") @@ -119,7 +81,7 @@ endif() set(_dependency_files "") set(_dependencies "") foreach(_pkg_name ${rosidl_generate_interfaces_DEPENDENCY_PACKAGE_NAMES}) - foreach(_idl_file ${${_pkg_name}_INTERFACE_FILES}) + 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}") @@ -130,10 +92,17 @@ endforeach() set(target_dependencies "${rosidl_generator_py_BIN}" ${rosidl_generator_py_GENERATOR_FILES} - "${rosidl_generator_py_TEMPLATE_DIR}/_msg_support.c.em" + "${rosidl_generator_py_TEMPLATE_DIR}/_action_pkg_typesupport_entry_point.c.em" + "${rosidl_generator_py_TEMPLATE_DIR}/_action.py.em" + "${rosidl_generator_py_TEMPLATE_DIR}/_idl_pkg_typesupport_entry_point.c.em" + "${rosidl_generator_py_TEMPLATE_DIR}/_idl_support.c.em" + "${rosidl_generator_py_TEMPLATE_DIR}/_idl.py.em" "${rosidl_generator_py_TEMPLATE_DIR}/_msg_pkg_typesupport_entry_point.c.em" + "${rosidl_generator_py_TEMPLATE_DIR}/_msg_support.c.em" "${rosidl_generator_py_TEMPLATE_DIR}/_msg.py.em" + "${rosidl_generator_py_TEMPLATE_DIR}/_srv_pkg_typesupport_entry_point.c.em" "${rosidl_generator_py_TEMPLATE_DIR}/_srv.py.em" + ${rosidl_generate_interfaces_ABS_IDL_FILES} ${_dependency_files}) foreach(dep ${target_dependencies}) if(NOT EXISTS "${dep}") @@ -145,59 +114,27 @@ set(generator_arguments_file "${CMAKE_CURRENT_BINARY_DIR}/rosidl_generator_py__a rosidl_write_generator_arguments( "${generator_arguments_file}" PACKAGE_NAME "${PROJECT_NAME}" - ROS_INTERFACE_FILES "${rosidl_generate_interfaces_IDL_FILES}" + IDL_TUPLES "${rosidl_generate_interfaces_IDL_TUPLES}" ROS_INTERFACE_DEPENDENCIES "${_dependencies}" OUTPUT_DIR "${_output_path}" TEMPLATE_DIR "${rosidl_generator_py_TEMPLATE_DIR}" TARGET_DEPENDENCIES ${target_dependencies} ) -set(extra_generator_dependencies "") - -if(NOT _generated_msg_py_files STREQUAL "") - list(GET _generated_msg_py_files 0 _msg_file) - get_filename_component(_msg_package_dir1 "${_msg_file}" DIRECTORY) - get_filename_component(_msg_package_dir2 "${_msg_package_dir1}" NAME) -endif() - -if(NOT _generated_srv_py_files STREQUAL "") - list(GET _generated_srv_py_files 0 _srv_file) - get_filename_component(_srv_package_dir1 "${_srv_file}" DIRECTORY) - get_filename_component(_srv_package_dir2 "${_srv_package_dir1}" NAME) -endif() - -if(NOT _generated_action_py_files STREQUAL "") - list(GET _generated_action_py_files 0 _action_file) - get_filename_component(_action_package_dir1 "${_action_file}" DIRECTORY) - get_filename_component(_action_package_dir2 "${_action_package_dir1}" NAME) -endif() if(NOT rosidl_generate_interfaces_SKIP_INSTALL) ament_python_install_module("${_output_path}/__init__.py" DESTINATION_SUFFIX "${PROJECT_NAME}" ) - ament_python_install_module("${_output_path}/__init__.py" - DESTINATION_SUFFIX "${PROJECT_NAME}/${_msg_package_dir2}" - ) - # TODO(esteve): replace this with ament_python_install_module and allow a list - # of modules to be passed instead of iterating over _generated_msg_py_files + # of modules to be passed instead of iterating over _generated_py_files # See https://github.com/ros2/rosidl/issues/89 - if(NOT _msg_package_dir2 STREQUAL "") - install(FILES ${_generated_msg_py_files} - DESTINATION "${PYTHON_INSTALL_DIR}/${PROJECT_NAME}/${_msg_package_dir2}" - ) - endif() - if(NOT _srv_package_dir2 STREQUAL "") - install(FILES ${_generated_srv_py_files} - DESTINATION "${PYTHON_INSTALL_DIR}/${PROJECT_NAME}/${_srv_package_dir2}" - ) - endif() - if(NOT _action_package_dir2 STREQUAL "") - install(FILES ${_generated_action_py_files} - DESTINATION "${PYTHON_INSTALL_DIR}/${PROJECT_NAME}/${_action_package_dir2}" + foreach(_generated_py_dir ${_generated_py_dirs}) + install(DIRECTORY "${_output_path}/${_generated_py_dir}/" + DESTINATION "${PYTHON_INSTALL_DIR}/${PROJECT_NAME}/${_generated_py_dir}" + PATTERN "*.py" ) - endif() + endforeach() endif() set(_target_suffix "__py") @@ -210,7 +147,7 @@ file(WRITE "${_subdir}/CMakeLists.txt" "${_custom_command}") add_subdirectory("${_subdir}" ${rosidl_generate_interfaces_TARGET}${_target_suffix}) set_property( SOURCE - ${_generated_extension_files} ${_generated_msg_py_files} ${_generated_msg_c_files} ${_generated_srv_py_files} ${_generated_srv_c_files} ${_generated_action_py_files} ${_generated_action_c_files} + ${_generated_extension_files} ${_generated_py_files} ${_generated_c_files} PROPERTY GENERATED 1) macro(set_properties _build_type) @@ -231,11 +168,7 @@ macro(set_lib_properties _build_type) endmacro() set(_target_name_lib "${rosidl_generate_interfaces_TARGET}__python") -add_library(${_target_name_lib} SHARED - ${_generated_msg_c_files} - ${_generated_srv_c_files} - ${_generated_action_c_files} -) +add_library(${_target_name_lib} SHARED ${_generated_c_files}) add_dependencies( ${_target_name_lib} ${rosidl_generate_interfaces_TARGET}${_target_suffix} @@ -359,13 +292,9 @@ endif() if(BUILD_TESTING AND rosidl_generate_interfaces_ADD_LINTER_TESTS) if( - NOT _generated_msg_py_files STREQUAL "" OR + NOT _generated_py_files STREQUAL "" OR NOT _generated_extension_files STREQUAL "" OR - NOT _generated_msg_c_files STREQUAL "" OR - NOT _generated_srv_py_files STREQUAL "" OR - NOT _generated_srv_c_files STREQUAL "" OR - NOT _generated_action_c_files STREQUAL "" OR - NOT _generated_action_py_files STREQUAL "" + NOT _generated_c_files STREQUAL "" ) find_package(ament_cmake_cppcheck REQUIRED) ament_cppcheck( diff --git a/rosidl_generator_py/resource/_action.py.em b/rosidl_generator_py/resource/_action.py.em index 4393fe8c..fe3caff0 100644 --- a/rosidl_generator_py/resource/_action.py.em +++ b/rosidl_generator_py/resource/_action.py.em @@ -1,23 +1,39 @@ -# generated from rosidl_generator_py/resource/_action.py.em -# generated code does not contain a copyright notice - -@####################################################################### -@# EmPy template for generating _.py files -@# -@# Context: -@# - module_name -@# - package_name -@# - spec (rosidl_parser.ActionSpecification) -@# Parsed specification of the .action file -@# - convert_camel_case_to_lower_case_underscore (function) -@####################################################################### -@ -import logging -import traceback - - -class Metaclass(type): - """Metaclass of action '@(spec.action_name)'.""" +@# Included from rosidl_generator_py/resource/_idl.py.em +@{ +from rosidl_cmake import convert_camel_case_to_lower_case_underscore + +action_name = '_' + convert_camel_case_to_lower_case_underscore(action.structure_type.name) +module_name = '_' + convert_camel_case_to_lower_case_underscore(interface_path.stem) + +TEMPLATE( + '_msg.py.em', + package_name=package_name, interface_path=interface_path, + message=action.goal) +TEMPLATE( + '_msg.py.em', + package_name=package_name, interface_path=interface_path, + message=action.result) +TEMPLATE( + '_msg.py.em', + package_name=package_name, interface_path=interface_path, + message=action.feedback) +TEMPLATE( + '_srv.py.em', + package_name=package_name, interface_path=interface_path, + service=action.send_goal_service) +TEMPLATE( + '_srv.py.em', + package_name=package_name, interface_path=interface_path, + service=action.get_result_service) +TEMPLATE( + '_msg.py.em', + package_name=package_name, interface_path=interface_path, + message=action.feedback_message) +}@ + + +class Metaclass_@(action.structure_type.name)(type): + """Metaclass of action '@(action.structure_type.name)'.""" _TYPE_SUPPORT = None @@ -25,38 +41,56 @@ class Metaclass(type): def __import_type_support__(cls): try: from rosidl_generator_py import import_type_support - module = import_type_support('@(package_name)', '@(package_name)_action') + module = import_type_support('@(package_name)') except ImportError: - logger = logging.getLogger('rosidl_generator_py.@(spec.action_name)') + import logging + import traceback + logger = logging.getLogger( + '@('.'.join(action.structure_type.namespaces + [action.structure_type.name]))') logger.debug( - 'Failed to import needed modules for type support:\n' + traceback.format_exc()) + 'Failed to import needed modules for type support:\n' + + traceback.format_exc()) else: - cls._TYPE_SUPPORT = module.type_support_action__@(subfolder)_@(module_name) -@{ -prefix = '_' + convert_camel_case_to_lower_case_underscore(spec.action_name) + '__' -suffixes = ['feedback', 'goal', 'result'] -for field_name in [prefix + suffix for suffix in suffixes]: - print('%sfrom %s.%s import %s' % (' ' * 4 * 3, package_name, subfolder, field_name)) - print('%sif %s.Metaclass._TYPE_SUPPORT is None:' % (' ' * 4 * 3, field_name)) - print('%s%s.Metaclass.__import_type_support__()' % (' ' * 4 * 4, field_name)) -print('%sfrom %s.%s import %s' % (' ' * 4 * 3, 'action_msgs', 'msg', '_goal_status_array')) -print('%sif %s.Metaclass._TYPE_SUPPORT is None:' % (' ' * 4 * 3, '_goal_status_array')) -print('%s%s.Metaclass.__import_type_support__()' % (' ' * 4 * 4, '_goal_status_array')) -print('%sfrom %s.%s import %s' % (' ' * 4 * 3, 'action_msgs', 'srv', '_cancel_goal')) -print('%sif %s.Metaclass._TYPE_SUPPORT is None:' % (' ' * 4 * 3, '_cancel_goal')) -print('%s%s.Metaclass.__import_type_support__()' % (' ' * 4 * 4, '_cancel_goal')) -}@ + cls._TYPE_SUPPORT = module.type_support_action__@('__'.join(action.structure_type.namespaces[1:]))_@(action_name) + + from action_msgs.msg import _goal_status_array + if _goal_status_array.Metaclass_GoalStatusArray._TYPE_SUPPORT is None: + _goal_status_array.Metaclass_GoalStatusArray.__import_type_support__() + from action_msgs.srv import _cancel_goal + if _cancel_goal.Metaclass_CancelGoal._TYPE_SUPPORT is None: + _cancel_goal.Metaclass_CancelGoal.__import_type_support__() + + from @('.'.join(action.structure_type.namespaces)) import @(module_name) + if @(module_name).Metaclass_@(action.send_goal_service.structure_type.name)._TYPE_SUPPORT is None: + @(module_name).Metaclass_@(action.send_goal_service.structure_type.name).__import_type_support__() + if @(module_name).Metaclass_@(action.get_result_service.structure_type.name)._TYPE_SUPPORT is None: + @(module_name).Metaclass_@(action.get_result_service.structure_type.name).__import_type_support__() + if @(module_name).Metaclass_@(action.feedback_message.structure.type.name)._TYPE_SUPPORT is None: + @(module_name).Metaclass_@(action.feedback_message.structure.type.name).__import_type_support__() + + +class @(action.structure_type.name)(metaclass=Metaclass_@(action.structure_type.name)): + + # The goal message defined in the action definition. + from @('.'.join(action.structure_type.namespaces)).@(module_name) import @(action.goal.structure.type.name) as Goal + # The result message defined in the action definition. + from @('.'.join(action.structure_type.namespaces)).@(module_name) import @(action.result.structure.type.name) as Result + # The feedback message defined in the action definition. + from @('.'.join(action.structure_type.namespaces)).@(module_name) import @(action.feedback.structure.type.name) as Feedback + class Impl: -class @(spec.action_name)(metaclass=Metaclass): - from action_msgs.srv._cancel_goal import CancelGoal as CancelGoalService - from action_msgs.msg._goal_status_array import GoalStatusArray as GoalStatusMessage - from @(package_name).@(subfolder)._@convert_camel_case_to_lower_case_underscore(spec.action_name)__goal import @(spec.action_name)_Goal as GoalRequestService - from @(package_name).@(subfolder)._@convert_camel_case_to_lower_case_underscore(spec.action_name)__result import @(spec.action_name)_Result as GoalResultService - from @(package_name).@(subfolder)._@convert_camel_case_to_lower_case_underscore(spec.action_name)__feedback import @(spec.action_name)_Feedback as Feedback + # The send_goal service using a wrapped version of the goal message as a request. + from @('.'.join(action.structure_type.namespaces)).@(module_name) import @(action.send_goal_service.structure_type.name) as SendGoalService + # The get_result service using a wrapped version of the result message as a response. + from @('.'.join(action.structure_type.namespaces)).@(module_name) import @(action.get_result_service.structure_type.name) as GetResultService + # The feedback message with generic fields which wraps the feedback message. + from @('.'.join(action.structure_type.namespaces)).@(module_name) import @(action.feedback_message.structure.type.name) as FeedbackMessage - Goal = GoalRequestService.Request - Result = GoalResultService.Response + # The generic service to cancel a goal. + from action_msgs.srv._cancel_goal import CancelGoal as CancelGoalService + # The generic message for get the status of a goal. + from action_msgs.msg._goal_status_array import GoalStatusArray as GoalStatusMessage def __init__(self): raise NotImplementedError('Action classes can not be instantiated') diff --git a/rosidl_generator_py/resource/_action_pkg_typesupport_entry_point.c.em b/rosidl_generator_py/resource/_action_pkg_typesupport_entry_point.c.em new file mode 100644 index 00000000..44d7e301 --- /dev/null +++ b/rosidl_generator_py/resource/_action_pkg_typesupport_entry_point.c.em @@ -0,0 +1,90 @@ +@# Included from rosidl_generator_py/resource/_idl_pkg_typesupport_entry_point.c.em +@ +@{ +TEMPLATE( + '_msg_pkg_typesupport_entry_point.c.em', + package_name=package_name, idl_type=action.structure_type, + message=action.goal, typesupport_impl=typesupport_impl, + include_directives=include_directives, + register_functions=register_functions) +}@ + +@{ +TEMPLATE( + '_msg_pkg_typesupport_entry_point.c.em', + package_name=package_name, idl_type=action.structure_type, + message=action.result, typesupport_impl=typesupport_impl, + include_directives=include_directives, + register_functions=register_functions) +}@ + +@{ +TEMPLATE( + '_msg_pkg_typesupport_entry_point.c.em', + package_name=package_name, idl_type=action.structure_type, + message=action.feedback, typesupport_impl=typesupport_impl, + include_directives=include_directives, + register_functions=register_functions) +}@ + +@{ +TEMPLATE( + '_srv_pkg_typesupport_entry_point.c.em', + package_name=package_name, idl_type=action.structure_type, + service=action.send_goal_service, typesupport_impl=typesupport_impl, + include_directives=include_directives, + register_functions=register_functions) +}@ + +@{ +TEMPLATE( + '_srv_pkg_typesupport_entry_point.c.em', + package_name=package_name, idl_type=action.structure_type, + service=action.get_result_service, typesupport_impl=typesupport_impl, + include_directives=include_directives, + register_functions=register_functions) +}@ + +@{ +TEMPLATE( + '_msg_pkg_typesupport_entry_point.c.em', + package_name=package_name, idl_type=action.structure_type, + message=action.feedback_message, typesupport_impl=typesupport_impl, + include_directives=include_directives, + register_functions=register_functions) +}@ + +@{ +from rosidl_cmake import convert_camel_case_to_lower_case_underscore +type_name = convert_camel_case_to_lower_case_underscore(action.structure_type.name) +function_name = 'type_support' +}@ + +@{ +register_function = '_register_action_type__' + '__'.join(action.structure_type.namespaces[1:] + [type_name]) +register_functions.append(register_function) +}@ +int8_t +@(register_function)(PyObject * pymodule) +{ + int8_t err; + PyObject * pyobject_@(function_name) = NULL; + pyobject_@(function_name) = PyCapsule_New( + (void *)ROSIDL_TYPESUPPORT_INTERFACE__ACTION_SYMBOL_NAME(rosidl_typesupport_c, @(', '.join(action.structure_type.namespaces + [action.structure_type.name])))(), + NULL, NULL); + if (!pyobject_@(function_name)) { + // previously added objects will be removed when the module is destroyed + return -1; + } + err = PyModule_AddObject( + pymodule, + "@(function_name)_action__@('__'.join(action.structure_type.namespaces[1:] + [type_name]))", + pyobject_@(function_name)); + if (err) { + // the created capsule needs to be decremented + Py_XDECREF(pyobject_@(function_name)); + // previously added objects will be removed when the module is destroyed + return err; + } + return 0; +} diff --git a/rosidl_generator_py/resource/_idl.py.em b/rosidl_generator_py/resource/_idl.py.em new file mode 100644 index 00000000..300691f1 --- /dev/null +++ b/rosidl_generator_py/resource/_idl.py.em @@ -0,0 +1,55 @@ +# generated from rosidl_generator_py/resource/_idl.py.em +# with input from @(package_name):@(interface_path) +# generated code does not contain a copyright notice +@ +@####################################################################### +@# EmPy template for generating _.py files +@# +@# Context: +@# - package_name (string) +@# - interface_path (Path relative to the directory named after the package) +@# - content (IdlContent, list of elements, e.g. Messages or Services) +@####################################################################### +@ +@####################################################################### +@# Handle messages +@####################################################################### +@{ +from rosidl_parser.definition import Message +}@ +@[for message in content.get_elements_of_type(Message)]@ +@{ +TEMPLATE( + '_msg.py.em', + package_name=package_name, interface_path=interface_path, + message=message) +}@ +@[end for]@ +@ +@####################################################################### +@# Handle services +@####################################################################### +@{ +from rosidl_parser.definition import Service +}@ +@[for service in content.get_elements_of_type(Service)]@ +@{ +TEMPLATE( + '_srv.py.em', + package_name=package_name, interface_path=interface_path, service=service) +}@ +@[end for]@ +@ +@####################################################################### +@# Handle actions +@####################################################################### +@{ +from rosidl_parser.definition import Action +}@ +@[for action in content.get_elements_of_type(Action)]@ +@{ +TEMPLATE( + '_action.py.em', + package_name=package_name, interface_path=interface_path, action=action) +}@ +@[end for]@ diff --git a/rosidl_generator_py/resource/_idl_pkg_typesupport_entry_point.c.em b/rosidl_generator_py/resource/_idl_pkg_typesupport_entry_point.c.em new file mode 100644 index 00000000..9807270c --- /dev/null +++ b/rosidl_generator_py/resource/_idl_pkg_typesupport_entry_point.c.em @@ -0,0 +1,107 @@ +// generated from rosidl_generator_py/resource/_idl_pkg_typesupport_entry_point.c.em +// generated code does not contain a copyright notice +@ +@####################################################################### +@# EmPy template for generating __s.ep..c files +@# +@# Context: +@# - package_name (string) +@# - content (IdlContent, combined list of elements across all interface files +@# - typesupport_impl (string identifying the typesupport used) +@####################################################################### +@ +@####################################################################### +@# Handle messages +@####################################################################### +@{ +from rosidl_cmake import convert_camel_case_to_lower_case_underscore +from rosidl_parser.definition import Message + +include_directives = set() +register_functions = [] +}@ +#include + +static PyMethodDef @(package_name)__methods[] = { + {NULL, NULL, 0, NULL} /* sentinel */ +}; + +static struct PyModuleDef @(package_name)__module = { + PyModuleDef_HEAD_INIT, + "_@(package_name)_support", + "_@(package_name)_doc", + -1, /* -1 means that the module keeps state in global variables */ + @(package_name)__methods, + NULL, + NULL, + NULL, + NULL, +}; +@ +@[for message in content.get_elements_of_type(Message)]@ + +@{ +TEMPLATE( + '_msg_pkg_typesupport_entry_point.c.em', + package_name=package_name, idl_type=message.structure.type, message=message, + typesupport_impl=typesupport_impl, include_directives=include_directives, + register_functions=register_functions) +}@ +@[end for]@ +@ +@####################################################################### +@# Handle services +@####################################################################### +@{ +from rosidl_parser.definition import Service +}@ +@[for service in content.get_elements_of_type(Service)]@ + +@{ +TEMPLATE( + '_srv_pkg_typesupport_entry_point.c.em', + package_name=package_name, idl_type=service.structure_type, + service=service, typesupport_impl=typesupport_impl, + include_directives=include_directives, + register_functions=register_functions) +}@ +@[end for]@ +@ +@####################################################################### +@# Handle actions +@####################################################################### +@{ +from rosidl_parser.definition import Action +}@ +@[for action in content.get_elements_of_type(Action)]@ + +@{ +TEMPLATE( + '_action_pkg_typesupport_entry_point.c.em', + package_name=package_name, idl_type=action.structure_type, + action=action, typesupport_impl=typesupport_impl, + include_directives=include_directives, + register_functions=register_functions) +}@ +@[end for]@ + +PyMODINIT_FUNC +PyInit_@(package_name)_s__@(typesupport_impl)(void) +{ + PyObject * pymodule = NULL; + pymodule = PyModule_Create(&@(package_name)__module); + if (!pymodule) { + return NULL; + } + int8_t err; +@[for register_function in register_functions]@ + + err = @(register_function)(pymodule); + if (err) { + Py_XDECREF(pymodule); + return NULL; + } +@[end for]@ + + return pymodule; +} diff --git a/rosidl_generator_py/resource/_idl_support.c.em b/rosidl_generator_py/resource/_idl_support.c.em new file mode 100644 index 00000000..abb7536e --- /dev/null +++ b/rosidl_generator_py/resource/_idl_support.c.em @@ -0,0 +1,122 @@ +// generated from rosidl_generator_py/resource/_idl_support.c.em +// with input from @(package_name):@(interface_path) +// generated code does not contain a copyright notice +@ +@####################################################################### +@# EmPy template for generating __s.c files +@# +@# Context: +@# - package_name (string) +@# - interface_path (Path relative to the directory named after the package) +@# - content (IdlContent, list of elements, e.g. Messages or Services) +@####################################################################### +@ +@####################################################################### +@# Handle messages +@####################################################################### +@{ +from rosidl_parser.definition import Message + +include_directives = set() +}@ +@[for message in content.get_elements_of_type(Message)]@ +@{ + +TEMPLATE( + '_msg_support.c.em', + package_name=package_name, interface_path=interface_path, + message=message, include_directives=include_directives) +}@ +@[end for]@ +@ +@####################################################################### +@# Handle services +@####################################################################### +@{ +from rosidl_parser.definition import Service +}@ +@[for service in content.get_elements_of_type(Service)]@ +@{ + +TEMPLATE( + '_msg_support.c.em', + package_name=package_name, interface_path=interface_path, + message=service.request_message, include_directives=include_directives) +}@ + +@{ +TEMPLATE( + '_msg_support.c.em', + package_name=package_name, interface_path=interface_path, + message=service.response_message, include_directives=include_directives) +}@ +@[end for]@ +@ +@####################################################################### +@# Handle actions +@####################################################################### +@{ +from rosidl_parser.definition import Action +}@ +@[for action in content.get_elements_of_type(Action)]@ +@{ + +TEMPLATE( + '_msg_support.c.em', + package_name=package_name, interface_path=interface_path, + message=action.goal, include_directives=include_directives) +}@ + +@{ +TEMPLATE( + '_msg_support.c.em', + package_name=package_name, interface_path=interface_path, + message=action.result, include_directives=include_directives) +}@ + +@{ +TEMPLATE( + '_msg_support.c.em', + package_name=package_name, interface_path=interface_path, + message=action.feedback, include_directives=include_directives) +}@ + +@{ +TEMPLATE( + '_msg_support.c.em', + package_name=package_name, interface_path=interface_path, + message=action.send_goal_service.request_message, + include_directives=include_directives) +}@ + +@{ +TEMPLATE( + '_msg_support.c.em', + package_name=package_name, interface_path=interface_path, + message=action.send_goal_service.response_message, + include_directives=include_directives) +}@ + +@{ +TEMPLATE( + '_msg_support.c.em', + package_name=package_name, interface_path=interface_path, + message=action.get_result_service.request_message, + include_directives=include_directives) +}@ + +@{ +TEMPLATE( + '_msg_support.c.em', + package_name=package_name, interface_path=interface_path, + message=action.get_result_service.response_message, + include_directives=include_directives) +}@ + +@{ +TEMPLATE( + '_msg_support.c.em', + package_name=package_name, interface_path=interface_path, + message=action.feedback_message, include_directives=include_directives) +}@ +@[end for]@ diff --git a/rosidl_generator_py/resource/_msg.py.em b/rosidl_generator_py/resource/_msg.py.em index 24180fb7..bbc5c7c3 100644 --- a/rosidl_generator_py/resource/_msg.py.em +++ b/rosidl_generator_py/resource/_msg.py.em @@ -1,26 +1,27 @@ -# generated from rosidl_generator_py/resource/_msg.py.em -# generated code does not contain a copyright notice +@# Included from rosidl_generator_py/resource/_idl.py.em +@{ -@####################################################################### -@# EmPy template for generating _.py files -@# -@# Context: -@# - module_name -@# - package_name -@# - spec (rosidl_parser.MessageSpecification) -@# Parsed specification of the .msg file -@# - constant_value_to_py (function) -@# - get_python_type (function) -@# - value_to_py (function) -@####################################################################### -@ -from copy import copy -import logging -import traceback +from rosidl_cmake import convert_camel_case_to_lower_case_underscore +from rosidl_generator_py.generate_py_impl import constant_value_to_py +from rosidl_generator_py.generate_py_impl import get_python_type +from rosidl_generator_py.generate_py_impl import value_to_py +from rosidl_parser.definition import ACTION_FEEDBACK_SUFFIX +from rosidl_parser.definition import ACTION_GOAL_SUFFIX +from rosidl_parser.definition import ACTION_RESULT_SUFFIX +from rosidl_parser.definition import Array +from rosidl_parser.definition import BasicType +from rosidl_parser.definition import BaseString +from rosidl_parser.definition import BoundedSequence +from rosidl_parser.definition import NamespacedType +from rosidl_parser.definition import NestedType +from rosidl_parser.definition import Sequence +from rosidl_parser.definition import String +from rosidl_parser.definition import WString +}@ -class Metaclass(type): - """Metaclass of message '@(spec.base_type.type)'.""" +class Metaclass_@(message.structure.type.name)(type): + """Metaclass of message '@(message.structure.type.name)'.""" _CREATE_ROS_MESSAGE = None _CONVERT_FROM_PY = None @@ -29,7 +30,7 @@ class Metaclass(type): _TYPE_SUPPORT = None __constants = { -@[for constant in spec.constants]@ +@[for constant in message.constants.values()]@ '@(constant.name)': @constant_value_to_py(constant.type, constant.value), @[end for]@ } @@ -40,28 +41,46 @@ class Metaclass(type): from rosidl_generator_py import import_type_support module = import_type_support('@(package_name)') except ImportError: - logger = logging.getLogger('rosidl_generator_py.@(spec.base_type.type)') + import logging + import traceback + logger = logging.getLogger( + '@('.'.join(message.structure.type.namespaces + [message.structure.type.name]))') logger.debug( - 'Failed to import needed modules for type support:\n' + traceback.format_exc()) + 'Failed to import needed modules for type support:\n' + + traceback.format_exc()) else: - cls._CREATE_ROS_MESSAGE = module.create_ros_message_msg__@(subfolder)_@(module_name) - cls._CONVERT_FROM_PY = module.convert_from_py_msg__@(subfolder)_@(module_name) - cls._CONVERT_TO_PY = module.convert_to_py_msg__@(subfolder)_@(module_name) - cls._TYPE_SUPPORT = module.type_support_msg__@(subfolder)_@(module_name) - cls._DESTROY_ROS_MESSAGE = module.destroy_ros_message_msg__@(subfolder)_@(module_name) @{ -importable_typesupports = {} -for field in spec.fields: - if not field.type.is_primitive_type(): - key = '%s.msg.%s' % (field.type.pkg_name, field.type.type) - if key not in importable_typesupports: - importable_typesupports[key] = [field.type.pkg_name, field.type.type] -for key in sorted(importable_typesupports.keys()): - (pkg_name, field_name) = importable_typesupports[key] - print('%sfrom %s.msg import %s' % (' ' * 4 * 3, pkg_name, field_name)) - print('%sif %s.__class__._TYPE_SUPPORT is None:' % (' ' * 4 * 3, field_name)) - print('%s%s.__class__.__import_type_support__()' % (' ' * 4 * 4, field_name)) +suffix = '__'.join(message.structure.type.namespaces[1:]) + '__' + convert_camel_case_to_lower_case_underscore(message.structure.type.name) }@ + cls._CREATE_ROS_MESSAGE = module.create_ros_message_msg__@(suffix) + cls._CONVERT_FROM_PY = module.convert_from_py_msg__@(suffix) + cls._CONVERT_TO_PY = module.convert_to_py_msg__@(suffix) + cls._TYPE_SUPPORT = module.type_support_msg__@(suffix) + cls._DESTROY_ROS_MESSAGE = module.destroy_ros_message_msg__@(suffix) +@{ +importable_typesupports = set() +for member in message.structure.members: + type_ = member.type + if isinstance(type_, NestedType): + type_ = type_.basetype + if isinstance(type_, NamespacedType): + if ( + type_.name.endswith(ACTION_GOAL_SUFFIX) or + type_.name.endswith(ACTION_RESULT_SUFFIX) or + type_.name.endswith(ACTION_FEEDBACK_SUFFIX) + ): + action_name, suffix = type_.name.rsplit('_', 1) + typename = (*type_.namespaces, action_name, action_name + '.' + suffix) + else: + typename = (*type_.namespaces, type_.name, type_.name) + importable_typesupports.add(typename) +}@ +@[for typename in sorted(importable_typesupports)]@ + + from @('.'.join(typename[:-2])) import @(typename[-2]) + if @(typename[-1]).__class__._TYPE_SUPPORT is None: + @(typename[-1]).__class__.__import_type_support__() +@[end for]@ @@classmethod def __prepare__(cls, name, bases, **kwargs): @@ -69,119 +88,146 @@ for key in sorted(importable_typesupports.keys()): # the message class under "Data and other attributes defined here:" # as well as populate each message instance return { -@[for constant in spec.constants]@ +@[for constant in message.constants.values()]@ '@(constant.name)': cls.__constants['@(constant.name)'], @[end for]@ -@[for field in spec.fields]@ -@[ if field.default_value]@ - '@(field.name.upper())__DEFAULT': @value_to_py(field.type, field.default_value), +@[for member in message.structure.members]@ +@[ if member.has_annotation('default')]@ + '@(member.name.upper())__DEFAULT': @(value_to_py(member.type, member.get_annotation_value('default')['value'])), @[ end if]@ @[end for]@ } -@[for constant in spec.constants]@ +@[for constant in message.constants.values()]@ @@property def @(constant.name)(self): """Message constant '@(constant.name)'.""" - return Metaclass.__constants['@(constant.name)'] + return Metaclass_@(message.structure.type.name).__constants['@(constant.name)'] @[end for]@ -@[for field in spec.fields]@ -@[ if field.default_value]@ +@[for member in message.structure.members]@ +@[ if member.has_annotation('default')]@ @@property - def @(field.name.upper())__DEFAULT(cls): - """Return default value for message field '@(field.name)'.""" - return @value_to_py(field.type, field.default_value) + def @(member.name.upper())__DEFAULT(cls): + """Return default value for message field '@(member.name)'.""" + return @(value_to_py(member.type, member.get_annotation_value('default')['value'])) @[ end if]@ @[end for]@ -class @(spec.base_type.type)(metaclass=Metaclass): -@[if not spec.constants]@ - """Message class '@(spec.base_type.type)'.""" +class @(message.structure.type.name)(metaclass=Metaclass_@(message.structure.type.name)): +@[if not message.constants]@ + """Message class '@(message.structure.type.name)'.""" @[else]@ """ - Message class '@(spec.base_type.type)'. + Message class '@(message.structure.type.name)'. Constants: -@[ for constant in spec.constants]@ - @(constant.name) +@[ for constant_name in message.constants.keys()]@ + @(constant_name) @[ end for]@ """ @[end if]@ __slots__ = [ -@[for field in spec.fields]@ - '_@(field.name)', +@[for member in message.structure.members]@ + '_@(member.name)', @[end for]@ ] _fields_and_field_types = { -@[for field in spec.fields]@ -@[ if field.type.is_primitive_type() ]@ - '@(field.name)': '@(field.type)', -@[ else ]@ -@[ if field.type.is_array ]@ -@[ if field.type.array_size ]@ -@[ if field.type.is_upper_bound ]@ - '@(field.name)': '@(field.type.pkg_name)/@(field.type.type)[<=@(field.type.array_size)]', -@[ else ]@ - '@(field.name)': '@(field.type.pkg_name)/@(field.type.type)[@(field.type.array_size)]', -@[ end if ]@ -@[ else ]@ - '@(field.name)': '@(field.type.pkg_name)/@(field.type.type)[]', -@[ end if ]@ -@[ else ]@ - '@(field.name)': '@(field.type.pkg_name)/@(field.type.type)', -@[ end if ]@ -@[ end if ]@ +@[for member in message.structure.members]@ +@{ +type_ = member.type +if isinstance(type_, NestedType): + type_ = type_.basetype +}@ + '@(member.name)': '@ +@# the prefix for nested types +@[ if isinstance(member.type, Sequence)]@ +sequence<@ +@[ end if]@ +@# the typename of the non-nested type or the nested basetype +@[ if isinstance(type_, BasicType)]@ +@(type_.type)@ +@[ elif isinstance(type_, BaseString)]@ +@ +@[ if isinstance(type_, WString)]@ +w@ +@[ end if]@ +string@ +@[ if type_.maximum_size is not None]@ +<@(type_.maximum_size)>@ +@[ end if]@ +@[ elif isinstance(type_, NamespacedType)]@ +@('/'.join([type_.namespaces[0], type_.name]))@ +@[ end if]@ +@# the suffix for nested types +@[ if isinstance(member.type, Sequence)]@ +@[ if isinstance(member.type, BoundedSequence)]@ +, @(member.type.upper_bound)@ +@[ end if]@ +>@ +@[ elif isinstance(member.type, Array)]@ +[@(member.type.size)]@ +@[ end if]@ +', @[end for]@ } -@ -@[if len(spec.fields) > 0]@ def __init__(self, **kwargs): assert all('_' + key in self.__slots__ for key in kwargs.keys()), \ 'Invalid arguments passed to constructor: %s' % \ ', '.join(sorted(k for k in kwargs.keys() if '_' + k not in self.__slots__)) -@[ for field in spec.fields]@ -@[ if field.default_value]@ - self.@(field.name) = kwargs.get( - '@(field.name)', @(spec.base_type.type).@(field.name.upper())__DEFAULT) -@[ else]@ -@[ if not field.type.is_primitive_type() and (not field.type.is_array or - (field.type.array_size and not field.type.is_upper_bound))]@ - from @(field.type.pkg_name).msg import @(field.type.type) +@[for member in message.structure.members]@ +@{ +type_ = member.type +if isinstance(type_, NestedType): + type_ = type_.basetype +}@ +@[ if member.has_annotation('default')]@ + self.@(member.name) = kwargs.get( + '@(member.name)', @(message.structure.type.name).@(member.name.upper())__DEFAULT) +@[ else]@ +@[ if isinstance(type_, NamespacedType) and not isinstance(member.type, Sequence)]@ +@[ if ( + type_.name.endswith(ACTION_GOAL_SUFFIX) or + type_.name.endswith(ACTION_RESULT_SUFFIX) or + type_.name.endswith(ACTION_FEEDBACK_SUFFIX) + )]@ + from @('.'.join(type_.namespaces))._@(convert_camel_case_to_lower_case_underscore(type_.name.rsplit('_', 1)[0])) import @(type_.name) +@[ else]@ + from @('.'.join(type_.namespaces)) import @(type_.name) @[ end if]@ -@[ if field.type.array_size and not field.type.is_upper_bound]@ -@[ if field.type.type == 'byte']@ - self.@(field.name) = kwargs.get( - '@(field.name)', - [bytes([0]) for x in range(@(field.type.array_size))] - ) -@[ elif field.type.type == 'char']@ - self.@(field.name) = kwargs.get( - '@(field.name)', - [chr(0) for x in range(@(field.type.array_size))] +@[ end if]@ +@[ if isinstance(member.type, Array)]@ +@[ if isinstance(type_, BasicType) and type_.type == 'octet']@ + self.@(member.name) = kwargs.get( + '@(member.name)', + [bytes([0]) for x in range(@(member.type.size))] ) -@[ else]@ - self.@(field.name) = kwargs.get( - '@(field.name)', - [@(get_python_type(field.type))() for x in range(@(field.type.array_size))] +@[ elif isinstance(type_, BasicType) and type_.type in ('char', 'wchar')]@ + self.@(member.name) = kwargs.get( + '@(member.name)', + [chr(0) for x in range(@(member.type.size))] ) -@[ end if]@ -@[ elif field.type.is_array]@ - self.@(field.name) = kwargs.get('@(field.name)', []) -@[ elif field.type.type == 'byte']@ - self.@(field.name) = kwargs.get('@(field.name)', bytes([0])) -@[ elif field.type.type == 'char']@ - self.@(field.name) = kwargs.get('@(field.name)', chr(0)) @[ else]@ - self.@(field.name) = kwargs.get('@(field.name)', @(get_python_type(field.type))()) + self.@(member.name) = kwargs.get( + '@(member.name)', + [@(get_python_type(type_))() for x in range(@(member.type.size))] + ) @[ end if]@ +@[ elif isinstance(member.type, Sequence)]@ + self.@(member.name) = kwargs.get('@(member.name)', []) +@[ elif isinstance(type_, BasicType) and type_.type == 'octet']@ + self.@(member.name) = kwargs.get('@(member.name)', bytes([0])) +@[ elif isinstance(type_, BasicType) and type_.type in ('char', 'wchar')]@ + self.@(member.name) = kwargs.get('@(member.name)', chr(0)) +@[ else]@ + self.@(member.name) = kwargs.get('@(member.name)', @(get_python_type(type_))()) @[ end if]@ -@[ end for]@ -@[end if]@ +@[ end if]@ +@[end for]@ def __repr__(self): typename = self.__class__.__module__.split('.') @@ -193,136 +239,151 @@ class @(spec.base_type.type)(metaclass=Metaclass): def __eq__(self, other): if not isinstance(other, self.__class__): return False -@[for field in spec.fields]@ - if self.@(field.name) != other.@(field.name): +@[for member in message.structure.members]@ + if self.@(member.name) != other.@(member.name): return False @[end for]@ return True @@classmethod def get_fields_and_field_types(cls): + from copy import copy return copy(cls._fields_and_field_types) -@[for field in spec.fields]@ +@[for member in message.structure.members]@ @{ +type_ = member.type +if isinstance(type_, NestedType): + type_ = type_.basetype + import inspect import builtins noqa_string = '' -if field.name in dict(inspect.getmembers(builtins)).keys(): +if member.name in dict(inspect.getmembers(builtins)).keys(): noqa_string = ' # noqa: A003' }@ @@property@(noqa_string) - def @(field.name)(self): - """Message field '@(field.name)'.""" - return self._@(field.name) + def @(member.name)(self): + """Message field '@(member.name)'.""" + return self._@(member.name) - @@@(field.name).setter@(noqa_string) - def @(field.name)(self, value): + @@@(member.name).setter@(noqa_string) + def @(member.name)(self, value): if __debug__: -@[ if not field.type.is_primitive_type()]@ - from @(field.type.pkg_name).msg import @(field.type.type) +@[ if isinstance(type_, NamespacedType)]@ +@[ if ( + type_.name.endswith(ACTION_GOAL_SUFFIX) or + type_.name.endswith(ACTION_RESULT_SUFFIX) or + type_.name.endswith(ACTION_FEEDBACK_SUFFIX) + )]@ + from @('.'.join(type_.namespaces))._@(convert_camel_case_to_lower_case_underscore(type_.name.rsplit('_', 1)[0])) import @(type_.name) +@[ else]@ + from @('.'.join(type_.namespaces)) import @(type_.name) +@[ end if]@ @[ end if]@ -@[ if field.type.is_array]@ +@[ if isinstance(member.type, NestedType)]@ from collections.abc import Sequence from collections.abc import Set from collections import UserList from collections import UserString -@[ elif field.type.string_upper_bound]@ +@[ elif isinstance(type_, String) and type_.maximum_size is not None]@ from collections import UserString -@[ elif field.type.type == 'byte']@ +@[ elif isinstance(type_, BasicType) and type_.type == 'octet']@ from collections.abc import ByteString -@[ elif field.type.type in ['char']]@ +@[ elif isinstance(type_, BasicType) and type_.type in ('char', 'wchar')]@ from collections import UserString @[ end if]@ assert \ -@[ if field.type.is_array]@ +@[ if isinstance(member.type, NestedType)]@ ((isinstance(value, Sequence) or isinstance(value, Set) or isinstance(value, UserList)) and not isinstance(value, str) and not isinstance(value, UserString) and @{assert_msg_suffixes = ['a set or sequence']}@ -@[ if field.type.type == 'string' and field.type.string_upper_bound]@ - all(len(val) <= @field.type.string_upper_bound for val in value) and -@{assert_msg_suffixes.append('and each string value not longer than %d' % field.type.string_upper_bound)}@ +@[ if isinstance(type_, String) and type_.maximum_size is not None]@ + all(len(val) <= @(type_.maximum_size) for val in value) and +@{assert_msg_suffixes.append('and each string value not longer than %d' % type_.maximum_size)}@ @[ end if]@ -@[ if field.type.array_size]@ -@[ if field.type.is_upper_bound]@ - len(value) <= @(field.type.array_size) and -@{assert_msg_suffixes.insert(1, 'with length <= %d' % field.type.array_size)}@ +@[ if isinstance(member.type, Array) or isinstance(member.type, BoundedSequence)]@ +@[ if isinstance(member.type, BoundedSequence)]@ + len(value) <= @(member.type.upper_bound) and +@{assert_msg_suffixes.insert(1, 'with length <= %d' % member.type.upper_bound)}@ @[ else]@ - len(value) == @(field.type.array_size) and -@{assert_msg_suffixes.insert(1, 'with length %d' % field.type.array_size)}@ + len(value) == @(member.type.size) and +@{assert_msg_suffixes.insert(1, 'with length %d' % member.type.size)}@ @[ end if]@ @[ end if]@ - all(isinstance(v, @(get_python_type(field.type))) for v in value) and -@{assert_msg_suffixes.append("and each value of type '%s'" % get_python_type(field.type))}@ -@[ if field.type.type.startswith('int')]@ + all(isinstance(v, @(get_python_type(type_))) for v in value) and +@{assert_msg_suffixes.append("and each value of type '%s'" % get_python_type(type_))}@ +@[ if isinstance(type_, BasicType) and type_.type.startswith('int')]@ @{ -nbits = int(field.type.type[3:]) +nbits = int(type_.type[3:]) bound = 2**(nbits - 1) }@ all(val >= -@(bound) and val < @(bound) for val in value)), \ @{assert_msg_suffixes.append('and each integer in [%d, %d]' % (-bound, bound - 1))}@ -@[ elif field.type.type.startswith('uint')]@ +@[ elif isinstance(type_, BasicType) and type_.type.startswith('uint')]@ @{ -nbits = int(field.type.type[4:]) +nbits = int(type_.type[4:]) bound = 2**nbits }@ all(val >= 0 and val < @(bound) for val in value)), \ @{assert_msg_suffixes.append('and each unsigned integer in [0, %d]' % (bound - 1))}@ -@[ elif field.type.type == 'char']@ - all(ord(val) >= -128 and ord(val) < 128 for val in value)), \ -@{assert_msg_suffixes.append('and each characters ord() in [-128, 127]')}@ +@[ elif isinstance(type_, BasicType) and type_.type == 'char']@ + all(val >= 0 and val) < 256 for val in value)), \ +@{assert_msg_suffixes.append('and each char in [0, 255]')}@ @[ else]@ True), \ @[ end if]@ - "The '@(field.name)' field must be @(' '.join(assert_msg_suffixes))" -@[ elif field.type.string_upper_bound]@ + "The '@(member.name)' field must be @(' '.join(assert_msg_suffixes))" +@[ elif isinstance(member.type, BaseString) and member.type.maximum_size is not None]@ ((isinstance(value, str) or isinstance(value, UserString)) and - len(value) <= @(field.type.string_upper_bound)), \ - "The '@(field.name)' field must be string value " \ - 'not longer than @(field.type.string_upper_bound)' -@[ elif not field.type.is_primitive_type()]@ - isinstance(value, @(field.type.type)), \ - "The '@(field.name)' field must be a sub message of type '@(field.type.type)'" -@[ elif field.type.type == 'byte']@ + len(value) <= @(member.type.maximum_size)), \ + "The '@(member.name)' field must be string value " \ + 'not longer than @(type_.maximum_size)' +@[ elif isinstance(type_, NamespacedType)]@ + isinstance(value, @(type_.name)), \ + "The '@(member.name)' field must be a sub message of type '@(type_.name)'" +@[ elif isinstance(type_, BasicType) and type_.type == 'octet']@ ((isinstance(value, bytes) or isinstance(value, ByteString)) and len(value) == 1), \ - "The '@(field.name)' field must of type 'bytes' or 'ByteString' with a length 1" -@[ elif field.type.type == 'char']@ + "The '@(member.name)' field must of type 'bytes' or 'ByteString' with a length 1" +@[ elif isinstance(type_, BasicType) and type_.type == 'char']@ ((isinstance(value, str) or isinstance(value, UserString)) and len(value) == 1 and ord(value) >= -128 and ord(value) < 128), \ - "The '@(field.name)' field must of type 'str' or 'UserString' " \ + "The '@(member.name)' field must of type 'str' or 'UserString' " \ 'with a length 1 and the character ord() in [-128, 127]' -@[ elif field.type.type in [ - 'bool', - 'float32', 'float64', +@[ elif isinstance(type_, BaseString)]@ + isinstance(value, str), \ + "The '@(member.name)' field must of type '@(get_python_type(type_))'" +@[ elif isinstance(type_, BasicType) and type_.type in [ + 'boolean', + 'float', 'double', 'int8', 'uint8', 'int16', 'uint16', 'int32', 'uint32', 'int64', 'uint64', - 'string', ]]@ - isinstance(value, @(get_python_type(field.type))), \ - "The '@(field.name)' field must of type '@(get_python_type(field.type))'" -@[ if field.type.type.startswith('int')]@ + isinstance(value, @(get_python_type(type_))), \ + "The '@(member.name)' field must of type '@(get_python_type(type_))'" +@[ if type_.type.startswith('int')]@ @{ -nbits = int(field.type.type[3:]) +nbits = int(type_.type[3:]) bound = 2**(nbits - 1) }@ assert value >= -@(bound) and value < @(bound), \ - "The '@(field.name)' field must be an integer in [@(-bound), @(bound - 1)]" -@[ elif field.type.type.startswith('uint')]@ + "The '@(member.name)' field must be an integer in [@(-bound), @(bound - 1)]" +@[ elif type_.type.startswith('uint')]@ @{ -nbits = int(field.type.type[4:]) +nbits = int(type_.type[4:]) bound = 2**nbits }@ assert value >= 0 and value < @(bound), \ - "The '@(field.name)' field must be an unsigned integer in [0, @(bound - 1)]" + "The '@(member.name)' field must be an unsigned integer in [0, @(bound - 1)]" @[ end if]@ @[ else]@ False @[ end if]@ - self._@(field.name) = value + self._@(member.name) = value @[end for]@ diff --git a/rosidl_generator_py/resource/_msg_pkg_typesupport_entry_point.c.em b/rosidl_generator_py/resource/_msg_pkg_typesupport_entry_point.c.em index 37ecb9d7..d9cda9ca 100644 --- a/rosidl_generator_py/resource/_msg_pkg_typesupport_entry_point.c.em +++ b/rosidl_generator_py/resource/_msg_pkg_typesupport_entry_point.c.em @@ -1,196 +1,83 @@ -// generated from rosidl_generator_py/resource/_msg_pkg_typesupport_entry_point.c.em -// generated code does not contain a copyright notice - -@####################################################################### -@# EmPy template for generating __s.ep._c.c files -@# -@# Context: -@# - package_name -@# - message_specs (list of rosidl_parser.MessageSpecification) -@# Parsed specification of the .msg files -@# - service_specs (list of rosidl_parser.ServiceSpecification) -@# Parsed specification of the .srv files -@# - action_specs (list of rosidl_parser.ActionSpecification) -@# Parsed specification of the .action files -@# - typesupport_impl (string identifying the typesupport used) -@# - convert_camel_case_to_lower_case_underscore (function) -@####################################################################### -@ -#include -#include -#include - +@# Included from rosidl_generator_py/resource/_idl_pkg_typesupport_entry_point.c.em @{ -static_includes = set([ - '#include ', -]) -if message_specs: - static_includes.add('#include ') -if service_specs: - static_includes.add('#include ') -if action_specs: - static_includes.add('#include ') -}@ -@[for value in sorted(static_includes)]@ -@(value) -@[end for]@ -@{ -includes = {} -for spec, subfolder in message_specs: - type_name = spec.base_type.type - module_name = convert_camel_case_to_lower_case_underscore(type_name) - key = '%s/%s/%s' % (spec.base_type.pkg_name, subfolder, module_name) - includes[key + '_support'] = '#include <%s__type_support.h>' % key - includes[key + '_struct'] = '#include <%s__struct.h>' % key - includes[key + '_functions'] = '#include <%s__functions.h>' % key - -for spec, subfolder in service_specs: - type_name = convert_camel_case_to_lower_case_underscore(spec.srv_name) - module_name = convert_camel_case_to_lower_case_underscore(type_name) - key = '%s/%s/%s' % (spec.pkg_name, subfolder, module_name) - includes[key] = '#include <%s.h>' % key +from rosidl_cmake import convert_camel_case_to_lower_case_underscore + +include_parts = idl_type.namespaces + [ + convert_camel_case_to_lower_case_underscore(idl_type.name)] +include_base = '/'.join(include_parts) + +header_files = [ + 'stdbool.h', + 'stdint.h', + 'rosidl_generator_c/visibility_control.h', + 'rosidl_generator_c/message_type_support_struct.h', + 'rosidl_generator_c/service_type_support_struct.h', + 'rosidl_generator_c/action_type_support_struct.h', + include_base + '__type_support.h', + include_base + '__struct.h', + include_base + '__functions.h', +] }@ -@[for v in sorted(includes.values())]@ -@(v) +@[for header_file in header_files]@ +@[ if header_file in include_directives]@ +// already included above +// @ +@[ else]@ +@{include_directives.add(header_file)}@ +@[ end if]@ +@[ if '/' not in header_file]@ +#include <@(header_file)> +@[ else]@ +#include "@(header_file)" +@[ end if]@ @[end for]@ @ -@[for spec, subfolder in message_specs]@ @{ -pkg_name = spec.base_type.pkg_name -type_name = spec.base_type.type -module_name = convert_camel_case_to_lower_case_underscore(type_name) -msg_typename = '%s__%s__%s' % (pkg_name, subfolder, type_name) +module_name = convert_camel_case_to_lower_case_underscore(message.structure.type.name) +msg_typename = '__'.join(message.structure.type.namespaces + [message.structure.type.name]) }@ -static void * @(pkg_name)__@(subfolder)__@(module_name)__create_ros_message(void) +static void * @('__'.join(message.structure.type.namespaces + [module_name]))__create_ros_message(void) { return @(msg_typename)__create(); } -static void @(pkg_name)__@(subfolder)__@(module_name)__destroy_ros_message(void * raw_ros_message) +static void @('__'.join(message.structure.type.namespaces + [module_name]))__destroy_ros_message(void * raw_ros_message) { @(msg_typename) * ros_message = (@(msg_typename) *)raw_ros_message; @(msg_typename)__destroy(ros_message); } ROSIDL_GENERATOR_C_IMPORT -bool @(pkg_name)__@(subfolder)__@(module_name)__convert_from_py(PyObject * _pymsg, void * ros_message); +bool @('__'.join(message.structure.type.namespaces + [module_name]))__convert_from_py(PyObject * _pymsg, void * ros_message); ROSIDL_GENERATOR_C_IMPORT -PyObject * @(pkg_name)__@(subfolder)__@(module_name)__convert_to_py(void * raw_ros_message); -@[end for]@ +PyObject * @('__'.join(message.structure.type.namespaces + [module_name]))__convert_to_py(void * raw_ros_message); -static PyMethodDef @(package_name)__methods[] = { - {NULL, NULL, 0, NULL} /* sentinel */ -}; - -static struct PyModuleDef @(package_name)__module = { - PyModuleDef_HEAD_INIT, - "_@(package_name)_support", - "_@(package_name)_doc", - -1, /* -1 means that the module keeps state in global variables */ - @(package_name)__methods, - NULL, - NULL, - NULL, - NULL, -}; -@ -@[for spec, subfolder in message_specs]@ -@{ -type_name = convert_camel_case_to_lower_case_underscore(spec.base_type.type) -function_names = ['create_ros_message', 'destroy_ros_message', 'convert_from_py', 'convert_to_py', 'type_support'] -}@ ROSIDL_GENERATOR_C_IMPORT const rosidl_message_type_support_t * -ROSIDL_GET_MSG_TYPE_SUPPORT(@(pkg_name), @(subfolder), @(spec.msg_name)); - -int8_t -_register_msg_type__@(subfolder)__@(type_name)(PyObject * pymodule) -{ - int8_t err; -@[ for function_name in function_names]@ +ROSIDL_GET_MSG_TYPE_SUPPORT(@(', '.join(message.structure.type.namespaces + [message.structure.type.name]))); - PyObject * pyobject_@(function_name) = NULL; - pyobject_@(function_name) = PyCapsule_New( -@[ if function_name != 'type_support']@ - (void *)&@(pkg_name)__@(subfolder)__@(type_name)__@(function_name), -@[ else]@ - (void *)ROSIDL_GET_MSG_TYPE_SUPPORT(@(pkg_name), @(subfolder), @(spec.msg_name)), -@[ end if]@ - NULL, NULL); - if (!pyobject_@(function_name)) { - // previously added objects will be removed when the module is destroyed - return -1; - } - err = PyModule_AddObject( - pymodule, - "@(function_name)_msg__@(subfolder)_@(type_name)", - pyobject_@(function_name)); - if (err) { - // the created capsule needs to be decremented - Py_XDECREF(pyobject_@(function_name)); - // previously added objects will be removed when the module is destroyed - return err; - } -@[ end for]@ - return 0; -} -@[end for]@ -@ -@[for spec, subfolder in service_specs]@ @{ -type_name = convert_camel_case_to_lower_case_underscore(spec.srv_name) -function_name = 'type_support' +register_function = '_register_msg_type__' + '__'.join(message.structure.type.namespaces[1:] + [module_name]) +register_functions.append(register_function) }@ - -ROSIDL_GENERATOR_C_IMPORT -const rosidl_service_type_support_t * -ROSIDL_TYPESUPPORT_INTERFACE__SERVICE_SYMBOL_NAME(rosidl_typesupport_c, @(spec.pkg_name), @(subfolder), @(spec.srv_name))(); - int8_t -_register_srv_type__@(subfolder)__@(type_name)(PyObject * pymodule) +@(register_function)(PyObject * pymodule) { int8_t err; - PyObject * pyobject_@(function_name) = NULL; - pyobject_@(function_name) = PyCapsule_New( - (void *)ROSIDL_TYPESUPPORT_INTERFACE__SERVICE_SYMBOL_NAME(rosidl_typesupport_c, @(spec.pkg_name), @(subfolder), @(spec.srv_name))(), - NULL, NULL); - if (!pyobject_@(function_name)) { - // previously added objects will be removed when the module is destroyed - return -1; - } - err = PyModule_AddObject( - pymodule, - "@(function_name)_srv__@(subfolder)_@(type_name)", - pyobject_@(function_name)); - if (err) { - // the created capsule needs to be decremented - Py_XDECREF(pyobject_@(function_name)); - // previously added objects will be removed when the module is destroyed - return err; - } - return 0; -} -@[end for]@ -@ -@[for spec, subfolder in action_specs]@ @{ -type_name = convert_camel_case_to_lower_case_underscore(spec.action_name) -function_name = 'type_support' +function_names = ['create_ros_message', 'destroy_ros_message', 'convert_from_py', 'convert_to_py', 'type_support'] }@ +@[for function_name in function_names]@ -ROSIDL_GENERATOR_C_IMPORT -const rosidl_action_type_support_t * -ROSIDL_TYPESUPPORT_INTERFACE__ACTION_SYMBOL_NAME(rosidl_typesupport_c, @(spec.pkg_name), @(subfolder), @(spec.action_name))(); - -int8_t -_register_action_type__@(subfolder)__@(type_name)(PyObject * pymodule) -{ - int8_t err; PyObject * pyobject_@(function_name) = NULL; pyobject_@(function_name) = PyCapsule_New( - (void *)ROSIDL_TYPESUPPORT_INTERFACE__ACTION_SYMBOL_NAME(rosidl_typesupport_c, @(spec.pkg_name), @(subfolder), @(spec.action_name))(), +@[ if function_name != 'type_support']@ + (void *)&@('__'.join(message.structure.type.namespaces + [module_name]))__@(function_name), +@[ else]@ + (void *)ROSIDL_GET_MSG_TYPE_SUPPORT(@(', '.join(message.structure.type.namespaces + [message.structure.type.name]))), +@[ end if]@ NULL, NULL); if (!pyobject_@(function_name)) { // previously added objects will be removed when the module is destroyed @@ -198,7 +85,7 @@ _register_action_type__@(subfolder)__@(type_name)(PyObject * pymodule) } err = PyModule_AddObject( pymodule, - "@(function_name)_action__@(subfolder)_@(type_name)", + "@(function_name)_msg__@('__'.join(message.structure.type.namespaces[1:] + [module_name]))", pyobject_@(function_name)); if (err) { // the created capsule needs to be decremented @@ -206,49 +93,6 @@ _register_action_type__@(subfolder)__@(type_name)(PyObject * pymodule) // previously added objects will be removed when the module is destroyed return err; } - return 0; -} -@[end for]@ - -PyMODINIT_FUNC -PyInit_@(package_name)_s__@(typesupport_impl)(void) -{ - PyObject * pymodule = NULL; - pymodule = PyModule_Create(&@(package_name)__module); - if (!pymodule) { - return NULL; - } - int8_t err; -@[for spec, subfolder in message_specs]@ -@{ -type_name = convert_camel_case_to_lower_case_underscore(spec.base_type.type) -}@ - err = _register_msg_type__@(subfolder)__@(type_name)(pymodule); - if (err) { - Py_XDECREF(pymodule); - return NULL; - } @[end for]@ -@[for spec, subfolder in service_specs]@ -@{ -type_name = convert_camel_case_to_lower_case_underscore(spec.srv_name) -}@ - err = _register_srv_type__@(subfolder)__@(type_name)(pymodule); - if (err) { - Py_XDECREF(pymodule); - return NULL; - } -@[end for]@ -@[for spec, subfolder in action_specs]@ -@{ -type_name = convert_camel_case_to_lower_case_underscore(spec.action_name) -}@ - err = _register_action_type__@(subfolder)__@(type_name)(pymodule); - if (err) { - Py_XDECREF(pymodule); - return NULL; - } -@[end for]@ - - return pymodule; + return 0; } diff --git a/rosidl_generator_py/resource/_msg_support.c.em b/rosidl_generator_py/resource/_msg_support.c.em index c8833dd6..ac639655 100644 --- a/rosidl_generator_py/resource/_msg_support.c.em +++ b/rosidl_generator_py/resource/_msg_support.c.em @@ -1,79 +1,133 @@ -// generated from rosidl_generator_py/resource/_msg_support.c.em -// generated code does not contain a copyright notice +@# Included from rosidl_generator_py/resource/_idl.py.em +@{ +from rosidl_cmake import convert_camel_case_to_lower_case_underscore +from rosidl_parser.definition import Array +from rosidl_parser.definition import BasicType +from rosidl_parser.definition import NamespacedType +from rosidl_parser.definition import NestedType +from rosidl_parser.definition import Sequence +from rosidl_parser.definition import String +from rosidl_parser.definition import WString -@####################################################################### -@# EmPy template for generating __s.c files -@# -@# Context: -@# - module_name -@# - spec (rosidl_parser.MessageSpecification) -@# Parsed specification of the .msg file -@# - convert_camel_case_to_lower_case_underscore (function) -@# - primitive_msg_type_to_c (function) -@####################################################################### -@ -#include -#include -#include +def primitive_msg_type_to_c(type_): + from rosidl_generator_c import BASIC_IDL_TYPES_TO_C + from rosidl_parser.definition import BasicType + from rosidl_parser.definition import String + if isinstance(type_, String): + return 'rosidl_generator_c__String' + assert isinstance(type_, BasicType) + return BASIC_IDL_TYPES_TO_C[type_.type] -#include <@(spec.base_type.pkg_name)/@(subfolder)/@(module_name)__struct.h> -#include <@(spec.base_type.pkg_name)/@(subfolder)/@(module_name)__functions.h> + +include_parts = [package_name] + list(interface_path.parents[0].parts) + \ + [convert_camel_case_to_lower_case_underscore(interface_path.stem)] +include_base = '/'.join(include_parts) + +header_files = [ + 'Python.h', + 'stdbool.h', + 'rosidl_generator_c/visibility_control.h', + include_base + '__struct.h', + include_base + '__functions.h', +] +}@ +@[for header_file in header_files]@ +@[ if header_file in include_directives]@ +// already included above +// @ +@[ else]@ +@{include_directives.add(header_file)}@ +@[ end if]@ +@[ if '/' not in header_file]@ +#include <@(header_file)> +@[ else]@ +#include "@(header_file)" +@[ end if]@ +@[end for]@ @{ have_not_included_primitive_arrays = True have_not_included_string = True -nested_array_dict = {} +have_not_included_wstring = True +nested_types = set() }@ -@[for field in spec.fields]@ -@[ if field.type.is_array and have_not_included_primitive_arrays]@ -@{have_not_included_primitive_arrays = False}@ -#include -#include - -@[ end if]@ -@[ if field.type.type == 'string' and have_not_included_string]@ -@{have_not_included_string = False}@ -#include -#include +@[for member in message.structure.members]@ +@{ +type_ = member.type +if isinstance(type_, NestedType): + type_ = type_.basetype +header_files = [] +if isinstance(member.type, NestedType) and have_not_included_primitive_arrays: + have_not_included_primitive_arrays = False + header_files += [ + 'rosidl_generator_c/primitives_sequence.h', + 'rosidl_generator_c/primitives_sequence_functions.h'] +if isinstance(type_, String) and have_not_included_string: + have_not_included_string = False + header_files += [ + 'rosidl_generator_c/string.h', + 'rosidl_generator_c/string_functions.h'] +if isinstance(type_, WString) and have_not_included_wstring: + have_not_included_wstring = False + header_files += [ + 'rosidl_generator_c/u16string.h', + 'rosidl_generator_c/u16string_functions.h'] +}@ +@[if header_files]@ +@[ for header_file in header_files]@ +@[ if header_file in include_directives]@ +// already included above +// @ +@[ else]@ +@{include_directives.add(header_file)}@ +@[ end if]@ +#include "@(header_file)" +@[ end for]@ -@[ end if]@ +@[end if]@ @{ -if not field.type.is_primitive_type() and field.type.is_array: - if field.type.type not in nested_array_dict: - nested_array_dict[field.type.type] = field.type.pkg_name +if isinstance(member.type, NestedType) and isinstance(member.type.basetype, NamespacedType): + nested_types.add((*member.type.basetype.namespaces, member.type.basetype.name)) }@ @[end for]@ -@[if nested_array_dict != {}]@ +@[if nested_types]@ // Nested array functions includes -@[ for key in nested_array_dict]@ -#include <@(nested_array_dict[key])/msg/@convert_camel_case_to_lower_case_underscore(key)__functions.h> +@[ for type_ in sorted(nested_types)]@ +#include "@('/'.join(type_[:-1]))/@(convert_camel_case_to_lower_case_underscore(type_[-1]))__functions.h" @[ end for]@ // end nested array functions include @[end if]@ @{ -msg_typename = '%s__%s__%s' % (spec.base_type.pkg_name, subfolder, spec.base_type.type) +msg_typename = '__'.join(message.structure.type.namespaces + [message.structure.type.name]) }@ @ -@[for field in spec.fields]@ -@{lowercase_field_type = convert_camel_case_to_lower_case_underscore(field.type.type)}@ -@[ if not field.type.is_primitive_type()]@ -@[ if spec.base_type.pkg_name != field.type.pkg_name]@ +@[for member in message.structure.members]@ +@{ +type_ = member.type +if isinstance(type_, NestedType): + type_ = type_.basetype +}@ +@[ if isinstance(type_, NamespacedType)]@ +@[ if type_.namespaces[0] != package_name]@ ROSIDL_GENERATOR_C_IMPORT @[ end if]@ -bool @(field.type.pkg_name)__msg__@(lowercase_field_type)__convert_from_py(PyObject * _pymsg, void * _ros_message); -@[ if spec.base_type.pkg_name != field.type.pkg_name]@ +bool @('__'.join(type_.namespaces + [convert_camel_case_to_lower_case_underscore(type_.name)]))__convert_from_py(PyObject * _pymsg, void * _ros_message); +@[ if type_.namespaces[0] != package_name]@ ROSIDL_GENERATOR_C_IMPORT @[ end if]@ -PyObject * @(field.type.pkg_name)__msg__@(lowercase_field_type)__convert_to_py(void * raw_ros_message); +PyObject * @('__'.join(type_.namespaces + [convert_camel_case_to_lower_case_underscore(type_.name)]))__convert_to_py(void * raw_ros_message); @[ end if]@ @[end for]@ +@{ +module_name = '_' + convert_camel_case_to_lower_case_underscore(interface_path.stem) +}@ ROSIDL_GENERATOR_C_EXPORT -bool @(spec.base_type.pkg_name)__@(subfolder)__@(module_name)__convert_from_py(PyObject * _pymsg, void * _ros_message) +bool @('__'.join(message.structure.type.namespaces + [convert_camel_case_to_lower_case_underscore(message.structure.type.name)]))__convert_from_py(PyObject * _pymsg, void * _ros_message) { @{ -full_classname = '%s.%s._%s.%s' % (spec.base_type.pkg_name, subfolder, module_name, spec.base_type.type) +full_classname = '%s.%s.%s' % ('.'.join(message.structure.type.namespaces), module_name, message.structure.type.name) }@ // check that the passed message is of the expected Python class { @@ -107,46 +161,47 @@ full_classname = '%s.%s._%s.%s' % (spec.base_type.pkg_name, subfolder, module_na full_classname_dest, @(len(full_classname))) == 0); } @(msg_typename) * ros_message = _ros_message; -@[if not spec.fields]@ - (void)ros_message; -@[end if]@ -@[for field in spec.fields]@ - { // @(field.name) - PyObject * field = PyObject_GetAttrString(_pymsg, "@(field.name)"); +@[for member in message.structure.members]@ +@{ +type_ = member.type +if isinstance(type_, NestedType): + type_ = type_.basetype +}@ + { // @(member.name) + PyObject * field = PyObject_GetAttrString(_pymsg, "@(member.name)"); if (!field) { return false; } -@[ if not field.type.is_primitive_type()]@ +@[ if isinstance(type_, NamespacedType)]@ @{ -nested_type = '%s__%s__%s' % (field.type.pkg_name, 'msg', field.type.type) -lowercase_field_type = convert_camel_case_to_lower_case_underscore(field.type.type) +nested_type = '__'.join(type_.namespaces + [type_.name]) }@ -@[ if field.type.is_array]@ - PyObject * seq_field = PySequence_Fast(field, "expected a sequence in '@(field.name)'"); +@[ if isinstance(member.type, NestedType)]@ + PyObject * seq_field = PySequence_Fast(field, "expected a sequence in '@(member.name)'"); if (!seq_field) { Py_DECREF(field); return false; } -@[ if field.type.array_size is None or field.type.is_upper_bound]@ +@[ if isinstance(member.type, Sequence)]@ Py_ssize_t size = PySequence_Size(field); if (-1 == size) { Py_DECREF(seq_field); Py_DECREF(field); return false; } - if (!@(nested_type)__Sequence__init(&(ros_message->@(field.name)), size)) { + if (!@(nested_type)__Sequence__init(&(ros_message->@(member.name)), size)) { PyErr_SetString(PyExc_RuntimeError, "unable to create @(nested_type)__Sequence ros_message"); Py_DECREF(seq_field); Py_DECREF(field); return false; } - @(nested_type) * dest = ros_message->@(field.name).data; + @(nested_type) * dest = ros_message->@(member.name).data; @[ else]@ - Py_ssize_t size = @(field.type.array_size); - @(nested_type) * dest = ros_message->@(field.name); + Py_ssize_t size = @(member.type.size); + @(nested_type) * dest = ros_message->@(member.name); @[ end if]@ for (Py_ssize_t i = 0; i < size; ++i) { - if (!@(field.type.pkg_name)__msg__@(lowercase_field_type)__convert_from_py(PySequence_Fast_GET_ITEM(seq_field, i), &dest[i])) { + if (!@('__'.join(type_.namespaces + [convert_camel_case_to_lower_case_underscore(type_.name)]))__convert_from_py(PySequence_Fast_GET_ITEM(seq_field, i), &dest[i])) { Py_DECREF(seq_field); Py_DECREF(field); return false; @@ -154,43 +209,43 @@ lowercase_field_type = convert_camel_case_to_lower_case_underscore(field.type.ty } Py_DECREF(seq_field); @[ else]@ - if (!@(field.type.pkg_name)__msg__@(lowercase_field_type)__convert_from_py(field, &ros_message->@(field.name))) { + if (!@('__'.join(type_.namespaces + [convert_camel_case_to_lower_case_underscore(type_.name)]))__convert_from_py(field, &ros_message->@(member.name))) { Py_DECREF(field); return false; } @[ end if]@ -@[ elif field.type.is_array]@ - PyObject * seq_field = PySequence_Fast(field, "expected a sequence in '@(field.name)'"); +@[ elif isinstance(member.type, NestedType)]@ + PyObject * seq_field = PySequence_Fast(field, "expected a sequence in '@(member.name)'"); if (!seq_field) { Py_DECREF(field); return false; } -@[ if field.type.array_size is None or field.type.is_upper_bound]@ +@[ if isinstance(member.type, Sequence)]@ Py_ssize_t size = PySequence_Size(field); if (-1 == size) { Py_DECREF(seq_field); Py_DECREF(field); return false; } -@[ if field.type.type == 'string']@ - if (!rosidl_generator_c__String__Sequence__init(&(ros_message->@(field.name)), size)) { +@[ if isinstance(member.type.basetype, String)]@ + if (!rosidl_generator_c__String__Sequence__init(&(ros_message->@(member.name)), size)) { PyErr_SetString(PyExc_RuntimeError, "unable to create String__Sequence ros_message"); Py_DECREF(seq_field); Py_DECREF(field); return false; } @[ else]@ - if (!rosidl_generator_c__@(field.type.type)__Sequence__init(&(ros_message->@(field.name)), size)) { - PyErr_SetString(PyExc_RuntimeError, "unable to create @(field.type.type)__Sequence ros_message"); + if (!rosidl_generator_c__@(member.type.basetype.type)__Sequence__init(&(ros_message->@(member.name)), size)) { + PyErr_SetString(PyExc_RuntimeError, "unable to create @(member.type.basetype.type)__Sequence ros_message"); Py_DECREF(seq_field); Py_DECREF(field); return false; } @[ end if]@ - @primitive_msg_type_to_c(field.type.type) * dest = ros_message->@(field.name).data; + @primitive_msg_type_to_c(member.type.basetype) * dest = ros_message->@(member.name).data; @[ else]@ - Py_ssize_t size = @(field.type.array_size); - @primitive_msg_type_to_c(field.type.type) * dest = ros_message->@(field.name); + Py_ssize_t size = @(member.type.size); + @primitive_msg_type_to_c(member.type.basetype) * dest = ros_message->@(member.name); @[ end if]@ for (Py_ssize_t i = 0; i < size; ++i) { PyObject * item = PySequence_Fast_GET_ITEM(seq_field, i); @@ -199,7 +254,7 @@ lowercase_field_type = convert_camel_case_to_lower_case_underscore(field.type.ty Py_DECREF(field); return false; } -@[ if field.type.type == 'char']@ +@[ if isinstance(member.type.basetype, BasicType) and member.type.basetype.type == 'char']@ assert(PyUnicode_Check(item)); PyObject * encoded_item = PyUnicode_AsASCIIString(item); if (!encoded_item) { @@ -207,12 +262,12 @@ lowercase_field_type = convert_camel_case_to_lower_case_underscore(field.type.ty Py_DECREF(field); return false; } - @primitive_msg_type_to_c(field.type.type) tmp = PyBytes_AS_STRING(encoded_item)[0]; + @primitive_msg_type_to_c(member.type.basetype) tmp = PyBytes_AS_STRING(encoded_item)[0]; Py_DECREF(encoded_item); -@[ elif field.type.type == 'byte']@ +@[ elif isinstance(member.type.basetype, BasicType) and member.type.basetype.type == 'octet']@ assert(PyBytes_Check(item)); - @primitive_msg_type_to_c(field.type.type) tmp = PyBytes_AS_STRING(item)[0]; -@[ elif field.type.type == 'string']@ + @primitive_msg_type_to_c(member.type.basetype) tmp = PyBytes_AS_STRING(item)[0]; +@[ elif isinstance(member.type.basetype, String)]@ assert(PyUnicode_Check(item)); PyObject * encoded_item = PyUnicode_AsASCIIString(item); if (!encoded_item) { @@ -222,101 +277,101 @@ lowercase_field_type = convert_camel_case_to_lower_case_underscore(field.type.ty } rosidl_generator_c__String__assign(&dest[i], PyBytes_AS_STRING(encoded_item)); Py_DECREF(encoded_item); -@[ elif field.type.type == 'bool']@ +@[ elif isinstance(member.type.basetype, BasicType) and member.type.basetype.type == 'boolean']@ assert(PyBool_Check(item)); - @primitive_msg_type_to_c(field.type.type) tmp = (item == Py_True); -@[ elif field.type.type in ['float32', 'float64']]@ + @primitive_msg_type_to_c(member.type.basetype) tmp = (item == Py_True); +@[ elif isinstance(member.type.basetype, BasicType) and member.type.basetype.type in ('float', 'double')]@ assert(PyFloat_Check(item)); -@[ if field.type.type == 'float32']@ - @primitive_msg_type_to_c(field.type.type) tmp = (float)PyFloat_AS_DOUBLE(item); +@[ if member.type.basetype.type == 'float']@ + @primitive_msg_type_to_c(member.type.basetype) tmp = (float)PyFloat_AS_DOUBLE(item); @[ else]@ - @primitive_msg_type_to_c(field.type.type) tmp = PyFloat_AS_DOUBLE(item); + @primitive_msg_type_to_c(member.type.basetype) tmp = PyFloat_AS_DOUBLE(item); @[ end if]@ -@[ elif field.type.type in [ +@[ elif isinstance(member.type.basetype, BasicType) and member.type.basetype.type in ( 'int8', 'int16', 'int32', - ]]@ + )]@ assert(PyLong_Check(item)); - @primitive_msg_type_to_c(field.type.type) tmp = (@(primitive_msg_type_to_c(field.type.type)))PyLong_AsLong(item); -@[ elif field.type.type in [ + @primitive_msg_type_to_c(member.type.basetype) tmp = (@(primitive_msg_type_to_c(member.type.basetype)))PyLong_AsLong(item); +@[ elif isinstance(member.type.basetype, BasicType) and member.type.basetype.type in ( 'uint8', 'uint16', 'uint32', - ]]@ + )]@ assert(PyLong_Check(item)); -@[ if field.type.type == 'uint32']@ - @primitive_msg_type_to_c(field.type.type) tmp = PyLong_AsUnsignedLong(item); +@[ if isinstance(member.type.basetype, BasicType) and member.type.basetype.type == 'uint32']@ + @primitive_msg_type_to_c(member.type.basetype) tmp = PyLong_AsUnsignedLong(item); @[ else]@ - @primitive_msg_type_to_c(field.type.type) tmp = (@(primitive_msg_type_to_c(field.type.type)))PyLong_AsUnsignedLong(item); + @primitive_msg_type_to_c(member.type.basetype) tmp = (@(primitive_msg_type_to_c(member.type.basetype)))PyLong_AsUnsignedLong(item); @[ end if] -@[ elif field.type.type == 'int64']@ +@[ elif isinstance(member.type.basetype, BasicType) and member.type.basetype.type == 'int64']@ assert(PyLong_Check(item)); - @primitive_msg_type_to_c(field.type.type) tmp = PyLong_AsLongLong(item); -@[ elif field.type.type == 'uint64']@ + @primitive_msg_type_to_c(member.type.basetype) tmp = PyLong_AsLongLong(item); +@[ elif isinstance(member.type.basetype, BasicType) and member.type.basetype.type == 'uint64']@ assert(PyLong_Check(item)); - @primitive_msg_type_to_c(field.type.type) tmp = PyLong_AsUnsignedLongLong(item); + @primitive_msg_type_to_c(member.type.basetype) tmp = PyLong_AsUnsignedLongLong(item); @[ end if]@ -@[ if field.type.type != 'string']@ - memcpy(&dest[i], &tmp, sizeof(@primitive_msg_type_to_c(field.type.type))); +@[ if isinstance(member.type.basetype, BasicType)]@ + memcpy(&dest[i], &tmp, sizeof(@primitive_msg_type_to_c(member.type.basetype))); @[ end if]@ } Py_DECREF(seq_field); -@[ elif field.type.type == 'char']@ +@[ elif isinstance(member.type, BasicType) and member.type.type == 'char']@ assert(PyUnicode_Check(field)); PyObject * encoded_field = PyUnicode_AsASCIIString(field); if (!encoded_field) { Py_DECREF(field); return false; } - ros_message->@(field.name) = PyBytes_AS_STRING(encoded_field)[0]; + ros_message->@(member.name) = PyBytes_AS_STRING(encoded_field)[0]; Py_DECREF(encoded_field); -@[ elif field.type.type == 'byte']@ +@[ elif isinstance(member.type, BasicType) and member.type.type == 'octet']@ assert(PyBytes_Check(field)); - ros_message->@(field.name) = PyBytes_AS_STRING(field)[0]; -@[ elif field.type.type == 'string']@ + ros_message->@(member.name) = PyBytes_AS_STRING(field)[0]; +@[ elif isinstance(member.type, String)]@ assert(PyUnicode_Check(field)); PyObject * encoded_field = PyUnicode_AsASCIIString(field); if (!encoded_field) { Py_DECREF(field); return false; } - rosidl_generator_c__String__assign(&ros_message->@(field.name), PyBytes_AS_STRING(encoded_field)); + rosidl_generator_c__String__assign(&ros_message->@(member.name), PyBytes_AS_STRING(encoded_field)); Py_DECREF(encoded_field); -@[ elif field.type.type == 'bool']@ +@[ elif isinstance(member.type, BasicType) and member.type.type == 'boolean']@ assert(PyBool_Check(field)); - ros_message->@(field.name) = (Py_True == field); -@[ elif field.type.type in ['float32', 'float64']]@ + ros_message->@(member.name) = (Py_True == field); +@[ elif isinstance(member.type, BasicType) and member.type.type in ('float', 'double')]@ assert(PyFloat_Check(field)); -@[ if field.type.type == 'float32']@ - ros_message->@(field.name) = (float)PyFloat_AS_DOUBLE(field); +@[ if member.type.type == 'float']@ + ros_message->@(member.name) = (float)PyFloat_AS_DOUBLE(field); @[ else]@ - ros_message->@(field.name) = PyFloat_AS_DOUBLE(field); + ros_message->@(member.name) = PyFloat_AS_DOUBLE(field); @[ end if]@ -@[ elif field.type.type in [ +@[ elif isinstance(member.type, BasicType) and member.type.type in ( 'int8', 'int16', 'int32', - ]]@ + )]@ assert(PyLong_Check(field)); - ros_message->@(field.name) = (@(primitive_msg_type_to_c(field.type.type)))PyLong_AsLong(field); -@[ elif field.type.type in [ + ros_message->@(member.name) = (@(primitive_msg_type_to_c(member.type)))PyLong_AsLong(field); +@[ elif isinstance(member.type, BasicType) and member.type.type in ( 'uint8', 'uint16', 'uint32', - ]]@ + )]@ assert(PyLong_Check(field)); -@[ if field.type.type == 'uint32']@ - ros_message->@(field.name) = PyLong_AsUnsignedLong(field); +@[ if member.type.type == 'uint32']@ + ros_message->@(member.name) = PyLong_AsUnsignedLong(field); @[ else]@ - ros_message->@(field.name) = (@(primitive_msg_type_to_c(field.type.type)))PyLong_AsUnsignedLong(field); + ros_message->@(member.name) = (@(primitive_msg_type_to_c(member.type)))PyLong_AsUnsignedLong(field); @[ end if]@ -@[ elif field.type.type == 'int64']@ +@[ elif isinstance(member.type, BasicType) and member.type.type == 'int64']@ assert(PyLong_Check(field)); - ros_message->@(field.name) = PyLong_AsLongLong(field); -@[ elif field.type.type == 'uint64']@ + ros_message->@(member.name) = PyLong_AsLongLong(field); +@[ elif isinstance(member.type, BasicType) and member.type.type == 'uint64']@ assert(PyLong_Check(field)); - ros_message->@(field.name) = PyLong_AsUnsignedLongLong(field); + ros_message->@(member.name) = PyLong_AsUnsignedLongLong(field); @[ else]@ assert(false); @[ end if]@ @@ -328,14 +383,14 @@ lowercase_field_type = convert_camel_case_to_lower_case_underscore(field.type.ty } ROSIDL_GENERATOR_C_EXPORT -PyObject * @(spec.base_type.pkg_name)__@(subfolder)__@(module_name)__convert_to_py(void * raw_ros_message) +PyObject * @('__'.join(message.structure.type.namespaces + [convert_camel_case_to_lower_case_underscore(message.structure.type.name)]))__convert_to_py(void * raw_ros_message) { - /* NOTE(esteve): Call constructor of @(spec.base_type.type) */ + /* NOTE(esteve): Call constructor of @(message.structure.type.name) */ PyObject * _pymessage = NULL; { - PyObject * pymessage_module = PyImport_ImportModule("@(spec.base_type.pkg_name).@(subfolder)._@(module_name)"); + PyObject * pymessage_module = PyImport_ImportModule("@('.'.join(message.structure.type.namespaces)).@(module_name)"); assert(pymessage_module); - PyObject * pymessage_class = PyObject_GetAttrString(pymessage_module, "@(spec.base_type.type)"); + PyObject * pymessage_class = PyObject_GetAttrString(pymessage_module, "@(message.structure.type.name)"); assert(pymessage_class); Py_DECREF(pymessage_module); _pymessage = PyObject_CallObject(pymessage_class, NULL); @@ -345,22 +400,23 @@ PyObject * @(spec.base_type.pkg_name)__@(subfolder)__@(module_name)__convert_to_ } } @(msg_typename) * ros_message = (@(msg_typename) *)raw_ros_message; -@[if not spec.fields]@ - (void)ros_message; -@[end if]@ -@[for field in spec.fields]@ - { // @(field.name) +@[for member in message.structure.members]@ +@{ +type_ = member.type +if isinstance(type_, NestedType): + type_ = type_.basetype +}@ + { // @(member.name) PyObject * field = NULL; -@[ if not field.type.is_primitive_type()]@ +@[ if isinstance(type_, NamespacedType)]@ @{ -nested_type = '%s__%s__%s' % (field.type.pkg_name, 'msg', field.type.type) -lowercase_field_type = convert_camel_case_to_lower_case_underscore(field.type.type) +nested_type = '__'.join(type_.namespaces + [type_.name]) }@ -@[ if field.type.is_array]@ -@[ if field.type.array_size is None or field.type.is_upper_bound]@ - size_t size = ros_message->@(field.name).size; +@[ if isinstance(member.type, NestedType)]@ +@[ if isinstance(member.type, Sequence)]@ + size_t size = ros_message->@(member.name).size; @[ else]@ - size_t size = @(field.type.array_size); + size_t size = @(member.type.size); @[ end if]@ field = PyList_New(size); if (!field) { @@ -368,12 +424,12 @@ lowercase_field_type = convert_camel_case_to_lower_case_underscore(field.type.ty } @(nested_type) * item; for (size_t i = 0; i < size; ++i) { -@[ if field.type.array_size is None or field.type.is_upper_bound]@ - item = &(ros_message->@(field.name).data[i]); +@[ if isinstance(member.type, Sequence)]@ + item = &(ros_message->@(member.name).data[i]); @[ else]@ - item = &(ros_message->@(field.name)[i]); + item = &(ros_message->@(member.name)[i]); @[ end if]@ - PyObject * pyitem = @(field.type.pkg_name)__msg__@(lowercase_field_type)__convert_to_py(item); + PyObject * pyitem = @('__'.join(type_.namespaces + [convert_camel_case_to_lower_case_underscore(type_.name)]))__convert_to_py(item); if (!pyitem) { Py_DECREF(field); return NULL; @@ -384,33 +440,33 @@ lowercase_field_type = convert_camel_case_to_lower_case_underscore(field.type.ty } assert(PySequence_Check(field)); @[ else]@ - field = @(field.type.pkg_name)__msg__@(lowercase_field_type)__convert_to_py(&ros_message->@(field.name)); + field = @('__'.join(type_.namespaces + [convert_camel_case_to_lower_case_underscore(type_.name)]))__convert_to_py(&ros_message->@(member.name)); if (!field) { return NULL; } @[ end if]@ -@[ elif field.type.is_array]@ -@[ if field.type.array_size is None or field.type.is_upper_bound]@ - size_t size = ros_message->@(field.name).size; - @primitive_msg_type_to_c(field.type.type) * src = ros_message->@(field.name).data; +@[ elif isinstance(member.type, NestedType)]@ +@[ if isinstance(member.type, Sequence)]@ + size_t size = ros_message->@(member.name).size; + @primitive_msg_type_to_c(member.type.basetype) * src = ros_message->@(member.name).data; @[ else]@ - size_t size = @(field.type.array_size); - @primitive_msg_type_to_c(field.type.type) * src = ros_message->@(field.name); + size_t size = @(member.type.size); + @primitive_msg_type_to_c(member.type.basetype) * src = ros_message->@(member.name); @[ end if]@ field = PyList_New(size); if (!field) { return NULL; } for (size_t i = 0; i < size; ++i) { -@[ if field.type.type == 'char']@ +@[ if isinstance(member.type.basetype, BasicType) and member.type.basetype.type == 'char']@ int rc = PyList_SetItem(field, i, Py_BuildValue("C", src[i])); (void)rc; assert(rc == 0); -@[ elif field.type.type == 'byte']@ +@[ elif isinstance(member.type.basetype, BasicType) and member.type.basetype.type == 'octet']@ int rc = PyList_SetItem(field, i, PyBytes_FromStringAndSize((const char *)&src[i], 1)); (void)rc; assert(rc == 0); -@[ elif field.type.type == 'string']@ +@[ elif isinstance(member.type.basetype, String)]@ PyObject * decoded_item = PyUnicode_DecodeASCII(src[i].data, strlen(src[i].data), "strict"); if (!decoded_item) { return NULL; @@ -418,86 +474,86 @@ lowercase_field_type = convert_camel_case_to_lower_case_underscore(field.type.ty int rc = PyList_SetItem(field, i, decoded_item); (void)rc; assert(rc == 0); -@[ elif field.type.type == 'bool']@ +@[ elif isinstance(member.type.basetype, BasicType) and member.type.basetype.type == 'boolean']@ @# using PyBool_FromLong because PyList_SetItem will steal ownership of the passed item int rc = PyList_SetItem(field, i, PyBool_FromLong(src[i] ? 1 : 0)); (void)rc; assert(rc == 0); -@[ elif field.type.type in ['float32', 'float64']]@ +@[ elif isinstance(member.type.basetype, BasicType) and member.type.basetype.type in ('float', 'double')]@ int rc = PyList_SetItem(field, i, PyFloat_FromDouble(src[i])); (void)rc; assert(rc == 0); -@[ elif field.type.type in [ +@[ elif isinstance(member.type.basetype, BasicType) and member.type.basetype.type in ( 'int8', 'int16', 'int32', - ]]@ + )]@ int rc = PyList_SetItem(field, i, PyLong_FromLong(src[i])); (void)rc; assert(rc == 0); -@[ elif field.type.type in [ +@[ elif isinstance(member.type.basetype, BasicType) and member.type.basetype.type in ( 'uint8', 'uint16', 'uint32', - ]]@ + )]@ int rc = PyList_SetItem(field, i, PyLong_FromUnsignedLong(src[i])); (void)rc; assert(rc == 0); -@[ elif field.type.type == 'int64']@ +@[ elif isinstance(member.type.basetype, BasicType) and member.type.basetype.type == 'int64']@ int rc = PyList_SetItem(field, i, PyLong_FromLongLong(src[i])); (void)rc; assert(rc == 0); -@[ elif field.type.type == 'uint64']@ +@[ elif isinstance(member.type.basetype, BasicType) and member.type.basetype.type == 'uint64']@ int rc = PyList_SetItem(field, i, PyLong_FromUnsignedLongLong(src[i])); (void)rc; assert(rc == 0); @[ end if]@ } assert(PySequence_Check(field)); -@[ elif field.type.type == 'char']@ - field = Py_BuildValue("C", ros_message->@(field.name)); +@[ elif isinstance(member.type, BasicType) and member.type.type == 'char']@ + field = Py_BuildValue("C", ros_message->@(member.name)); if (!field) { return NULL; } -@[ elif field.type.type == 'byte']@ - field = PyBytes_FromStringAndSize((const char *)&ros_message->@(field.name), 1); +@[ elif isinstance(member.type, BasicType) and member.type.type == 'octet']@ + field = PyBytes_FromStringAndSize((const char *)&ros_message->@(member.name), 1); if (!field) { return NULL; } -@[ elif field.type.type == 'string']@ +@[ elif isinstance(member.type, String)]@ field = PyUnicode_DecodeASCII( - ros_message->@(field.name).data, - strlen(ros_message->@(field.name).data), + ros_message->@(member.name).data, + strlen(ros_message->@(member.name).data), "strict"); if (!field) { return NULL; } -@[ elif field.type.type == 'bool']@ +@[ elif isinstance(member.type, BasicType) and member.type.type == 'boolean']@ @# using PyBool_FromLong allows treating the variable uniformly by calling Py_DECREF on it later - field = PyBool_FromLong(ros_message->@(field.name) ? 1 : 0); -@[ elif field.type.type in ['float32', 'float64']]@ - field = PyFloat_FromDouble(ros_message->@(field.name)); -@[ elif field.type.type in [ + field = PyBool_FromLong(ros_message->@(member.name) ? 1 : 0); +@[ elif isinstance(member.type, BasicType) and member.type.type in ('float', 'double')]@ + field = PyFloat_FromDouble(ros_message->@(member.name)); +@[ elif isinstance(member.type, BasicType) and member.type.type in ( 'int8', 'int16', 'int32', - ]]@ - field = PyLong_FromLong(ros_message->@(field.name)); -@[ elif field.type.type in [ + )]@ + field = PyLong_FromLong(ros_message->@(member.name)); +@[ elif isinstance(member.type, BasicType) and member.type.type in ( 'uint8', 'uint16', 'uint32', - ]]@ - field = PyLong_FromUnsignedLong(ros_message->@(field.name)); -@[ elif field.type.type == 'int64']@ - field = PyLong_FromLongLong(ros_message->@(field.name)); -@[ elif field.type.type == 'uint64']@ - field = PyLong_FromUnsignedLongLong(ros_message->@(field.name)); + )]@ + field = PyLong_FromUnsignedLong(ros_message->@(member.name)); +@[ elif isinstance(member.type, BasicType) and member.type.type == 'int64']@ + field = PyLong_FromLongLong(ros_message->@(member.name)); +@[ elif isinstance(member.type, BasicType) and member.type.type == 'uint64']@ + field = PyLong_FromUnsignedLongLong(ros_message->@(member.name)); @[ else]@ assert(false); @[ end if]@ { - int rc = PyObject_SetAttrString(_pymessage, "@(field.name)", field); + int rc = PyObject_SetAttrString(_pymessage, "@(member.name)", field); Py_DECREF(field); if (rc) { return NULL; diff --git a/rosidl_generator_py/resource/_srv.py.em b/rosidl_generator_py/resource/_srv.py.em index 8f188140..523c2c24 100644 --- a/rosidl_generator_py/resource/_srv.py.em +++ b/rosidl_generator_py/resource/_srv.py.em @@ -1,23 +1,23 @@ -# generated from rosidl_generator_py/resource/_srv.py.em -# generated code does not contain a copyright notice - -@####################################################################### -@# EmPy template for generating _.py files -@# -@# Context: -@# - module_name -@# - package_name -@# - spec (rosidl_parser.ServiceSpecification) -@# Parsed specification of the .srv file -@# - convert_camel_case_to_lower_case_underscore (function) -@####################################################################### -@ -import logging -import traceback - - -class Metaclass(type): - """Metaclass of message '@(spec.srv_name)'.""" +@# Included from rosidl_generator_py/resource/_idl.py.em +@{ +from rosidl_cmake import convert_camel_case_to_lower_case_underscore + +service_name = '_' + convert_camel_case_to_lower_case_underscore(service.structure_type.name) +module_name = '_' + convert_camel_case_to_lower_case_underscore(interface_path.stem) + +TEMPLATE( + '_msg.py.em', + package_name=package_name, interface_path=interface_path, + message=service.request_message) +TEMPLATE( + '_msg.py.em', + package_name=package_name, interface_path=interface_path, + message=service.response_message) +}@ + + +class Metaclass_@(service.structure_type.name)(type): + """Metaclass of service '@(service.structure_type.name)'.""" _TYPE_SUPPORT = None @@ -27,23 +27,26 @@ class Metaclass(type): from rosidl_generator_py import import_type_support module = import_type_support('@(package_name)') except ImportError: - logger = logging.getLogger('rosidl_generator_py.@(spec.srv_name)') + import logging + import traceback + logger = logging.getLogger( + '@('.'.join(service.structure_type.namespaces + [service.structure_type.name]))') logger.debug( - 'Failed to import needed modules for type support:\n' + traceback.format_exc()) + 'Failed to import needed modules for type support:\n' + + traceback.format_exc()) else: - cls._TYPE_SUPPORT = module.type_support_srv__@(subfolder)_@(module_name) -@{ -srv_name = '_' + convert_camel_case_to_lower_case_underscore(spec.srv_name) -for field_name in [srv_name + '__request', srv_name + '__response']: - print('%sfrom %s.%s import %s' % (' ' * 4 * 3, package_name, subfolder, field_name)) - print('%sif %s.Metaclass._TYPE_SUPPORT is None:' % (' ' * 4 * 3, field_name)) - print('%s%s.Metaclass.__import_type_support__()' % (' ' * 4 * 4, field_name)) -}@ + cls._TYPE_SUPPORT = module.type_support_srv__@('__'.join(service.structure_type.namespaces[1:]))_@(service_name) + + from @('.'.join(service.structure_type.namespaces)) import @(module_name) + if @(module_name).Metaclass_@(service.request_message.structure.type.name)._TYPE_SUPPORT is None: + @(module_name).Metaclass_@(service.request_message.structure.type.name).__import_type_support__() + if @(module_name).Metaclass_@(service.response_message.structure.type.name)._TYPE_SUPPORT is None: + @(module_name).Metaclass_@(service.response_message.structure.type.name).__import_type_support__() -class @(spec.srv_name)(metaclass=Metaclass): - from @(package_name).@(subfolder)._@convert_camel_case_to_lower_case_underscore(spec.srv_name)__request import @(spec.srv_name)_Request as Request - from @(package_name).@(subfolder)._@convert_camel_case_to_lower_case_underscore(spec.srv_name)__response import @(spec.srv_name)_Response as Response +class @(service.structure_type.name)(metaclass=Metaclass_@(service.structure_type.name)): + from @('.'.join(service.structure_type.namespaces)).@(module_name) import @(service.request_message.structure.type.name) as Request + from @('.'.join(service.structure_type.namespaces)).@(module_name) import @(service.response_message.structure.type.name) as Response def __init__(self): raise NotImplementedError('Service classes can not be instantiated') diff --git a/rosidl_generator_py/resource/_srv_pkg_typesupport_entry_point.c.em b/rosidl_generator_py/resource/_srv_pkg_typesupport_entry_point.c.em new file mode 100644 index 00000000..943b9bf6 --- /dev/null +++ b/rosidl_generator_py/resource/_srv_pkg_typesupport_entry_point.c.em @@ -0,0 +1,57 @@ +@# Included from rosidl_generator_py/resource/_idl_pkg_typesupport_entry_point.c.em +@{ +TEMPLATE( + '_msg_pkg_typesupport_entry_point.c.em', + package_name=package_name, idl_type=idl_type, + message=service.request_message, typesupport_impl=typesupport_impl, + include_directives=include_directives, + register_functions=register_functions) +}@ + +@{ +TEMPLATE( + '_msg_pkg_typesupport_entry_point.c.em', + package_name=package_name, idl_type=idl_type, + message=service.response_message, typesupport_impl=typesupport_impl, + include_directives=include_directives, + register_functions=register_functions) +}@ +@ +@{ +from rosidl_cmake import convert_camel_case_to_lower_case_underscore +type_name = convert_camel_case_to_lower_case_underscore(service.structure_type.name) +function_name = 'type_support' +}@ + +ROSIDL_GENERATOR_C_IMPORT +const rosidl_service_type_support_t * +ROSIDL_TYPESUPPORT_INTERFACE__SERVICE_SYMBOL_NAME(rosidl_typesupport_c, @(', '.join(service.structure_type.namespaces + [service.structure_type.name])))(); + +@{ +register_function = '_register_srv_type__' + '__'.join(service.structure_type.namespaces[1:] + [type_name]) +register_functions.append(register_function) +}@ +int8_t +@(register_function)(PyObject * pymodule) +{ + int8_t err; + PyObject * pyobject_@(function_name) = NULL; + pyobject_@(function_name) = PyCapsule_New( + (void *)ROSIDL_TYPESUPPORT_INTERFACE__SERVICE_SYMBOL_NAME(rosidl_typesupport_c, @(', '.join(service.structure_type.namespaces + [service.structure_type.name])))(), + NULL, NULL); + if (!pyobject_@(function_name)) { + // previously added objects will be removed when the module is destroyed + return -1; + } + err = PyModule_AddObject( + pymodule, + "@(function_name)_srv__@('__'.join(service.structure_type.namespaces[1:] + [type_name]))", + pyobject_@(function_name)); + if (err) { + // the created capsule needs to be decremented + Py_XDECREF(pyobject_@(function_name)); + // previously added objects will be removed when the module is destroyed + return err; + } + return 0; +} diff --git a/rosidl_generator_py/rosidl_generator_py/generate_py_impl.py b/rosidl_generator_py/rosidl_generator_py/generate_py_impl.py index e44a80a2..15130194 100644 --- a/rosidl_generator_py/rosidl_generator_py/generate_py_impl.py +++ b/rosidl_generator_py/rosidl_generator_py/generate_py_impl.py @@ -1,4 +1,4 @@ -# Copyright 2014-2016 Open Source Robotics Foundation, Inc. +# Copyright 2014-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. @@ -12,148 +12,81 @@ # See the License for the specific language governing permissions and # limitations under the License. -from collections import defaultdict +from ast import literal_eval import os -import re +import pathlib from rosidl_cmake import convert_camel_case_to_lower_case_underscore from rosidl_cmake import expand_template +from rosidl_cmake import generate_files from rosidl_cmake import get_newest_modification_time from rosidl_cmake import read_generator_arguments -from rosidl_generator_c import primitive_msg_type_to_c -from rosidl_parser import parse_action_file -from rosidl_parser import parse_message_file -from rosidl_parser import parse_service_file +from rosidl_parser.definition import BasicType +from rosidl_parser.definition import IdlContent +from rosidl_parser.definition import IdlLocator +from rosidl_parser.definition import NamespacedType +from rosidl_parser.definition import NestedType +from rosidl_parser.definition import String +from rosidl_parser.parser import parse_idl_file def generate_py(generator_arguments_file, typesupport_impls): + mapping = { + '_idl.py.em': '_%s.py', + '_idl_support.c.em': '_%s_s.c', + } + generate_files(generator_arguments_file, mapping) + args = read_generator_arguments(generator_arguments_file) + package_name = args['package_name'] + + # expand init modules for each directory + modules = {} + idl_content = IdlContent() + 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 + for subfolder in modules.keys(): + with open(os.path.join(args['output_dir'], subfolder, '__init__.py'), 'w') as f: + for idl_stem in sorted(modules[subfolder]): + module_name = '_' + \ + convert_camel_case_to_lower_case_underscore(idl_stem) + f.write( + 'from {package_name}.{subfolder}.{module_name} import ' + '{idl_stem} # noqa: F401\n'.format_map(locals())) + + # expand templates per available typesupport implementation template_dir = args['template_dir'] type_support_impl_by_filename = { '_%s_s.ep.{0}.c'.format(impl): impl for impl in typesupport_impls } - mapping_msgs = { - os.path.join(template_dir, '_msg.py.em'): ['_%s.py'], - os.path.join(template_dir, '_msg_support.c.em'): ['_%s_s.c'], - } mapping_msg_pkg_extension = { - os.path.join(template_dir, '_msg_pkg_typesupport_entry_point.c.em'): + os.path.join(template_dir, '_idl_pkg_typesupport_entry_point.c.em'): type_support_impl_by_filename.keys(), } - mapping_srvs = { - os.path.join(template_dir, '_srv.py.em'): ['_%s.py'], - } - - mapping_actions = { - os.path.join(template_dir, '_action.py.em'): ['_%s.py'], - } - - for template_file in mapping_msgs.keys(): - assert os.path.exists(template_file), 'Could not find template: ' + template_file for template_file in mapping_msg_pkg_extension.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 = { - 'constant_value_to_py': constant_value_to_py, - 'get_python_type': get_python_type, - 'primitive_msg_type_to_c': primitive_msg_type_to_c, - 'value_to_py': value_to_py, - 'convert_camel_case_to_lower_case_underscore': convert_camel_case_to_lower_case_underscore, - } latest_target_timestamp = get_newest_modification_time(args['target_dependencies']) - modules = defaultdict(list) - message_specs = [] - service_specs = [] - action_specs = [] - for ros_interface_file in args['ros_interface_files']: - extension = os.path.splitext(ros_interface_file)[1] - subfolder = os.path.basename(os.path.dirname(ros_interface_file)) - if extension == '.msg': - spec = parse_message_file(args['package_name'], ros_interface_file) - message_specs.append((spec, subfolder)) - mapping = mapping_msgs - type_name = spec.base_type.type - elif extension == '.srv': - spec = parse_service_file(args['package_name'], ros_interface_file) - service_specs.append((spec, subfolder)) - mapping = mapping_srvs - type_name = spec.srv_name - elif extension == '.action': - spec = parse_action_file(args['package_name'], ros_interface_file) - action_specs.append((spec, subfolder)) - mapping = mapping_actions - type_name = spec.action_name - else: - continue - - module_name = convert_camel_case_to_lower_case_underscore(type_name) - modules[subfolder].append((module_name, type_name)) - for template_file, generated_filenames in mapping.items(): - for generated_filename in generated_filenames: - data = { - 'module_name': module_name, - 'package_name': args['package_name'], - 'spec': spec, 'subfolder': subfolder, - } - data.update(functions) - generated_file = os.path.join( - args['output_dir'], subfolder, generated_filename % module_name) - expand_template( - template_file, data, generated_file, - minimum_timestamp=latest_target_timestamp) - - for subfolder in modules.keys(): - import_list = {} - for module_name, type_ in modules[subfolder]: - if (subfolder == 'srv' or subfolder == 'action') and \ - (type_.endswith('Request') or type_.endswith('Response')): - continue - import_list['%s # noqa\n' % type_] = 'from %s.%s._%s import %s # noqa: I100\n' % \ - (args['package_name'], subfolder, module_name, type_) - - path_to_module = os.path.join(args['output_dir'], subfolder, '__init__.py') - - content = '' - if os.path.isfile(path_to_module): - with open(path_to_module, 'r') as f: - content = f.read() - with open(path_to_module, 'w') as f: - block_name = args['package_name'] - if action_specs: - block_name += '_action' - content = re.sub( - r'# BEGIN %s$.*^# END %s' % (block_name, block_name), - '', content, 0, re.M | re.S - ) - content = re.sub(r'^\s*$', '', content, 0, re.M) - content = ''.join( - ['# BEGIN %s\n' % block_name] + - sorted(import_list.values()) + # import_line - sorted(import_list.keys()) + # noqa_line - ['# END %s\n' % block_name] - ) + content - f.write(content) - for template_file, generated_filenames in mapping_msg_pkg_extension.items(): for generated_filename in generated_filenames: package_name = args['package_name'] - if action_specs: - package_name += '_action' data = { - 'package_name': package_name, - 'action_specs': action_specs, - 'message_specs': message_specs, - 'service_specs': service_specs, + 'package_name': args['package_name'], + 'content': idl_content, 'typesupport_impl': type_support_impl_by_filename.get(generated_filename, ''), } - data.update(functions) generated_file = os.path.join( args['output_dir'], generated_filename % package_name ) @@ -165,15 +98,14 @@ def generate_py(generator_arguments_file, typesupport_impls): def value_to_py(type_, value, array_as_tuple=False): - assert type_.is_primitive_type() assert value is not None - if not type_.is_array: + if not isinstance(type_, NestedType): return primitive_value_to_py(type_, value) py_values = [] - for single_value in value: - py_value = primitive_value_to_py(type_, single_value) + for single_value in literal_eval(value): + py_value = primitive_value_to_py(type_.basetype, single_value) py_values.append(py_value) if array_as_tuple: return '(%s)' % ', '.join(py_values) @@ -182,59 +114,61 @@ def value_to_py(type_, value, array_as_tuple=False): def primitive_value_to_py(type_, value): - assert type_.is_primitive_type() assert value is not None - if type_.type == 'bool': + if isinstance(type_, String): + return "'%s'" % escape_string(value) + + assert isinstance(type_, BasicType) + + if type_.type == 'boolean': return 'True' if value else 'False' - if type_.type in [ + if type_.type in ( 'int8', 'uint8', 'int16', 'uint16', 'int32', 'uint32', 'int64', 'uint64', - ]: + ): return str(value) if type_.type == 'char': return repr('%c' % value) - if type_.type == 'byte': + if type_.type == 'octet': return repr(bytes([value])) - if type_.type in ['float32', 'float64']: + if type_.type in ('float', 'double'): return '%s' % value - if type_.type == 'string': - return "'%s'" % escape_string(value) - assert False, "unknown primitive type '%s'" % type_.type def constant_value_to_py(type_, value): assert value is not None - if type_ == 'bool': - return 'True' if value else 'False' + if isinstance(type_, BasicType): + if type_.type == 'bool': + return 'True' if value else 'False' - if type_ in [ - 'int8', 'uint8', - 'int16', 'uint16', - 'int32', 'uint32', - 'int64', 'uint64', - ]: - return str(value) + if type_.type in ( + 'int8', 'uint8', + 'int16', 'uint16', + 'int32', 'uint32', + 'int64', 'uint64', + ): + return str(value) - if type_ == 'char': - return repr('%c' % value) + if type_.type == 'char': + return repr('%c' % value) - if type_ == 'byte': - return repr(bytes([value])) + if type_.type == 'octet': + return repr(bytes([value])) - if type_ in ['float32', 'float64']: - return '%s' % value + if type_.type in ('float', 'double'): + return '%s' % value - if type_ == 'string': + if isinstance(type_, String): return "'%s'" % escape_string(value) assert False, "unknown constant type '%s'" % type_ @@ -247,37 +181,41 @@ def escape_string(s): def get_python_type(type_): - if not type_.is_primitive_type(): - return type_.type + if isinstance(type_, NamespacedType): + return type_.name - if type_.string_upper_bound: + if isinstance(type_, String): return 'str' - if type_.is_array: - if type_.type == 'byte': + if isinstance(type_, NestedType): + if isinstance(type_.basetype, BasicType) and type_.basetype.type == 'octet': return 'bytes' - if type_.type == 'char': + if isinstance(type_.basetype, BasicType) and type_.basetype.type in ('char', 'wchar'): return 'str' - if type_.type == 'bool': + if isinstance(type_, BasicType) and type_.type == 'boolean': return 'bool' - if type_.type == 'byte': + if isinstance(type_, BasicType) and type_.type == 'octet': return 'bytes' - if type_.type in [ + if isinstance(type_, BasicType) and type_.type in ( 'int8', 'uint8', 'int16', 'uint16', 'int32', 'uint32', 'int64', 'uint64', - ]: + ): return 'int' - if type_.type in ['float32', 'float64']: + if isinstance(type_, BasicType) and type_.type in ( + 'float', 'double', + ): return 'float' - if type_.type in ['char', 'string']: + if isinstance(type_, BasicType) and type_.type in ( + 'char', 'wchar', + ): return 'str' assert False, "unknown type '%s'" % type_ diff --git a/rosidl_generator_py/test/test_interfaces.py b/rosidl_generator_py/test/test_interfaces.py index 23e961e5..16b8403e 100644 --- a/rosidl_generator_py/test/test_interfaces.py +++ b/rosidl_generator_py/test/test_interfaces.py @@ -95,7 +95,7 @@ def test_constants(): assert 123 == Constants.X assert -123 == Constants.Y assert 'foo' == Constants.FOO - assert '\x7F' == Constants.TOTO + assert 127 == Constants.TOTO assert b'0' == Constants.TATA with pytest.raises(AttributeError): @@ -132,7 +132,7 @@ def test_default_values(): assert [5, 23] == c.UP_TO_THREE_INT32_VALUES_WITH_DEFAULT_VALUES__DEFAULT - assert '\x01' == c.CHAR_VALUE__DEFAULT + assert 1 == c.CHAR_VALUE__DEFAULT assert '1' != c.CHAR_VALUE__DEFAULT assert b'\x01' == c.BYTE_VALUE__DEFAULT assert b'1' != c.BYTE_VALUE__DEFAULT @@ -198,8 +198,8 @@ def test_check_constraints(): with pytest.raises(AssertionError): setattr(c, 'byte_value', 'abc') - c.char_value = 'a' - assert 'a' == c.char_value + c.char_value = ord('a') + assert ord('a') == c.char_value assert b'a' != c.char_value with pytest.raises(AssertionError): setattr(c, 'char_value', b'a') @@ -269,8 +269,8 @@ def test_slot_attributes(): expected_nested_slot_types_dict = { 'primitives': 'rosidl_generator_py/Primitives', 'two_primitives': 'rosidl_generator_py/Primitives[2]', - 'up_to_three_primitives': 'rosidl_generator_py/Primitives[<=3]', - 'unbounded_primitives': 'rosidl_generator_py/Primitives[]', + 'up_to_three_primitives': 'sequence', + 'unbounded_primitives': 'sequence', } assert len(nested_slot_types_dict) == len(expected_nested_slot_types_dict) @@ -287,17 +287,17 @@ def test_primative_slot_attributes(): string_slots = getattr(b, '__slots__') assert len(string_slot_types_dict) == len(string_slots) expected_string_slot_types_dict = { - 'ub_string_static_array_value': 'string<=5[3]', - 'ub_string_ub_array_value': 'string<=5[<=10]', - 'ub_string_dynamic_array_value': 'string<=5[]', - 'string_dynamic_array_value': 'string[]', + 'ub_string_static_array_value': 'string<5>[3]', + 'ub_string_ub_array_value': 'sequence, 10>', + 'ub_string_dynamic_array_value': 'sequence>', + 'string_dynamic_array_value': 'sequence', 'string_static_array_value': 'string[3]', - 'string_bounded_array_value': 'string[<=10]', - 'def_string_dynamic_array_value': 'string[]', + 'string_bounded_array_value': 'sequence', + 'def_string_dynamic_array_value': 'sequence', 'def_string_static_array_value': 'string[3]', - 'def_string_bounded_array_value': 'string[<=10]', - 'def_various_quotes': 'string[]', - 'def_various_commas': 'string[]', + 'def_string_bounded_array_value': 'sequence', + 'def_various_quotes': 'sequence', + 'def_various_commas': 'sequence', } assert len(string_slot_types_dict) == len(expected_string_slot_types_dict)