Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 861b2f9

Browse files
committedNov 8, 2021
Move NodeActionExtension to descriptions/Node.py
Distro A, OPSEC #4584. You may have additional rights; please see https://rosmilitary.org/faq/?category=ros-2-license Signed-off-by: matthew.lanting <[email protected]>
1 parent 39072de commit 861b2f9

File tree

2 files changed

+114
-105
lines changed

2 files changed

+114
-105
lines changed
 

‎launch_ros/launch_ros/actions/node.py

Lines changed: 2 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,6 @@
2121
from typing import Text # noqa: F401
2222
from typing import Tuple # noqa: F401
2323

24-
try:
25-
import importlib.metadata as importlib_metadata
26-
except ModuleNotFoundError:
27-
import importlib_metadata
28-
2924
from launch.actions import ExecuteLocal
3025
from launch.actions import ExecuteProcess
3126
from launch.frontend import Entity
@@ -43,48 +38,6 @@
4338
from launch_ros.remap_rule_type import SomeRemapRules
4439

4540

46-
class NodeActionExtension:
47-
"""
48-
The extension point for launch_ros node action extensions.
49-
50-
The following properties must be defined:
51-
* `NAME` (will be set to the entry point name)
52-
53-
The following methods may be defined:
54-
* `command_extension`
55-
* `execute`
56-
"""
57-
58-
NAME = None
59-
EXTENSION_POINT_VERSION = '0.1'
60-
61-
def __init__(self):
62-
super(NodeActionExtension, self).__init__()
63-
plugin_support.satisfies_version(self.EXTENSION_POINT_VERSION, '^0.1')
64-
65-
def prepare_for_execute(self, context, ros_specific_arguments, node_action):
66-
"""
67-
Perform any actions prior to the node's process being launched.
68-
69-
`context` is the context within which the launch is taking place,
70-
containing amongst other things the command line arguments provided by
71-
the user.
72-
73-
`ros_specific_arguments` is a dictionary of command line arguments that
74-
will be passed to the executable and are specific to ROS.
75-
76-
`node_action` is the Node action instance that is calling the
77-
extension.
78-
79-
This method must return a tuple of command line additions as a list of
80-
launch.substitutions.TextSubstitution objects, and
81-
`ros_specific_arguments` with any modifications made to it. If no
82-
modifications are made, it should return
83-
`[], ros_specific_arguments`.
84-
"""
85-
return [], ros_specific_arguments
86-
87-
8841
@expose_action('node')
8942
class Node(ExecuteLocal):
9043
"""Action that executes a ROS node."""
@@ -180,7 +133,6 @@ def __init__(
180133
ros_exec_kwargs = {'additional_env': additional_env, 'arguments': arguments}
181134
self.__ros_exec = RosExecutable(package=package, executable=executable,
182135
nodes=[self.__node_desc])
183-
self.__extensions = get_extensions(self.__logger)
184136
super().__init__(process_description=self.__ros_exec, **kwargs)
185137

186138
def is_node_name_fully_specified(self):
@@ -300,12 +252,12 @@ def parse(cls, entity: Entity, parser: Parser):
300252
@property
301253
def node_package(self):
302254
"""Getter for node_package."""
303-
return self.__node_exec.package
255+
return self.__ros_exec.package
304256

305257
@property
306258
def node_executable(self):
307259
"""Getter for node_executable."""
308-
return self.__node_exec.executable
260+
return self.__ros_exec.executable
309261

310262
@property
311263
def node_name(self):
@@ -326,56 +278,3 @@ def expanded_node_namespace(self):
326278
def expanded_remapping_rules(self):
327279
"""Getter for expanded_remappings."""
328280
return self.__node_desc.expanded_remappings
329-
330-
331-
def instantiate_extension(
332-
group_name,
333-
extension_name,
334-
extension_class,
335-
extensions,
336-
logger,
337-
*,
338-
unique_instance=False
339-
):
340-
if not unique_instance and extension_class in extensions:
341-
return extensions[extension_name]
342-
try:
343-
extension_instance = extension_class()
344-
except plugin_support.PluginException as e: # noqa: F841
345-
logger.warning(
346-
f"Failed to instantiate '{group_name}' extension "
347-
f"'{extension_name}': {e}")
348-
return None
349-
except Exception as e: # noqa: F841
350-
logger.error(
351-
f"Failed to instantiate '{group_name}' extension "
352-
f"'{extension_name}': {e}")
353-
return None
354-
if not unique_instance:
355-
extensions[extension_name] = extension_instance
356-
return extension_instance
357-
358-
359-
def get_extensions(logger):
360-
group_name = 'launch_ros.node_action'
361-
entry_points = {}
362-
for entry_point in importlib_metadata.entry_points().get(group_name, []):
363-
entry_points[entry_point.name] = entry_point
364-
extension_types = {}
365-
for entry_point in entry_points:
366-
try:
367-
extension_type = entry_points[entry_point].load()
368-
except Exception as e: # noqa: F841
369-
logger.warning(f"Failed to load entry point '{entry_point.name}': {e}")
370-
continue
371-
extension_types[entry_points[entry_point].name] = extension_type
372-
373-
extensions = {}
374-
for extension_name, extension_class in extension_types.items():
375-
extension_instance = instantiate_extension(
376-
group_name, extension_name, extension_class, extensions, logger)
377-
if extension_instance is None:
378-
continue
379-
extension_instance.NAME = extension_name
380-
extensions[extension_name] = extension_instance
381-
return extensions

‎launch_ros/launch_ros/descriptions/node.py

Lines changed: 112 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@
3535
from typing import Tuple
3636
from typing import Union
3737

38+
try:
39+
import importlib.metadata as importlib_metadata
40+
except ModuleNotFoundError:
41+
import importlib_metadata
42+
3843
from launch import Action
3944
from launch import LaunchContext
4045
from launch import SomeSubstitutionsType
@@ -52,6 +57,7 @@
5257
from launch_ros.utilities import normalize_parameters
5358
from launch_ros.utilities import normalize_remap_rules
5459
from launch_ros.utilities import prefix_namespace
60+
from launch_ros.utilities import plugin_support
5561

5662
from rclpy.validate_namespace import validate_namespace
5763
from rclpy.validate_node_name import validate_node_name
@@ -64,6 +70,48 @@
6470
from ..remap_rule_type import SomeRemapRules
6571

6672

73+
class NodeActionExtension:
74+
"""
75+
The extension point for launch_ros node action extensions.
76+
77+
The following properties must be defined:
78+
* `NAME` (will be set to the entry point name)
79+
80+
The following methods may be defined:
81+
* `command_extension`
82+
* `execute`
83+
"""
84+
85+
NAME = None
86+
EXTENSION_POINT_VERSION = '0.1'
87+
88+
def __init__(self):
89+
super(NodeActionExtension, self).__init__()
90+
plugin_support.satisfies_version(self.EXTENSION_POINT_VERSION, '^0.1')
91+
92+
def prepare_for_execute(self, context, ros_specific_arguments, node_action):
93+
"""
94+
Perform any actions prior to the node's process being launched.
95+
96+
`context` is the context within which the launch is taking place,
97+
containing amongst other things the command line arguments provided by
98+
the user.
99+
100+
`ros_specific_arguments` is a dictionary of command line arguments that
101+
will be passed to the executable and are specific to ROS.
102+
103+
`node_action` is the Node action instance that is calling the
104+
extension.
105+
106+
This method must return a tuple of command line additions as a list of
107+
launch.substitutions.TextSubstitution objects, and
108+
`ros_specific_arguments` with any modifications made to it. If no
109+
modifications are made, it should return
110+
`[], ros_specific_arguments`.
111+
"""
112+
return [], ros_specific_arguments
113+
114+
67115
class Node:
68116
"""Describes a ROS node."""
69117

@@ -146,6 +194,7 @@ def __init__(
146194
self.__substitutions_performed = False
147195

148196
self.__logger = launch.logging.get_logger(__name__)
197+
self.__extensions = get_extensions(self.__logger)
149198

150199
@property
151200
def node_name(self):
@@ -218,14 +267,14 @@ def _get_parameter_rule(self, param: 'Parameter', context: LaunchContext):
218267
return f'{self.__expanded_node_name}:{name}:={yaml.dump(value)}'
219268

220269
def prepare(self, context: LaunchContext, executable: Executable, action: Action) -> None:
221-
self._perform_substitutions(context, executable.cmd)
270+
self._perform_substitutions(context, executable.cmd, action)
222271

223272
# Prepare any traits which may be defined for this node
224273
if self.__traits is not None:
225274
for trait in self.__traits:
226275
trait.prepare(self, context, action)
227276

228-
def _perform_substitutions(self, context: LaunchContext, cmd: List) -> None:
277+
def _perform_substitutions(self, context: LaunchContext, cmd: List, action: Action) -> None:
229278
try:
230279
if self.__substitutions_performed:
231280
# This function may have already been called by a subclass' `execute`, for example.
@@ -332,6 +381,14 @@ def _perform_substitutions(self, context: LaunchContext, cmd: List) -> None:
332381
ros_specific_arguments['ns'] = '{}__ns:={}'.format(
333382
original_name_prefix, self.__expanded_node_namespace
334383
)
384+
# Give extensions a chance to prepare for execution
385+
for extension in self.__extensions.values():
386+
cmd_extension, ros_specific_arguments = extension.prepare_for_execute(
387+
context,
388+
ros_specific_arguments,
389+
action
390+
)
391+
cmd.extend(cmd_extension)
335392
context.extend_locals({'ros_specific_arguments': ros_specific_arguments})
336393

337394
if self.is_node_name_fully_specified():
@@ -343,3 +400,56 @@ def _perform_substitutions(self, context: LaunchContext, cmd: List) -> None:
343400
'there are now at least {} nodes with the name {} created within this '
344401
'launch context'.format(node_name_count, self.node_name)
345402
)
403+
404+
405+
def instantiate_extension(
406+
group_name,
407+
extension_name,
408+
extension_class,
409+
extensions,
410+
logger,
411+
*,
412+
unique_instance=False
413+
):
414+
if not unique_instance and extension_class in extensions:
415+
return extensions[extension_name]
416+
try:
417+
extension_instance = extension_class()
418+
except plugin_support.PluginException as e: # noqa: F841
419+
logger.warning(
420+
f"Failed to instantiate '{group_name}' extension "
421+
f"'{extension_name}': {e}")
422+
return None
423+
except Exception as e: # noqa: F841
424+
logger.error(
425+
f"Failed to instantiate '{group_name}' extension "
426+
f"'{extension_name}': {e}")
427+
return None
428+
if not unique_instance:
429+
extensions[extension_name] = extension_instance
430+
return extension_instance
431+
432+
433+
def get_extensions(logger):
434+
group_name = 'launch_ros.node_action'
435+
entry_points = {}
436+
for entry_point in importlib_metadata.entry_points().get(group_name, []):
437+
entry_points[entry_point.name] = entry_point
438+
extension_types = {}
439+
for entry_point in entry_points:
440+
try:
441+
extension_type = entry_points[entry_point].load()
442+
except Exception as e: # noqa: F841
443+
logger.warning(f"Failed to load entry point '{entry_point.name}': {e}")
444+
continue
445+
extension_types[entry_points[entry_point].name] = extension_type
446+
447+
extensions = {}
448+
for extension_name, extension_class in extension_types.items():
449+
extension_instance = instantiate_extension(
450+
group_name, extension_name, extension_class, extensions, logger)
451+
if extension_instance is None:
452+
continue
453+
extension_instance.NAME = extension_name
454+
extensions[extension_name] = extension_instance
455+
return extensions

0 commit comments

Comments
 (0)
Please sign in to comment.