Skip to content

Commit 36cbcb6

Browse files
Scope launch file dir/path locals to included launch file
Signed-off-by: Christophe Bedard <[email protected]>
1 parent 628d61e commit 36cbcb6

File tree

2 files changed

+49
-15
lines changed

2 files changed

+49
-15
lines changed

launch/launch/actions/include_launch_description.py

+41-8
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import launch.logging
2626

27+
from .opaque_function import OpaqueFunction
2728
from .set_launch_configuration import SetLaunchConfiguration
2829
from ..action import Action
2930
from ..frontend import Entity
@@ -147,13 +148,7 @@ def _try_get_arguments_names_without_context(self):
147148
def execute(self, context: LaunchContext) -> List[LaunchDescriptionEntity]:
148149
"""Execute the action."""
149150
launch_description = self.__launch_description_source.get_launch_description(context)
150-
# If the location does not exist, then it's likely set to '<script>' or something.
151-
context.extend_locals({
152-
'current_launch_file_path': self._get_launch_file(),
153-
})
154-
context.extend_locals({
155-
'current_launch_file_directory': self._get_launch_file_directory(),
156-
})
151+
self._set_launch_file_location_locals(context)
157152

158153
# Do best effort checking to see if non-optional, non-default declared arguments
159154
# are being satisfied.
@@ -188,7 +183,45 @@ def execute(self, context: LaunchContext) -> List[LaunchDescriptionEntity]:
188183
set_launch_configuration_actions.append(SetLaunchConfiguration(name, value))
189184

190185
# Set launch arguments as launch configurations and then include the launch description.
191-
return [*set_launch_configuration_actions, launch_description]
186+
return [
187+
*set_launch_configuration_actions,
188+
launch_description,
189+
OpaqueFunction(function=self._restore_launch_file_location_locals),
190+
]
191+
192+
def _set_launch_file_location_locals(self, context: LaunchContext) -> None:
193+
context._push_locals()
194+
# Keep the previous launch file path/dir locals so that we can restore them after
195+
context_locals = context.get_locals_as_dict()
196+
self.__previous_launch_file_path = context_locals.get('current_launch_file_path', None)
197+
self.__previous_launch_file_dir = context_locals.get('current_launch_file_directory', None)
198+
context.extend_locals({
199+
'current_launch_file_path': self._get_launch_file(),
200+
})
201+
context.extend_locals({
202+
'current_launch_file_directory': self._get_launch_file_directory(),
203+
})
204+
205+
def _restore_launch_file_location_locals(self, context: LaunchContext) -> None:
206+
# We want to keep the state of the context locals even after the include, since included
207+
# launch descriptions are meant to act as if they were included literally in the parent
208+
# launch description.
209+
# However, we want to restore the launch file path/dir locals to their previous state, and
210+
# we may have to just delete them if we're now going back to a launch script (i.e., not a
211+
# launch file). However, there is no easy way to delete context locals, so save current
212+
# locals, reset to the state before the include previous state and then re-apply locals,
213+
# potentially minus the launch file path/dir locals.
214+
context_locals = context.get_locals_as_dict()
215+
if self.__previous_launch_file_path is None:
216+
del context_locals['current_launch_file_path']
217+
else:
218+
context_locals['current_launch_file_path'] = self.__previous_launch_file_path
219+
if self.__previous_launch_file_dir is None:
220+
del context_locals['current_launch_file_directory']
221+
else:
222+
context_locals['current_launch_file_directory'] = self.__previous_launch_file_dir
223+
context._pop_locals()
224+
context.extend_locals(context_locals)
192225

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

launch/test/launch/actions/test_include_launch_description.py

+8-7
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 == os.path.dirname(this_file)
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'
@@ -278,8 +278,9 @@ def test_include_python():
278278
assert 'IncludeLaunchDescription' in action.describe()
279279
assert isinstance(action.describe_sub_entities(), list)
280280
assert isinstance(action.describe_conditional_sub_entities(), list)
281-
# Result should only contain a single launch description as there are no launch arguments.
282-
assert len(action.visit(LaunchContext())) == 1
281+
# Result should only contain a single launch description (+ internal action) as there are
282+
# no launch arguments.
283+
assert len(action.visit(LaunchContext())) == 2
283284
assert action.get_asyncio_future() is None
284285
assert len(action.launch_arguments) == 0
285286

0 commit comments

Comments
 (0)