Skip to content

Commit e6a2b99

Browse files
Roger Strainmlanting
authored andcommitted
Refactor arguments from nodes, address feedback
Distro A; OPSEC #4584 Signed-off-by: Roger Strain <[email protected]>
1 parent 1566309 commit e6a2b99

File tree

10 files changed

+46
-30
lines changed

10 files changed

+46
-30
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: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ def __init__(
100100
remappings: Optional[SomeRemapRules] = None,
101101
ros_arguments: Optional[Iterable[SomeSubstitutionsType]] = None,
102102
arguments: Optional[Iterable[SomeSubstitutionsType]] = None,
103+
additional_env: Optional[Dict[SomeSubstitutionsType, SomeSubstitutionsType]] = None,
103104
**kwargs
104105
) -> None:
105106
"""
@@ -170,17 +171,21 @@ def __init__(
170171
passed to the node as ROS remapping rules
171172
:param: ros_arguments list of ROS arguments for the node
172173
:param: arguments list of extra arguments for the node
174+
:param additional_env: Dictionary of environment variables to be added. If env was
175+
None, they are added to the current environment. If not, env is updated with
176+
additional_env.
173177
"""
174178
self.__node_desc = NodeDescription(node_name=name, node_namespace=namespace,
175-
parameters=parameters, remappings=remappings,
176-
arguments=arguments)
179+
parameters=parameters, remappings=remappings)
180+
ros_exec_kwargs = {'additional_env': additional_env, 'arguments': arguments}
177181
self.__ros_exec = RosExecutable(package=package, executable=executable,
178182
nodes=[self.__node_desc])
179183
self.__extensions = get_extensions(self.__logger)
180184
super().__init__(process_description=self.__ros_exec, **kwargs)
181185

182-
def _perform_substitutions(self, lc: LaunchContext):
183-
self.__node_desc.prepare(lc, self.__ros_exec)
186+
def prepare(self, context: LaunchContext):
187+
self.__node_desc.prepare(context, self.__ros_exec, self)
188+
super().prepare(context)
184189

185190
def is_node_name_fully_specified(self):
186191
return self.__node_desc.is_node_name_fully_specified()
@@ -253,6 +258,10 @@ def get_nested_dictionary_from_nested_key_value_pairs(params):
253258
def parse(cls, entity: Entity, parser: Parser):
254259
"""Parse node."""
255260
# See parse method of `ExecuteProcess`
261+
# Note: This class originally was a direct descendant of ExecuteProcess,
262+
# but has been refactored to better divide the concept of a node vs an
263+
# executable process. This class remains as a compatibility layer, and
264+
# must hand off parsing duties to its original ancestor.
256265
_, kwargs = ExecuteProcess.parse(entity, parser, ignore=['cmd'])
257266
args = entity.get_attr('args', optional=True)
258267
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
@@ -356,5 +356,5 @@ def get_test_node_name_parameters():
356356
)
357357
def test_node_name(node_object, expected_result):
358358
lc = LaunchContext()
359-
node_object._perform_substitutions(lc)
359+
node_object._Node__node_desc._perform_substitutions(lc, [])
360360
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_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

test_launch_ros/test/test_launch_ros/frontend/test_node_frontend.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ def check_launch_node(file):
159159
assert(0 == ls.run())
160160
evaluated_parameters = evaluate_parameters(
161161
ls.context,
162-
ld.describe_sub_entities()[3]._Node__parameters
162+
ld.describe_sub_entities()[3].process_description.nodes[0]._Node__parameters
163163
)
164164
assert len(evaluated_parameters) == 3
165165
assert isinstance(evaluated_parameters[0], dict)
@@ -200,7 +200,7 @@ def check_launch_node(file):
200200
assert param_dict['param_group1.param15'] == ['2', '5', '8']
201201

202202
# Check remappings exist
203-
remappings = ld.describe_sub_entities()[3]._Node__remappings
203+
remappings = ld.describe_sub_entities()[3].process_description.nodes[0]._Node__remappings
204204
assert remappings is not None
205205
assert len(remappings) == 2
206206

0 commit comments

Comments
 (0)