Skip to content

Commit 0610e64

Browse files
Scope launch file dir/path locals to included launch file
Signed-off-by: Christophe Bedard <[email protected]>
1 parent 6eb159c commit 0610e64

File tree

2 files changed

+49
-16
lines changed

2 files changed

+49
-16
lines changed

launch/launch/actions/include_launch_description.py

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
import launch.logging
3030

31-
31+
from .opaque_function import OpaqueFunction
3232
from .set_launch_configuration import SetLaunchConfiguration
3333
from ..action import Action
3434
from ..frontend import Entity
@@ -212,13 +212,7 @@ def execute(self, context: LaunchContext) -> List[Union[SetLaunchConfiguration,
212212
LaunchDescriptionEntity]]:
213213
"""Execute the action."""
214214
launch_description = self.__launch_description_source.get_launch_description(context)
215-
# If the location does not exist, then it's likely set to '<script>' or something.
216-
context.extend_locals({
217-
'current_launch_file_path': self._get_launch_file(),
218-
})
219-
context.extend_locals({
220-
'current_launch_file_directory': self._get_launch_file_directory(),
221-
})
215+
self._set_launch_file_location_locals(context)
222216

223217
# Do best effort checking to see if non-optional, non-default declared arguments
224218
# are being satisfied.
@@ -255,7 +249,45 @@ def execute(self, context: LaunchContext) -> List[Union[SetLaunchConfiguration,
255249
set_launch_configuration_actions.append(SetLaunchConfiguration(name, value))
256250

257251
# Set launch arguments as launch configurations and then include the launch description.
258-
return [*set_launch_configuration_actions, launch_description]
252+
return [
253+
*set_launch_configuration_actions,
254+
launch_description,
255+
OpaqueFunction(function=self._restore_launch_file_location_locals),
256+
]
257+
258+
def _set_launch_file_location_locals(self, context: LaunchContext) -> None:
259+
context._push_locals()
260+
# Keep the previous launch file path/dir locals so that we can restore them after
261+
context_locals = context.get_locals_as_dict()
262+
self.__previous_launch_file_path = context_locals.get('current_launch_file_path', None)
263+
self.__previous_launch_file_dir = context_locals.get('current_launch_file_directory', None)
264+
context.extend_locals({
265+
'current_launch_file_path': self._get_launch_file(),
266+
})
267+
context.extend_locals({
268+
'current_launch_file_directory': self._get_launch_file_directory(),
269+
})
270+
271+
def _restore_launch_file_location_locals(self, context: LaunchContext) -> None:
272+
# We want to keep the state of the context locals even after the include, since included
273+
# launch descriptions are meant to act as if they were included literally in the parent
274+
# launch description.
275+
# However, we want to restore the launch file path/dir locals to their previous state, and
276+
# we may have to just delete them if we're now going back to a launch script (i.e., not a
277+
# launch file). However, there is no easy way to delete context locals, so save current
278+
# locals, reset to the state before the include previous state and then re-apply locals,
279+
# potentially minus the launch file path/dir locals.
280+
context_locals = context.get_locals_as_dict()
281+
if self.__previous_launch_file_path is None:
282+
del context_locals['current_launch_file_path']
283+
else:
284+
context_locals['current_launch_file_path'] = self.__previous_launch_file_path
285+
if self.__previous_launch_file_dir is None:
286+
del context_locals['current_launch_file_directory']
287+
else:
288+
context_locals['current_launch_file_directory'] = self.__previous_launch_file_dir
289+
context._pop_locals()
290+
context.extend_locals(context_locals)
259291

260292
def __repr__(self) -> Text:
261293
"""Return a description of this IncludeLaunchDescription as a string."""

launch/test/launch/actions/test_include_launch_description.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def test_include_launch_description_methods():
5151
assert isinstance(action.describe_sub_entities(), list)
5252
assert isinstance(action.describe_conditional_sub_entities(), list)
5353
# Result should only contain the launch description as there are no launch arguments.
54-
assert action.visit(LaunchContext()) == [ld]
54+
assert action.visit(LaunchContext())[0] == ld
5555
assert action.get_asyncio_future() is None
5656
assert len(action.launch_arguments) == 0
5757

@@ -61,7 +61,7 @@ def test_include_launch_description_methods():
6161
assert isinstance(action2.describe_sub_entities(), list)
6262
assert isinstance(action2.describe_conditional_sub_entities(), list)
6363
# Result should only contain the launch description as there are no launch arguments.
64-
assert action2.visit(LaunchContext()) == [ld2]
64+
assert action2.visit(LaunchContext())[0] == ld2
6565
assert action2.get_asyncio_future() is None
6666
assert len(action2.launch_arguments) == 0
6767

@@ -75,7 +75,7 @@ def test_include_launch_description_launch_file_location():
7575
assert isinstance(action.describe_conditional_sub_entities(), list)
7676
lc1 = LaunchContext()
7777
# Result should only contain the launch description as there are no launch arguments.
78-
assert action.visit(lc1) == [ld]
78+
assert action.visit(lc1)[0] == ld
7979
assert lc1.locals.current_launch_file_directory == '<script>'
8080
assert action.get_asyncio_future() is None
8181

@@ -87,7 +87,7 @@ def test_include_launch_description_launch_file_location():
8787
assert isinstance(action2.describe_conditional_sub_entities(), list)
8888
lc2 = LaunchContext()
8989
# Result should only contain the launch description as there are no launch arguments.
90-
assert action2.visit(lc2) == [ld2]
90+
assert action2.visit(lc2)[0] == ld2
9191
assert lc2.locals.current_launch_file_directory == str(this_file.parent)
9292
assert action2.get_asyncio_future() is None
9393

@@ -150,7 +150,7 @@ def test_include_launch_description_launch_arguments():
150150
assert len(action1.launch_arguments) == 1
151151
lc1 = LaunchContext()
152152
result1 = action1.visit(lc1)
153-
assert len(result1) == 2
153+
assert len(result1) == 3
154154
assert isinstance(result1[0], SetLaunchConfiguration)
155155
assert perform_substitutions(lc1, result1[0].name) == 'foo'
156156
assert perform_substitutions(lc1, result1[0].value) == 'FOO'
@@ -276,8 +276,9 @@ def test_include_python():
276276
assert 'IncludeLaunchDescription' in action.describe()
277277
assert isinstance(action.describe_sub_entities(), list)
278278
assert isinstance(action.describe_conditional_sub_entities(), list)
279-
# Result should only contain a single launch description as there are no launch arguments.
280-
assert len(action.visit(LaunchContext())) == 1
279+
# Result should only contain a single launch description (+ internal action) as there are
280+
# no launch arguments.
281+
assert len(action.visit(LaunchContext())) == 2
281282
assert action.get_asyncio_future() is None
282283
assert len(action.launch_arguments) == 0
283284

0 commit comments

Comments
 (0)