Skip to content

Commit e8b2dcb

Browse files
author
Roger Strain
committed
Refactor arguments from nodes, address feedback
Distro A; OPSEC #4584 Signed-off-by: Roger Strain <[email protected]>
1 parent b9fd2a6 commit e8b2dcb

File tree

11 files changed

+50
-33
lines changed

11 files changed

+50
-33
lines changed

launch_ros/launch_ros/actions/lifecycle_node.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ def execute(self, context: launch.LaunchContext) -> Optional[List[Action]]:
142142
143143
Delegated to :meth:`launch.actions.ExecuteProcess.execute`.
144144
"""
145-
self._perform_substitutions(context) # ensure self.node_name is expanded
145+
self.prepare(context) # ensure self.node_name is expanded
146146
if '<node_name_unspecified>' in self.node_name:
147147
raise RuntimeError('node_name unexpectedly incomplete for lifecycle node')
148148
node = get_ros_node(context)

launch_ros/launch_ros/actions/node.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
"""Module for the Node action."""
1616

17+
from typing import Dict
1718
from typing import Iterable
1819
from typing import List
1920
from typing import Optional
@@ -51,6 +52,7 @@ def __init__(
5152
parameters: Optional[SomeParameters] = None,
5253
remappings: Optional[SomeRemapRules] = None,
5354
arguments: Optional[Iterable[SomeSubstitutionsType]] = None,
55+
additional_env: Optional[Dict[SomeSubstitutionsType, SomeSubstitutionsType]] = None,
5456
**kwargs
5557
) -> None:
5658
"""
@@ -112,16 +114,20 @@ def __init__(
112114
:param: remappings ordered list of 'to' and 'from' string pairs to be
113115
passed to the node as ROS remapping rules
114116
:param: arguments list of extra arguments for the node
117+
:param additional_env: Dictionary of environment variables to be added. If env was
118+
None, they are added to the current environment. If not, env is updated with
119+
additional_env.
115120
"""
116121
self.__node_desc = NodeDescription(node_name=name, node_namespace=namespace,
117-
parameters=parameters, remappings=remappings,
118-
arguments=arguments)
122+
parameters=parameters, remappings=remappings)
123+
ros_exec_kwargs = {'additional_env': additional_env, 'arguments': arguments}
119124
self.__ros_exec = RosExecutable(package=package, executable=executable,
120-
nodes=[self.__node_desc])
125+
nodes=[self.__node_desc], **ros_exec_kwargs)
121126
super().__init__(process_description=self.__ros_exec, **kwargs)
122127

123-
def _perform_substitutions(self, lc: LaunchContext):
124-
self.__node_desc.prepare(lc, self.__ros_exec)
128+
def prepare(self, context: LaunchContext):
129+
self.__node_desc.prepare(context, self.__ros_exec, self)
130+
super().prepare(context)
125131

126132
def is_node_name_fully_specified(self):
127133
return self.__node_desc.is_node_name_fully_specified()
@@ -193,6 +199,10 @@ def get_nested_dictionary_from_nested_key_value_pairs(params):
193199
def parse(cls, entity: Entity, parser: Parser):
194200
"""Parse node."""
195201
# See parse method of `ExecuteProcess`
202+
# Note: This class originally was a direct descendant of ExecuteProcess,
203+
# but has been refactored to better divide the concept of a node vs an
204+
# executable process. This class remains as a compatibility layer, and
205+
# must hand off parsing duties to its original ancestor.
196206
_, kwargs = ExecuteProcess.parse(entity, parser, ignore=['cmd'])
197207
args = entity.get_attr('args', optional=True)
198208
if args is not None:

launch_ros/launch_ros/descriptions/node.py

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

38+
from launch import Action
3839
from launch import LaunchContext
3940
from launch import SomeSubstitutionsType
4041
from launch.descriptions import Executable
@@ -75,7 +76,6 @@ def __init__(
7576
node_namespace: Optional[SomeSubstitutionsType] = None,
7677
parameters: Optional[SomeParameters] = None,
7778
remappings: Optional[SomeRemapRules] = None,
78-
arguments: Optional[Iterable[SomeSubstitutionsType]] = None,
7979
traits: Optional[Iterable[NodeTrait]] = None,
8080
**kwargs
8181
) -> None:
@@ -121,7 +121,6 @@ def __init__(
121121
or dictionaries of parameters.
122122
:param: remappings ordered list of 'to' and 'from' string pairs to be
123123
passed to the node as ROS remapping rules
124-
:param: arguments list of extra arguments for the node
125124
:param: traits list of special traits of the node
126125
"""
127126
if parameters is not None:
@@ -134,7 +133,6 @@ def __init__(
134133
self.__node_namespace = node_namespace
135134
self.__parameters = [] if parameters is None else normalized_params
136135
self.__remappings = [] if remappings is None else list(normalize_remap_rules(remappings))
137-
self.__arguments = arguments
138136
self.__traits = traits
139137

140138
self.__expanded_node_name = self.UNSPECIFIED_NODE_NAME
@@ -169,11 +167,6 @@ def remappings(self):
169167
"""Getter for remappings."""
170168
return self.__remappings
171169

172-
@property
173-
def arguments(self):
174-
"""Getter for arguments."""
175-
return self.__arguments
176-
177170
@property
178171
def traits(self):
179172
"""Getter for traits."""
@@ -217,14 +210,22 @@ def _get_parameter_rule(self, param: 'Parameter', context: LaunchContext):
217210
name, value = param.evaluate(context)
218211
return f'{name}:={yaml.dump(value)}'
219212

220-
def prepare(self, context: LaunchContext, executable: Executable) -> None:
213+
def prepare(self, context: LaunchContext, executable: Executable, action: Action) -> None:
214+
self._perform_substitutions(context, executable.cmd)
215+
216+
# Prepare any traits which may be defined for this node
217+
if self.__traits is not None:
218+
for trait in self.__traits:
219+
trait.prepare(self, context, action)
220+
221+
def _perform_substitutions(self, context: LaunchContext, cmd: List) -> None:
221222
try:
222223
if self.__substitutions_performed:
223224
# This function may have already been called by a subclass' `execute`, for example.
224225
return
225226
self.__substitutions_performed = True
226227
cmd_ext = ['--ros-args'] # Prepend ros specific arguments with --ros-args flag
227-
executable.cmd.extend([normalize_to_list_of_substitutions(x) for x in cmd_ext])
228+
cmd.extend([normalize_to_list_of_substitutions(x) for x in cmd_ext])
228229
if self.__node_name is not None:
229230
self.__expanded_node_name = perform_substitutions(
230231
context, normalize_to_list_of_substitutions(self.__node_name))
@@ -240,7 +241,7 @@ def prepare(self, context: LaunchContext, executable: Executable) -> None:
240241
if expanded_node_namespace is not None:
241242
self.__expanded_node_namespace = expanded_node_namespace
242243
cmd_ext = ['-r', LocalSubstitution("ros_specific_arguments['ns']")]
243-
executable.cmd.extend([normalize_to_list_of_substitutions(x) for x in cmd_ext])
244+
cmd.extend([normalize_to_list_of_substitutions(x) for x in cmd_ext])
244245
validate_namespace(self.__expanded_node_namespace)
245246
except Exception:
246247
self.__logger.error(
@@ -262,7 +263,7 @@ def prepare(self, context: LaunchContext, executable: Executable) -> None:
262263
param_file_path = self._create_params_file_from_dict(global_params)
263264
self.__expanded_parameter_arguments.append((param_file_path, True))
264265
cmd_ext = ['--params-file', f'{param_file_path}']
265-
executable.cmd.extend([normalize_to_list_of_substitutions(x) for x in cmd_ext])
266+
cmd.extend([normalize_to_list_of_substitutions(x) for x in cmd_ext])
266267
assert os.path.isfile(param_file_path)
267268
# expand parameters too
268269
if self.__parameters is not None:
@@ -287,7 +288,7 @@ def prepare(self, context: LaunchContext, executable: Executable) -> None:
287288
continue
288289
self.__expanded_parameter_arguments.append((param_argument, is_file))
289290
cmd_ext = ['--params-file' if is_file else '-p', f'{param_argument}']
290-
executable.cmd.extend([normalize_to_list_of_substitutions(x) for x in cmd_ext])
291+
cmd.extend([normalize_to_list_of_substitutions(x) for x in cmd_ext])
291292
# expand remappings too
292293
global_remaps = context.launch_configurations.get('ros_remaps', None)
293294
if global_remaps or self.__remappings:
@@ -303,7 +304,7 @@ def prepare(self, context: LaunchContext, executable: Executable) -> None:
303304
cmd_ext = []
304305
for src, dst in self.__expanded_remappings:
305306
cmd_ext.extend(['-r', f'{src}:={dst}'])
306-
executable.cmd.extend([normalize_to_list_of_substitutions(x) for x in cmd_ext])
307+
cmd.extend([normalize_to_list_of_substitutions(x) for x in cmd_ext])
307308
# Prepare the ros_specific_arguments list and add it to the context so that the
308309
# LocalSubstitution placeholders added to the the cmd can be expanded using the contents.
309310
ros_specific_arguments: Dict[str, Union[str, List[str]]] = {}
@@ -321,4 +322,4 @@ def prepare(self, context: LaunchContext, executable: Executable) -> None:
321322
execute_process_logger.warning(
322323
'there are now at least {} nodes with the name {} created within this '
323324
'launch context'.format(node_name_count, self.node_name)
324-
)
325+
)

launch_ros/launch_ros/descriptions/node_trait.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,14 @@
2222

2323
"""Module for a description of a NodeTrait."""
2424

25+
from typing import TYPE_CHECKING
26+
2527
from launch import Action
2628
from launch import LaunchContext
2729

30+
if TYPE_CHECKING:
31+
from . import Node
32+
2833

2934
class NodeTrait:
3035
"""Describes a trait of a node."""
@@ -40,5 +45,5 @@ def __init__(self) -> None:
4045
"""
4146
pass
4247

43-
def prepare(self, node, context: LaunchContext, action: Action):
48+
def prepare(self, node: 'Node', context: LaunchContext, action: Action):
4449
"""Perform any actions necessary to prepare the node for execution."""

launch_ros/launch_ros/descriptions/ros_executable.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from typing import Iterable
2626
from typing import Optional
2727

28+
from launch import Action
2829
from launch import LaunchContext
2930
from launch import SomeSubstitutionsType
3031
from launch.descriptions import Executable
@@ -77,7 +78,7 @@ def nodes(self):
7778
"""Getter for nodes."""
7879
return self.__nodes
7980

80-
def apply_context(self, context: LaunchContext):
81+
def prepare(self, context: LaunchContext, action: Action):
8182
"""
8283
Prepare a ROS executable description for execution in a given environment.
8384
@@ -86,6 +87,6 @@ def apply_context(self, context: LaunchContext):
8687
- performs substitutions on various properties
8788
"""
8889
for node in self.__nodes:
89-
node.prepare(context, self)
90+
node.prepare(context, self, action)
9091

91-
super().apply_context(context)
92+
super().prepare(context, action)

test_launch_ros/test/test_launch_ros/actions/test_lifecycle_node.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,5 @@ def test_node_name():
5151
namespace='my_ns',
5252
)
5353
lc = LaunchContext()
54-
node_object._perform_substitutions(lc)
54+
node_object._Node__node_desc._perform_substitutions(lc, [])
5555
assert node_object.is_node_name_fully_specified() is True

test_launch_ros/test/test_launch_ros/actions/test_node.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,5 +348,5 @@ def get_test_node_name_parameters():
348348
)
349349
def test_node_name(node_object, expected_result):
350350
lc = LaunchContext()
351-
node_object._perform_substitutions(lc)
351+
node_object._Node__node_desc._perform_substitutions(lc, [])
352352
assert node_object.is_node_name_fully_specified() is expected_result

test_launch_ros/test/test_launch_ros/actions/test_push_ros_namespace.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ def test_push_ros_namespace(config):
127127
namespace=config.node_ns,
128128
name=config.node_name
129129
)
130-
node._perform_substitutions(lc)
130+
node._Node__node_desc._perform_substitutions(lc, [])
131131
expected_ns = (
132132
config.expected_ns if config.expected_ns is not None else
133133
NodeDescription.UNSPECIFIED_NODE_NAMESPACE

test_launch_ros/test/test_launch_ros/actions/test_set_parameter.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def extend_locals(self, val):
4545
pass
4646

4747
def perform_substitution(self, sub):
48-
return sub.perform(None)
48+
return sub.perform(self)
4949

5050

5151
def get_set_parameter_test_parameters():
@@ -121,7 +121,7 @@ def test_set_param_with_node():
121121
)
122122
set_param = SetParameter(name='my_param', value='my_value')
123123
set_param.execute(lc)
124-
node._perform_substitutions(lc)
124+
node._Node__node_desc._perform_substitutions(lc, [])
125125
expanded_parameter_arguments = node._Node__node_desc.expanded_parameter_arguments
126126
assert len(expanded_parameter_arguments) == 2
127127
param_file_path, is_file = expanded_parameter_arguments[0]

test_launch_ros/test/test_launch_ros/actions/test_set_remap.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ def test_set_remap_with_node():
9393
)
9494
set_remap = SetRemap('from1', 'to1')
9595
set_remap.execute(lc)
96-
node._perform_substitutions(lc)
96+
node._Node__node_desc._perform_substitutions(lc, [])
9797
assert len(node.expanded_remapping_rules) == 2
9898
assert node.expanded_remapping_rules == [('from1', 'to1'), ('from2', 'to2')]
9999

0 commit comments

Comments
 (0)