Skip to content

Commit 77417a3

Browse files
Scope launch file dir/path locals to included launch file
Signed-off-by: Christophe Bedard <[email protected]>
1 parent ba6cee3 commit 77417a3

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
@@ -50,7 +50,7 @@ def test_include_launch_description_methods():
5050
assert isinstance(action.describe_sub_entities(), list)
5151
assert isinstance(action.describe_conditional_sub_entities(), list)
5252
# Result should only contain the launch description as there are no launch arguments.
53-
assert action.visit(LaunchContext()) == [ld]
53+
assert action.visit(LaunchContext())[0] == ld
5454
assert action.get_asyncio_future() is None
5555
assert len(action.launch_arguments) == 0
5656

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

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

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

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

0 commit comments

Comments
 (0)