From 03c79ae929eee23df007f7e97323c05dc23a2bd8 Mon Sep 17 00:00:00 2001 From: SuperJappie08 <36795178+SuperJappie08@users.noreply.github.com> Date: Mon, 17 Mar 2025 15:15:23 +0100 Subject: [PATCH 1/4] Initial implementation of ScopedIncludeLaunchDescription Signed-off-by: SuperJappie08 <36795178+SuperJappie08@users.noreply.github.com> --- launch/launch/actions/__init__.py | 2 + .../include_scoped_launch_description.py | 131 ++++++++++++++++++ .../test_include_scoped_launch_description.py | 38 +++++ 3 files changed, 171 insertions(+) create mode 100644 launch/launch/actions/include_scoped_launch_description.py create mode 100644 launch/test/launch/actions/test_include_scoped_launch_description.py diff --git a/launch/launch/actions/__init__.py b/launch/launch/actions/__init__.py index 86d0bd9dd..65f773b13 100644 --- a/launch/launch/actions/__init__.py +++ b/launch/launch/actions/__init__.py @@ -23,6 +23,7 @@ from .for_loop import ForLoop from .group_action import GroupAction from .include_launch_description import IncludeLaunchDescription +from .include_scoped_launch_description import ScopedIncludeLaunchDescription from .log_info import LogInfo from .opaque_coroutine import OpaqueCoroutine from .opaque_function import OpaqueFunction @@ -51,6 +52,7 @@ 'ForLoop', 'GroupAction', 'IncludeLaunchDescription', + 'ScopedIncludeLaunchDescription', 'LogInfo', 'OpaqueCoroutine', 'OpaqueFunction', diff --git a/launch/launch/actions/include_scoped_launch_description.py b/launch/launch/actions/include_scoped_launch_description.py new file mode 100644 index 000000000..eaf00226d --- /dev/null +++ b/launch/launch/actions/include_scoped_launch_description.py @@ -0,0 +1,131 @@ +# Copyright 2025 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. + +"""Module for the ScopedIncludeLaunchDescription action.""" + +# from typing import override # Available starting from Python3.12 +from typing import List +from typing import Text +# from itertools import zip_longest + +from .include_launch_description import IncludeLaunchDescription +from .pop_environment import PopEnvironment +from .pop_launch_configurations import PopLaunchConfigurations +from .push_environment import PushEnvironment +from .push_launch_configurations import PushLaunchConfigurations +from .reset_environment import ResetEnvironment +from .reset_launch_configurations import ResetLaunchConfigurations +from .set_launch_configuration import SetLaunchConfiguration +from ..frontend import expose_action +from ..launch_context import LaunchContext +from ..launch_description_entity import LaunchDescriptionEntity +from ..utilities import normalize_to_list_of_substitutions +from ..utilities import perform_substitutions + + +@expose_action('scoped_include') +class ScopedIncludeLaunchDescription(IncludeLaunchDescription): + # TODO(SuperJappie08) Propper Documentation + + # NOTE(SuperJappie08) __init__ is not required since the function signature will be the same + # However maybe it is interresting for documentation purposes + + def get_sub_entities(self): + """Get subentities.""" + ret = super().get_sub_entities() + # TODO(SuperJappie08)? Do these internals need to be hidden? + return [ + PushLaunchConfigurations(), + PushEnvironment(), + ResetEnvironment(), + # NOTE(SuperJappie08) Need weird remap, since AnySubstitution type can be a List which + # is not Hashable. + ResetLaunchConfigurations({k[0]: v for k, v in self.launch_arguments}) + * ret, + PopEnvironment(), + PopLaunchConfigurations(), + ] + + def execute(self, context: LaunchContext) -> List[LaunchDescriptionEntity]: + """Execute the action.""" + # NOTE(SuperJappie08) Originally this returend something based on the used actions + # However after further consideration the context might not be correct that way. + context._push_launch_configurations() + context._push_environment() + + context._reset_environment() + + # Reset Launch Configuration + evaluated_configurations = {} + for k, v in self.launch_arguments: + evaluated_k = perform_substitutions(context, normalize_to_list_of_substitutions(k)) + evaluated_v = perform_substitutions(context, normalize_to_list_of_substitutions(v)) + evaluated_configurations[evaluated_k] = evaluated_v + + context.launch_configurations.clear() + context.launch_configurations.update(evaluated_configurations) + + launch_description = self.launch_description_source.get_launch_description(context) + + # If the location does not exist, then it's likely set to '