From 085e69c0c990b849dbeb5f37ea1cb69aa292cdec Mon Sep 17 00:00:00 2001 From: Matthew Elwin <10161574+m-elwin@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:26:26 -0600 Subject: [PATCH] Let XML executables/nodes be "required" (like in ROS 1) (#751) * Let XML nodes be "required" Essentially on_exit="shutdown" is equivalent to ROS 1 required="true". This feature is implemented using the python launchfile on_exit mechanism. Right now "shutdown" is the only action accepted by on_exit, but theoretically more "on_exit" actions could be added later. Example: * Added tests for yaml Signed-off-by: Matthew Elwin --- launch/launch/actions/execute_process.py | 12 +++++++++++- launch_xml/test/launch_xml/test_executable.py | 17 +++++++++++++++++ .../test/launch_yaml/test_executable.py | 18 ++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/launch/launch/actions/execute_process.py b/launch/launch/actions/execute_process.py index c28a9752f..ba2178142 100644 --- a/launch/launch/actions/execute_process.py +++ b/launch/launch/actions/execute_process.py @@ -23,7 +23,7 @@ from typing import Text from .execute_local import ExecuteLocal - +from .shutdown_action import Shutdown from ..descriptions import Executable from ..frontend import Entity from ..frontend import expose_action @@ -331,6 +331,16 @@ def parse( if name is not None: kwargs['name'] = parser.parse_substitution(name) + if 'on_exit' not in ignore: + on_exit = entity.get_attr('on_exit', optional=True) + if on_exit is not None: + if on_exit == 'shutdown': + kwargs['on_exit'] = [Shutdown()] + else: + raise ValueError( + 'Attribute on_exit of Entity node expected to be shutdown but got `{}`' + 'Other on_exit actions not yet supported'.format(on_exit)) + if 'prefix' not in ignore: prefix = entity.get_attr('launch-prefix', optional=True) if prefix is not None: diff --git a/launch_xml/test/launch_xml/test_executable.py b/launch_xml/test/launch_xml/test_executable.py index e27ecbf4e..265747669 100644 --- a/launch_xml/test/launch_xml/test_executable.py +++ b/launch_xml/test/launch_xml/test_executable.py @@ -19,6 +19,7 @@ import textwrap from launch import LaunchService +from launch.actions import Shutdown from launch.frontend import Parser import pytest @@ -64,5 +65,21 @@ def test_executable_wrong_subtag(): assert 'whats_this' in str(excinfo.value) +def test_executable_on_exit(): + xml_file = \ + """\ + + + + """ + xml_file = textwrap.dedent(xml_file) + root_entity, parser = Parser.load(io.StringIO(xml_file)) + ld = parser.parse_description(root_entity) + executable = ld.entities[0] + sub_entities = executable.get_sub_entities() + assert len(sub_entities) == 1 + assert isinstance(sub_entities[0], Shutdown) + + if __name__ == '__main__': test_executable() diff --git a/launch_yaml/test/launch_yaml/test_executable.py b/launch_yaml/test/launch_yaml/test_executable.py index 5c6ec2b1a..95ed66fc2 100644 --- a/launch_yaml/test/launch_yaml/test_executable.py +++ b/launch_yaml/test/launch_yaml/test_executable.py @@ -18,6 +18,7 @@ import textwrap from launch import LaunchService +from launch.actions import Shutdown from launch.frontend import Parser @@ -58,5 +59,22 @@ def test_executable(): assert(0 == ls.run()) +def test_executable_on_exit(): + yaml_file = \ + """\ + launch: + - executable: + cmd: ls + on_exit: shutdown + """ + yaml_file = textwrap.dedent(yaml_file) + root_entity, parser = Parser.load(io.StringIO(yaml_file)) + ld = parser.parse_description(root_entity) + executable = ld.entities[0] + sub_entities = executable.get_sub_entities() + assert len(sub_entities) == 1 + assert isinstance(sub_entities[0], Shutdown) + + if __name__ == '__main__': test_executable()