diff --git a/rosidl_generator_c/resource/msg__functions.c.em b/rosidl_generator_c/resource/msg__functions.c.em index f7fe8d7f1..096fbf2c4 100644 --- a/rosidl_generator_c/resource/msg__functions.c.em +++ b/rosidl_generator_c/resource/msg__functions.c.em @@ -240,6 +240,9 @@ bool } @[for member in message.structure.members]@ // @(member.name) +@[ if member.has_annotation('deprecated')]@ + DISABLE_DEPRECATED_PUSH +@[end if]@ @[ if isinstance(member.type, Array)]@ for (size_t i = 0; i < @(member.type.size); ++i) { @[ if isinstance(member.type.value_type, (AbstractGenericString, NamespacedType))]@ @@ -271,6 +274,9 @@ bool return false; } @[ end if]@ +@[ if member.has_annotation('deprecated')]@ + DISABLE_DEPRECATED_POP +@[end if]@ @[end for]@ return true; } @@ -285,6 +291,9 @@ bool } @[for member in message.structure.members]@ // @(member.name) +@[ if member.has_annotation('deprecated')]@ + DISABLE_DEPRECATED_PUSH +@[end if]@ @[ if isinstance(member.type, Array)]@ for (size_t i = 0; i < @(member.type.size); ++i) { @[ if isinstance(member.type.value_type, (AbstractGenericString, NamespacedType))]@ @@ -312,6 +321,9 @@ bool @[ else]@ output->@(member.name) = input->@(member.name); @[ end if]@ +@[ if member.has_annotation('deprecated')]@ + DISABLE_DEPRECATED_POP +@[end if]@ @[end for]@ return true; } diff --git a/rosidl_generator_c/resource/msg__struct.h.em b/rosidl_generator_c/resource/msg__struct.h.em index 23414ed45..436245cc2 100644 --- a/rosidl_generator_c/resource/msg__struct.h.em +++ b/rosidl_generator_c/resource/msg__struct.h.em @@ -22,6 +22,7 @@ from rosidl_generator_c import idl_structure_type_to_c_include_prefix from rosidl_generator_c import idl_structure_type_to_c_typename from rosidl_generator_c import interface_path_to_string from rosidl_generator_c import value_to_c +from rosidl_generator_c import get_deprecation_from_member }@ @#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @# Collect necessary include directives for all members @@ -29,6 +30,11 @@ from rosidl_generator_c import value_to_c from collections import OrderedDict includes = OrderedDict() for member in message.structure.members: + if (member.has_annotation('deprecated')): + includes.setdefault( + 'rosidl_runtime_c/deprecation.h', [] + ) + if isinstance(member.type, AbstractSequence) and isinstance(member.type.value_type, BasicType): member_names = includes.setdefault( 'rosidl_runtime_c/primitives_sequence.h', []) @@ -174,6 +180,9 @@ typedef struct @(idl_structure_type_to_c_typename(message.structure.namespaced_t /// @[ end if]@ @[ end for]@ +@[ if member.has_annotation('deprecated')]@ + @(get_deprecation_from_member(member)) +@[ end if]@ @(idl_declaration_to_c(member.type, member.name)); @[end for]@ } @(idl_structure_type_to_c_typename(message.structure.namespaced_type)); diff --git a/rosidl_generator_c/rosidl_generator_c/__init__.py b/rosidl_generator_c/rosidl_generator_c/__init__.py index 4e4bf76d9..e4f2d88b1 100644 --- a/rosidl_generator_c/rosidl_generator_c/__init__.py +++ b/rosidl_generator_c/rosidl_generator_c/__init__.py @@ -24,6 +24,7 @@ from rosidl_parser.definition import Array from rosidl_parser.definition import BasicType from rosidl_parser.definition import CHARACTER_TYPES +from rosidl_parser.definition import Member from rosidl_parser.definition import NamespacedType from rosidl_parser.definition import OCTET_TYPE from rosidl_pycommon import convert_camel_case_to_lower_case_underscore @@ -247,3 +248,14 @@ def type_hash_to_c_definition(hash_string, *, indent=2): result += ' ' * indent result += '}}' return result + + +def get_deprecation_from_member(member: Member) -> str: + if member.has_annotation('deprecated'): + deprecation_annotation = member.get_annotation_value('deprecated') + if not isinstance(deprecation_annotation, dict): + assert False, f'deprecation_annotation is not proper type: {deprecation_annotation}' + + text = deprecation_annotation['text'] + return f'ROSIDL_DEPRECATED("{text}")' + return '' diff --git a/rosidl_generator_cpp/resource/idl__struct.hpp.em b/rosidl_generator_cpp/resource/idl__struct.hpp.em index b8f985617..9bdd4662f 100644 --- a/rosidl_generator_cpp/resource/idl__struct.hpp.em +++ b/rosidl_generator_cpp/resource/idl__struct.hpp.em @@ -33,6 +33,7 @@ include_directives = set() #include #include +#include "rosidl_runtime_c/deprecation.h" #include "rosidl_runtime_cpp/bounded_vector.hpp" #include "rosidl_runtime_cpp/message_initialization.hpp" diff --git a/rosidl_generator_cpp/resource/msg__struct.hpp.em b/rosidl_generator_cpp/resource/msg__struct.hpp.em index 867b8c624..c4d646242 100644 --- a/rosidl_generator_cpp/resource/msg__struct.hpp.em +++ b/rosidl_generator_cpp/resource/msg__struct.hpp.em @@ -7,6 +7,7 @@ from rosidl_generator_cpp import msg_type_to_cpp from rosidl_generator_cpp import MSG_TYPE_TO_CPP from rosidl_generator_cpp import generate_zero_string from rosidl_generator_cpp import generate_default_string +from rosidl_generator_cpp import get_deprecation_from_member from rosidl_parser.definition import AbstractNestedType from rosidl_parser.definition import AbstractString from rosidl_parser.definition import AbstractWString @@ -111,6 +112,9 @@ struct @(message.structure.namespaced_type.name)_ # for a detailed explanation of the different _init parameters. init_list, alloc_list, member_list = create_init_alloc_and_member_lists(message) }@ +@[if any(mem.has_annotation('deprecated') for mem in message.structure.members)]@ + DISABLE_DEPRECATED_PUSH +@[end if] explicit @(message.structure.namespaced_type.name)_(rosidl_runtime_cpp::MessageInitialization _init = rosidl_runtime_cpp::MessageInitialization::ALL) @[if init_list]@ : @(',\n '.join(init_list)) @@ -203,21 +207,36 @@ non_defaulted_zero_initialized_members = [ } @[end if]@ } +@[if any(mem.has_annotation('deprecated') for mem in message.structure.members)]@ + DISABLE_DEPRECATED_POP +@[end if] // field types and members @[for member in message.structure.members]@ using _@(member.name)_type = @(msg_type_to_cpp(member.type)); +@[ if member.has_annotation('deprecated')]@ + @(get_deprecation_from_member(member)) +@[ end if]@ _@(member.name)_type @(member.name); @[end for]@ @[if len(message.structure.members) != 1 or message.structure.members[0].name != EMPTY_STRUCTURE_REQUIRED_MEMBER_NAME]@ // setters for named parameter idiom @[ for member in message.structure.members]@ +@[ if member.has_annotation('deprecated')]@ + @(get_deprecation_from_member(member)) +@[ end if]@ Type & set__@(member.name)( const @(msg_type_to_cpp(member.type)) & _arg) { +@[ if member.has_annotation('deprecated')]@ + DISABLE_DEPRECATED_PUSH +@[ end if]@ this->@(member.name) = _arg; +@[ if member.has_annotation('deprecated')]@ + DISABLE_DEPRECATED_POP +@[ end if]@ return *this; } @[ end for]@ @@ -307,9 +326,15 @@ u@ (void)other; @[end if]@ @[for member in message.structure.members]@ +@[ if member.has_annotation('deprecated')]@ + DISABLE_DEPRECATED_PUSH +@[ end if]@ if (this->@(member.name) != other.@(member.name)) { return false; } +@[ if member.has_annotation('deprecated')]@ + DISABLE_DEPRECATED_POP +@[ end if]@ @[end for]@ return true; } diff --git a/rosidl_generator_cpp/rosidl_generator_cpp/__init__.py b/rosidl_generator_cpp/rosidl_generator_cpp/__init__.py index a87092274..fd2798f14 100644 --- a/rosidl_generator_cpp/rosidl_generator_cpp/__init__.py +++ b/rosidl_generator_cpp/rosidl_generator_cpp/__init__.py @@ -24,6 +24,7 @@ from rosidl_parser.definition import BasicType from rosidl_parser.definition import BoundedSequence from rosidl_parser.definition import FLOATING_POINT_TYPES +from rosidl_parser.definition import Member from rosidl_parser.definition import NamespacedType from rosidl_parser.definition import UnboundedSequence from rosidl_pycommon import generate_files @@ -388,3 +389,14 @@ def generate_zero_string(membset: list, fill_args: str) -> list[str]: else: strlist.append('this->%s = %s;' % (member.name, member.zero_value)) return strlist + + +def get_deprecation_from_member(member: Member) -> str: + if (member.has_annotation('deprecated')): + deprecation_annotation = member.get_annotation_value('deprecated') + if not isinstance(deprecation_annotation, dict): + assert False, f'deprecation_annotation is not proper type: {deprecation_annotation}' + + text = deprecation_annotation['text'] + return f'[[deprecated("{text}")]]' + return '' diff --git a/rosidl_generator_tests/CMakeLists.txt b/rosidl_generator_tests/CMakeLists.txt index bd858f870..d0b88944d 100644 --- a/rosidl_generator_tests/CMakeLists.txt +++ b/rosidl_generator_tests/CMakeLists.txt @@ -35,6 +35,7 @@ if(BUILD_TESTING) ${test_interface_files_MSG_FILES} ${test_interface_files_SRV_FILES} msg/BasicIdl.idl + msg/Deprecated.idl msg/SmallConstant.msg ADD_LINTER_TESTS SKIP_INSTALL diff --git a/rosidl_generator_tests/msg/Deprecated.idl b/rosidl_generator_tests/msg/Deprecated.idl new file mode 100644 index 000000000..a56405dba --- /dev/null +++ b/rosidl_generator_tests/msg/Deprecated.idl @@ -0,0 +1,9 @@ +module rosidl_generator_tests { + module msg { + struct Deprecated { + @deprecated ( text="Use new_x") + int32 x; + int32 new_x; + }; + }; +}; diff --git a/rosidl_parser/test/msg/MyMessage.idl b/rosidl_parser/test/msg/MyMessage.idl index 0348c546c..58262828c 100644 --- a/rosidl_parser/test/msg/MyMessage.idl +++ b/rosidl_parser/test/msg/MyMessage.idl @@ -85,6 +85,11 @@ module rosidl_parser { // Optional test @optional int32 optional_int; + + // Deprecated test + @deprecated ( text="Use new_val instead") + int32 old_val; + int32 new_val; }; }; }; diff --git a/rosidl_parser/test/test_parser.py b/rosidl_parser/test/test_parser.py index e7a66f70c..614b4d4aa 100644 --- a/rosidl_parser/test/test_parser.py +++ b/rosidl_parser/test/test_parser.py @@ -134,7 +134,7 @@ def test_message_parser_structure(message_idl_file: IdlFile) -> None: structure = messages[0].structure assert structure.namespaced_type.namespaces == ['rosidl_parser', 'msg'] assert structure.namespaced_type.name == 'MyMessage' - assert len(structure.members) == 46 + assert len(structure.members) == 48 assert isinstance(structure.members[0].type, BasicType) assert structure.members[0].type.typename == 'int16' @@ -310,6 +310,17 @@ def test_message_parser_annotations(message_idl_file: IdlFile) -> None: assert len(structure.members[45].annotations) == 1 assert structure.members[45].annotations[0].name == 'optional' + assert isinstance(structure.members[46].type, BasicType) + assert structure.members[46].type.typename == 'int32' + assert structure.members[46].name == 'old_val' + assert len(structure.members[46].annotations) == 1 + assert structure.members[46].annotations[0].name == 'deprecated' + assert structure.members[46].annotations[0].value == {'text': 'Use new_val instead'} + + assert isinstance(structure.members[47].type, BasicType) + assert structure.members[47].type.typename == 'int32' + assert structure.members[47].name == 'new_val' + @pytest.fixture(scope='module') def service_idl_file() -> IdlFile: diff --git a/rosidl_runtime_c/include/rosidl_runtime_c/deprecation.h b/rosidl_runtime_c/include/rosidl_runtime_c/deprecation.h new file mode 100644 index 000000000..c31817059 --- /dev/null +++ b/rosidl_runtime_c/include/rosidl_runtime_c/deprecation.h @@ -0,0 +1,40 @@ +// Copyright 2017 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. + +#ifndef ROSIDL_RUNTIME_C__DEPRECATION_H_ +#define ROSIDL_RUNTIME_C__DEPRECATION_H_ + +// Can be removed in C23 +#if defined(_MSC_VER) + #define ROSIDL_DEPRECATED(text) __declspec(deprecated(text)) +#elif defined(__GNUC__) || defined(__clang__) + #define ROSIDL_DEPRECATED(text) __attribute__((deprecated(text))) +#else + #define ROSIDL_DEPRECATED(text) +#endif + +#if defined(_MSC_VER) + #define DISABLE_DEPRECATED_PUSH __pragma(warning(push)) \ + __pragma(warning(disable: 4996)) + #define DISABLE_DEPRECATED_POP __pragma(warning(pop)) +#elif defined(__clang__) || defined(__GNUC__) + #define DISABLE_DEPRECATED_PUSH _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") + #define DISABLE_DEPRECATED_POP _Pragma("GCC diagnostic pop") +#else + #define DISABLE_DEPRECATED_PUSH + #define DISABLE_DEPRECATED_POP +#endif + +#endif // ROSIDL_RUNTIME_C__DEPRECATION_H_ diff --git a/rosidl_typesupport_introspection_c/resource/msg__type_support.c.em b/rosidl_typesupport_introspection_c/resource/msg__type_support.c.em index 19b00ebef..8599b1e61 100644 --- a/rosidl_typesupport_introspection_c/resource/msg__type_support.c.em +++ b/rosidl_typesupport_introspection_c/resource/msg__type_support.c.em @@ -53,6 +53,11 @@ function_prefix = '__'.join([package_name] + list(interface_path.parents[0].part from collections import OrderedDict includes = OrderedDict() for member in message.structure.members: + if (member.has_annotation('deprecated')): + includes.setdefault( + 'rosidl_runtime_c/deprecation.h', [] + ) + if isinstance(member.type, AbstractSequence) and isinstance(member.type.value_type, BasicType): member_names = includes.setdefault( 'rosidl_runtime_c/primitives_sequence_functions.h', []) @@ -201,6 +206,9 @@ bool @(function_prefix)__resize_function__@(message.structure.namespaced_type.na @[ end if]@ @[ end if]@ @[end for]@ +@[if any(mem.has_annotation('deprecated') for mem in message.structure.members)]@ +DISABLE_DEPRECATED_PUSH +@[end if]@ static rosidl_typesupport_introspection_c__MessageMember @(function_prefix)__@(message.structure.namespaced_type.name)_message_member_array[@(len(message.structure.members))] = { @{ for index, member in enumerate(message.structure.members): @@ -272,6 +280,9 @@ for index, member in enumerate(message.structure.members): print(' }') }@ }; +@[if any(mem.has_annotation('deprecated') for mem in message.structure.members)]@ +DISABLE_DEPRECATED_POP +@[end if]@ static const rosidl_typesupport_introspection_c__MessageMembers @(function_prefix)__@(message.structure.namespaced_type.name)_message_members = { "@('__'.join([package_name] + list(interface_path.parents[0].parts)))", // message namespace diff --git a/rosidl_typesupport_introspection_cpp/resource/msg__type_support.cpp.em b/rosidl_typesupport_introspection_cpp/resource/msg__type_support.cpp.em index 392fc8394..df596d202 100644 --- a/rosidl_typesupport_introspection_cpp/resource/msg__type_support.cpp.em +++ b/rosidl_typesupport_introspection_cpp/resource/msg__type_support.cpp.em @@ -24,6 +24,7 @@ header_files = [ 'cstddef', # providing offsetof() 'string', 'vector', + 'rosidl_runtime_c/deprecation.h', 'rosidl_runtime_c/message_type_support_struct.h', 'rosidl_typesupport_cpp/message_type_support.hpp', 'rosidl_typesupport_interface/macros.h', @@ -167,6 +168,9 @@ void resize_function__@(message.structure.namespaced_type.name)__@(member.name)( @[ end if]@ @[ end if]@ @[end for]@ +@[if any(mem.has_annotation('deprecated') for mem in message.structure.members)]@ +DISABLE_DEPRECATED_PUSH +@[end if]@ static const ::rosidl_typesupport_introspection_cpp::MessageMember @(message.structure.namespaced_type.name)_message_member_array[@(len(message.structure.members))] = { @{ for index, member in enumerate(message.structure.members): @@ -238,6 +242,9 @@ for index, member in enumerate(message.structure.members): print(' }') }@ }; +@[if any(mem.has_annotation('deprecated') for mem in message.structure.members)]@ +DISABLE_DEPRECATED_POP +@[end if]@ static const ::rosidl_typesupport_introspection_cpp::MessageMembers @(message.structure.namespaced_type.name)_message_members = { "@('::'.join([package_name] + list(interface_path.parents[0].parts)))", // message namespace