diff --git a/rosidl_adapter_proto/bin/rosidl_adapter_proto b/rosidl_adapter_proto/bin/rosidl_adapter_proto index bf49b23..f99c314 100644 --- a/rosidl_adapter_proto/bin/rosidl_adapter_proto +++ b/rosidl_adapter_proto/bin/rosidl_adapter_proto @@ -23,9 +23,8 @@ import sys import pathlib import os -from rosidl_cmake import read_generator_arguments -from rosidl_adapter_proto import generate_proto -from rosidl_adapter_proto import compile_proto +from rosidl_adapter_proto.cli import convert_to_proto + def main(argv=sys.argv[1:]): parser = argparse.ArgumentParser( @@ -41,47 +40,12 @@ def main(argv=sys.argv[1:]): help='Path to the protoc executable') args = parser.parse_args(argv) - - generator_args = read_generator_arguments(args.generator_arguments_file) - - # Generate .proto files - rc = generate_proto(args.generator_arguments_file) - if rc: - return rc - - # Compile .proto files using protoc - cpp_out_dir = str(pathlib.Path(generator_args["output_dir"] + "/..").resolve()) - proto_path_list = [str(pathlib.Path(generator_args["output_dir"] + "/..").resolve())] - proto_files = [] - package_name = generator_args["package_name"] - - if "additional_files" in generator_args: - proto_path_list += generator_args["additional_files"] - - pathlib.Path(cpp_out_dir).mkdir(parents=True, exist_ok=True) - - for idl_tuple in generator_args.get('idl_tuples', []): - idl_parts = idl_tuple.rsplit(':', 1) - assert len(idl_parts) == 2 - idl_rel_path = pathlib.Path(idl_parts[1]) - idl_stem = idl_rel_path.stem - generated_file = os.path.join( - generator_args['output_dir'], - str(idl_rel_path.parent), - idl_stem + ".proto" - ) - proto_files.append(str(pathlib.Path(generated_file).resolve())) - - # compile proto files with protoc - rc = compile_proto(protoc_path = args.protoc_path, - proto_path_list = proto_path_list, - cpp_out_dir = cpp_out_dir, - proto_files = proto_files, - package_name = package_name - ) + try: + convert_to_proto(args.generator_arguments_file, args.protoc_path) + except RuntimeError: + return 1 - return rc - + return 0 if __name__ == '__main__': sys.exit(main()) diff --git a/rosidl_adapter_proto/rosidl_adapter_proto/cli.py b/rosidl_adapter_proto/rosidl_adapter_proto/cli.py new file mode 100644 index 0000000..7fd452d --- /dev/null +++ b/rosidl_adapter_proto/rosidl_adapter_proto/cli.py @@ -0,0 +1,157 @@ +import argparse +import os +import pathlib +import sys + +from ament_index_python import get_package_share_directory + +from catkin_pkg.package import package_exists_at +from catkin_pkg.package import parse_package + +from rosidl_adapter.action import convert_action_to_idl +from rosidl_adapter.msg import convert_msg_to_idl +from rosidl_adapter.srv import convert_srv_to_idl + +from rosidl_cmake import read_generator_arguments + +from rosidl_cli.command.helpers import interface_path_as_tuple +from rosidl_cli.command.helpers import legacy_generator_arguments_file +from rosidl_cli.command.translate.extensions import TranslateCommandExtension +from rosidl_cli.command.translate.api import translate + +from rosidl_adapter_proto import generate_proto +from rosidl_adapter_proto import compile_proto + +def convert_to_proto(generator_arguments_file, protoc_path): + generator_args = read_generator_arguments(generator_arguments_file) + + # Generate .proto files + rc = generate_proto(generator_arguments_file) + if rc : + raise RuntimeError + + # Compile .proto files using protoc + cpp_out_dir = str(pathlib.Path(generator_args["output_dir"] + "/..").resolve()) + proto_path_list = [str(pathlib.Path(generator_args["output_dir"] + "/..").resolve())] + proto_files = [] + package_name = generator_args["package_name"] + + if "additional_files" in generator_args: + proto_path_list += generator_args["additional_files"] + + pathlib.Path(cpp_out_dir).mkdir(parents=True, exist_ok=True) + + for idl_tuple in generator_args.get('idl_tuples', []): + idl_parts = idl_tuple.rsplit(':', 1) + assert len(idl_parts) == 2 + idl_rel_path = pathlib.Path(idl_parts[1]) + idl_stem = idl_rel_path.stem + generated_file = os.path.join( + generator_args['output_dir'], + str(idl_rel_path.parent), + idl_stem + ".proto" + ) + proto_files.append(str(pathlib.Path(generated_file).resolve())) + + # compile proto files with protoc + rc = compile_proto(protoc_path = protoc_path, + proto_path_list = proto_path_list, + cpp_out_dir = cpp_out_dir, + proto_files = proto_files, + package_name = package_name + ) + if rc : + raise RuntimeError + + return proto_files + + +def convert_files_to_idl(extension, conversion_function, argv=sys.argv[1:]): + parser = argparse.ArgumentParser( + description=f'Convert {extension} files to .idl') + parser.add_argument( + 'interface_files', nargs='+', + help='The interface files to convert') + args = parser.parse_args(argv) + + for interface_file in args.interface_files: + interface_file = pathlib.Path(interface_file) + package_dir = interface_file.parent.absolute() + while ( + len(package_dir.parents) and + not package_exists_at(str(package_dir)) + ): + package_dir = package_dir.parent + if not package_dir.parents: + print( + f"Could not find package for '{interface_file}'", + file=sys.stderr) + continue + warnings = [] + pkg = parse_package(package_dir, warnings=warnings) + + conversion_function( + package_dir, pkg.name, + interface_file.absolute().relative_to(package_dir), + interface_file.parent) + + +class TranslateToProto(TranslateCommandExtension): + + output_format = 'proto' + + def translate( + self, + package_name, + interface_files, + include_paths, + output_path + ): + + generated_files = [] + + package_share_path = pathlib.Path( + get_package_share_directory('rosidl_adapter_proto')) + templates_path = package_share_path / 'resource' + + + idl_interface_files = [] + non_idl_interface_files = [] + for path in interface_files: + if not path.endswith('.idl'): + non_idl_interface_files.append(path) + else: + idl_interface_files.append(path) + if non_idl_interface_files: + idl_interface_files.extend(translate( + package_name=package_name, + interface_files=non_idl_interface_files, + include_paths=include_paths, + output_format='idl', + output_path=output_path / 'tmp', + )) + + + # Generate code + with legacy_generator_arguments_file( + package_name=package_name, + interface_files=idl_interface_files, + include_paths=include_paths, + templates_path=templates_path, + output_path=output_path + ) as path_to_arguments_file: + generated_files.extend(convert_to_proto(path_to_arguments_file, '/usr/bin/protoc')) + return generated_files + + + +class TranslateMsgToProto(TranslateToProto): + input_format = 'msg' + + +class TranslateSrvToProto(TranslateToProto): + input_format = 'srv' + + +class TranslateActionToProto(TranslateToProto): + input_format = 'action' diff --git a/rosidl_adapter_proto/setup.cfg b/rosidl_adapter_proto/setup.cfg new file mode 100644 index 0000000..b0a21ca --- /dev/null +++ b/rosidl_adapter_proto/setup.cfg @@ -0,0 +1,5 @@ +[options.entry_points] +rosidl_cli.command.translate.extensions = + msg2proto = rosidl_adapter_proto.cli:TranslateMsgToProto + srv2proto = rosidl_adapter_proto.cli:TranslateSrvToProto + action2proto = rosidl_adapter_proto.cli:TranslateActionToProto diff --git a/rosidl_adapter_proto/test/test_rosidl_cli_translate.py b/rosidl_adapter_proto/test/test_rosidl_cli_translate.py new file mode 100644 index 0000000..a996569 --- /dev/null +++ b/rosidl_adapter_proto/test/test_rosidl_cli_translate.py @@ -0,0 +1,30 @@ +import pytest +import os +import pathlib +import shutil +import subprocess + +from ament_index_python import get_package_share_directory + +def test_ts_files_generation(): + build_then_install('/tmp/tr') + files_exists('/tmp/tr') + +def build_then_install(output_path): + if os.path.exists(output_path): + shutil.rmtree(output_path) + os.makedirs(output_path) + + package_share_path = pathlib.Path( + get_package_share_directory('std_msgs')) + + subprocess.run([ + 'rosidl', 'translate', '-o', output_path, '--from', 'msg', '--to', 'proto', 'std_msgs', './msg/String.msg' + ], cwd=package_share_path, check=True) + +def files_exists(output_path): + assert pathlib.Path('/tmp/tr/msg/String.pb.cc').exists() + assert pathlib.Path('/tmp/tr/msg/String.pb.h').exists() + assert pathlib.Path('/tmp/tr/msg/String.proto').exists() + assert pathlib.Path('/tmp/tr/tmp/msg/String.idl').exists() + \ No newline at end of file diff --git a/rosidl_typesupport_protobuf_c/CMakeLists.txt b/rosidl_typesupport_protobuf_c/CMakeLists.txt index dd473d2..97047bf 100644 --- a/rosidl_typesupport_protobuf_c/CMakeLists.txt +++ b/rosidl_typesupport_protobuf_c/CMakeLists.txt @@ -115,6 +115,8 @@ if(BUILD_TESTING) target_link_libraries(test_wstring_conversion ${PROJECT_NAME}) endif() + find_package(ament_cmake_pytest REQUIRED) + ament_add_pytest_test(test_rosidl_cli_ts_pb_c test/test_rosidl_cli_ts_pb_c.py) endif() ament_package( diff --git a/rosidl_typesupport_protobuf_c/package.xml b/rosidl_typesupport_protobuf_c/package.xml index f61936a..a98fa3a 100644 --- a/rosidl_typesupport_protobuf_c/package.xml +++ b/rosidl_typesupport_protobuf_c/package.xml @@ -30,6 +30,7 @@ rosidl_typesupport_interface ament_cmake_gtest + ament_cmake_pytest rosidl_typesupport_c_packages diff --git a/rosidl_typesupport_protobuf_c/rosidl_typesupport_protobuf_c/cli.py b/rosidl_typesupport_protobuf_c/rosidl_typesupport_protobuf_c/cli.py new file mode 100644 index 0000000..877cfd6 --- /dev/null +++ b/rosidl_typesupport_protobuf_c/rosidl_typesupport_protobuf_c/cli.py @@ -0,0 +1,72 @@ +import pathlib + +from ament_index_python import get_package_share_directory + +from rosidl_cli.command.generate.extensions import GenerateCommandExtension +from rosidl_cli.command.helpers import generate_visibility_control_file +from rosidl_cli.command.helpers import legacy_generator_arguments_file +from rosidl_cli.command.translate.api import translate + +from rosidl_typesupport_protobuf_c import generate_typesupport_protobuf_c + + +class GenerateProtobufCTypesupport(GenerateCommandExtension): + + def generate( + self, + package_name, + interface_files, + include_paths, + output_path + ): + generated_files = [] + + package_share_path = pathlib.Path( + get_package_share_directory('rosidl_typesupport_protobuf_c')) + templates_path = package_share_path / 'resource' + + # Normalize interface definition format to .idl + idl_interface_files = [] + non_idl_interface_files = [] + for path in interface_files: + if not path.endswith('.idl'): + non_idl_interface_files.append(path) + else: + idl_interface_files.append(path) + if non_idl_interface_files: + idl_interface_files.extend(translate( + package_name=package_name, + interface_files=non_idl_interface_files, + include_paths=include_paths, + output_format='idl', + output_path=output_path / 'tmp', + )) + + # Generate visibility control file + visibility_control_file_template_path = \ + 'rosidl_typesupport_protobuf_c__visibility_control.h.in' + visibility_control_file_template_path = \ + templates_path / visibility_control_file_template_path + visibility_control_file_path = \ + 'rosidl_typesupport_protobuf_c__visibility_control.h' + visibility_control_file_path = \ + output_path / 'msg' / visibility_control_file_path + + generate_visibility_control_file( + package_name=package_name, + template_path=visibility_control_file_template_path, + output_path=visibility_control_file_path + ) + generated_files.append(visibility_control_file_path) + + # Generate code + with legacy_generator_arguments_file( + package_name=package_name, + interface_files=idl_interface_files, + include_paths=include_paths, + templates_path=templates_path, + output_path=output_path + ) as path_to_arguments_file: + generated_files.extend(generate_typesupport_protobuf_c(path_to_arguments_file)) + + return generated_files diff --git a/rosidl_typesupport_protobuf_c/setup.cfg b/rosidl_typesupport_protobuf_c/setup.cfg new file mode 100644 index 0000000..80b42be --- /dev/null +++ b/rosidl_typesupport_protobuf_c/setup.cfg @@ -0,0 +1,3 @@ +[options.entry_points] +rosidl_cli.command.generate.typesupport_extensions = + protobuf_c = rosidl_typesupport_protobuf_c.cli:GenerateProtobufCTypesupport diff --git a/rosidl_typesupport_protobuf_c/test/test_rosidl_cli_ts_pb_c.py b/rosidl_typesupport_protobuf_c/test/test_rosidl_cli_ts_pb_c.py new file mode 100644 index 0000000..ca69223 --- /dev/null +++ b/rosidl_typesupport_protobuf_c/test/test_rosidl_cli_ts_pb_c.py @@ -0,0 +1,30 @@ +import pytest +import os +import pathlib +import shutil +import subprocess + +from ament_index_python import get_package_share_directory + +def test_ts_files_generation(): + build_then_install('/tmp/pb') + files_exists('/tmp/pb') + +def build_then_install(output_path): + if os.path.exists(output_path): + shutil.rmtree(output_path) + os.makedirs(output_path) + + package_share_path = pathlib.Path( + get_package_share_directory('std_msgs')) + + subprocess.run([ + 'rosidl', 'generate', '-ts', 'protobuf_c', '-o', output_path, 'std_msgs', './msg/String.msg' + ], cwd=package_share_path, check=True) + +def files_exists(output_path): + assert pathlib.Path('/tmp/pb/tmp/msg/String.idl').exists() + assert pathlib.Path('/tmp/pb/msg/rosidl_typesupport_protobuf_c__visibility_control.h').exists() + assert pathlib.Path('/tmp/pb/msg/string__rosidl_typesupport_protobuf_c.hpp').exists() + assert pathlib.Path('/tmp/pb/msg/detail/string__type_support.cpp').exists() + \ No newline at end of file diff --git a/rosidl_typesupport_protobuf_cpp/CMakeLists.txt b/rosidl_typesupport_protobuf_cpp/CMakeLists.txt index ccac6d0..85072b9 100644 --- a/rosidl_typesupport_protobuf_cpp/CMakeLists.txt +++ b/rosidl_typesupport_protobuf_cpp/CMakeLists.txt @@ -96,6 +96,8 @@ if(BUILD_TESTING) target_link_libraries(test_wstring_conversion ${PROJECT_NAME}) endif() + find_package(ament_cmake_pytest REQUIRED) + ament_add_pytest_test(test_rosidl_cli_ts_pb_cpp test/test_rosidl_cli_ts_pb_cpp.py) endif() ament_package(CONFIG_EXTRAS "cmake/rosidl_typesupport_protobuf_cpp-extras.cmake.in") diff --git a/rosidl_typesupport_protobuf_cpp/package.xml b/rosidl_typesupport_protobuf_cpp/package.xml index 485900b..b2f1943 100644 --- a/rosidl_typesupport_protobuf_cpp/package.xml +++ b/rosidl_typesupport_protobuf_cpp/package.xml @@ -29,6 +29,7 @@ rosidl_typesupport_protobuf ament_cmake_gtest + ament_cmake_pytest rosidl_typesupport_cpp_packages diff --git a/rosidl_typesupport_protobuf_cpp/rosidl_typesupport_protobuf_cpp/__init__.py b/rosidl_typesupport_protobuf_cpp/rosidl_typesupport_protobuf_cpp/__init__.py index 806324c..4252ef8 100644 --- a/rosidl_typesupport_protobuf_cpp/rosidl_typesupport_protobuf_cpp/__init__.py +++ b/rosidl_typesupport_protobuf_cpp/rosidl_typesupport_protobuf_cpp/__init__.py @@ -15,9 +15,6 @@ # limitations under the License. # # ================================= Apache 2.0 ================================= - -from rosidl_cmake import generate_files - from rosidl_pycommon import generate_files def get_template_mapping(): diff --git a/rosidl_typesupport_protobuf_cpp/rosidl_typesupport_protobuf_cpp/cli.py b/rosidl_typesupport_protobuf_cpp/rosidl_typesupport_protobuf_cpp/cli.py new file mode 100644 index 0000000..51239f3 --- /dev/null +++ b/rosidl_typesupport_protobuf_cpp/rosidl_typesupport_protobuf_cpp/cli.py @@ -0,0 +1,72 @@ +import pathlib + +from ament_index_python import get_package_share_directory + +from rosidl_cli.command.generate.extensions import GenerateCommandExtension +from rosidl_cli.command.helpers import generate_visibility_control_file +from rosidl_cli.command.helpers import legacy_generator_arguments_file +from rosidl_cli.command.translate.api import translate + +from rosidl_typesupport_protobuf_cpp import generate_cpp + + +class GenerateProtobufCppTypesupport(GenerateCommandExtension): + + def generate( + self, + package_name, + interface_files, + include_paths, + output_path + ): + generated_files = [] + + package_share_path = pathlib.Path( + get_package_share_directory('rosidl_typesupport_protobuf_cpp')) + templates_path = package_share_path / 'resource' + + # Normalize interface definition format to .idl + idl_interface_files = [] + non_idl_interface_files = [] + for path in interface_files: + if not path.endswith('.idl'): + non_idl_interface_files.append(path) + else: + idl_interface_files.append(path) + if non_idl_interface_files: + idl_interface_files.extend(translate( + package_name=package_name, + interface_files=non_idl_interface_files, + include_paths=include_paths, + output_format='idl', + output_path=output_path / 'tmp', + )) + + # Generate visibility control file + visibility_control_file_template_path = \ + 'rosidl_typesupport_protobuf_cpp__visibility_control.h.in' + visibility_control_file_template_path = \ + templates_path / visibility_control_file_template_path + visibility_control_file_path = \ + 'rosidl_typesupport_protobuf_cpp__visibility_control.h' + visibility_control_file_path = \ + output_path / 'msg' / visibility_control_file_path + + generate_visibility_control_file( + package_name=package_name, + template_path=visibility_control_file_template_path, + output_path=visibility_control_file_path + ) + generated_files.append(visibility_control_file_path) + + # Generate code + with legacy_generator_arguments_file( + package_name=package_name, + interface_files=idl_interface_files, + include_paths=include_paths, + templates_path=templates_path, + output_path=output_path + ) as path_to_arguments_file: + generated_files.extend(generate_cpp(path_to_arguments_file)) + + return generated_files diff --git a/rosidl_typesupport_protobuf_cpp/setup.cfg b/rosidl_typesupport_protobuf_cpp/setup.cfg new file mode 100644 index 0000000..b272e07 --- /dev/null +++ b/rosidl_typesupport_protobuf_cpp/setup.cfg @@ -0,0 +1,3 @@ +[options.entry_points] +rosidl_cli.command.generate.typesupport_extensions = + protobuf_cpp = rosidl_typesupport_protobuf_cpp.cli:GenerateProtobufCppTypesupport diff --git a/rosidl_typesupport_protobuf_cpp/test/test_rosidl_cli_ts_pb_cpp.py b/rosidl_typesupport_protobuf_cpp/test/test_rosidl_cli_ts_pb_cpp.py new file mode 100644 index 0000000..d4bc740 --- /dev/null +++ b/rosidl_typesupport_protobuf_cpp/test/test_rosidl_cli_ts_pb_cpp.py @@ -0,0 +1,31 @@ +import pytest +import os +import pathlib +import shutil +import subprocess + +from ament_index_python import get_package_share_directory + +def test_ts_files_generation(): + build_then_install('/tmp/pb') + files_exists('/tmp/pb') + +def build_then_install(output_path): + if os.path.exists(output_path): + shutil.rmtree(output_path) + os.makedirs(output_path) + + package_share_path = pathlib.Path( + get_package_share_directory('std_msgs')) + + subprocess.run([ + 'rosidl', 'generate', '-ts', 'protobuf_cpp', '-o', output_path, 'std_msgs', './msg/String.msg' + ], cwd=package_share_path, check=True) + +def files_exists(output_path): + assert pathlib.Path('/tmp/pb/tmp/msg/String.idl').exists() + assert pathlib.Path('/tmp/pb/msg/rosidl_typesupport_protobuf_cpp__visibility_control.h').exists() + assert pathlib.Path('/tmp/pb/msg/string__rosidl_typesupport_protobuf_cpp.hpp').exists() + assert pathlib.Path('/tmp/pb/msg/string__typeadapter_protobuf_cpp.hpp').exists() + assert pathlib.Path('/tmp/pb/msg/detail/string__type_support.cpp').exists() + \ No newline at end of file