Skip to content

Commit ac5e089

Browse files
author
Roger Strain
committed
Cleaned up access to substituted values
Modified handling of cmd parameter as described in #263 Distro A; OPSEC #2893 Signed-off-by: Roger Strain <[email protected]>
1 parent e36a19e commit ac5e089

File tree

2 files changed

+73
-27
lines changed

2 files changed

+73
-27
lines changed

launch/launch/descriptions/executable.py

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,27 @@
77
from typing import Iterable
88
from typing import List
99
from typing import Optional
10+
from typing import Text
1011
from typing import Tuple
12+
from typing import Union
1113

1214
from launch.some_substitutions_type import SomeSubstitutionsType
1315
from launch.substitution import Substitution
16+
from launch.substitutions.text_substitution import TextSubstitution
1417
from launch.launch_context import LaunchContext
1518
from launch.utilities import normalize_to_list_of_substitutions
1619
from launch.utilities import perform_substitutions
1720

18-
_global_process_counter_lock = threading.Lock()
19-
_global_process_counter = 0 # in Python3, this number is unbounded (no rollover)
21+
_executable_process_counter_lock = threading.Lock()
22+
_executable_process_counter = 0 # in Python3, this number is unbounded (no rollover)
23+
2024

2125
class Executable:
22-
"""Describes an executable (typically a single process) which may be run by the launch system."""
26+
"""Describes an executable (usually a single process) which may be run by the launch system."""
2327

2428
def __init__(
2529
self, *,
26-
cmd: Iterable[SomeSubstitutionsType],
30+
cmd: Union[SomeSubstitutionsType, Iterable[SomeSubstitutionsType]],
2731
name: Optional[SomeSubstitutionsType] = None,
2832
cwd: Optional[SomeSubstitutionsType] = None,
2933
env: Optional[Dict[SomeSubstitutionsType, SomeSubstitutionsType]] = None,
@@ -44,7 +48,12 @@ def __init__(
4448
None, they are added to the current environment. If not, env is updated with
4549
additional_env.
4650
"""
47-
self.__cmd = [normalize_to_list_of_substitutions(x) for x in cmd]
51+
if (isinstance(cmd, Text)):
52+
self.__cmd = [[TextSubstitution(text=cmd)]]
53+
elif (isinstance(cmd, Substitution)):
54+
self.__cmd = [[cmd]]
55+
else:
56+
self.__cmd = [normalize_to_list_of_substitutions(x) for x in cmd]
4857
self.__name = name if name is None else normalize_to_list_of_substitutions(name)
4958
self.__cwd = cwd if cwd is None else normalize_to_list_of_substitutions(cwd)
5059
self.__env = None # type: Optional[List[Tuple[List[Substitution], List[Substitution]]]]
@@ -88,9 +97,24 @@ def additional_env(self):
8897
return self.__additional_env
8998

9099
@property
91-
def process_details(self):
92-
"""Getter for the substituted executable details, e.g. cmd, cwd, env, or None if substitutions have not been performed."""
93-
return self.__process_event_args
100+
def final_name(self):
101+
"""Getter for final_name."""
102+
return self.__final_name
103+
104+
@property
105+
def final_cmd(self):
106+
"""Getter for final_cmd."""
107+
return self.__final_cmd
108+
109+
@property
110+
def final_cwd(self):
111+
"""Getter for cwd."""
112+
return self.__final_cwd
113+
114+
@property
115+
def final_env(self):
116+
"""Getter for final_env."""
117+
return self.__final_env
94118

95119
def apply_context(self, context: LaunchContext):
96120
"""
@@ -100,22 +124,22 @@ def apply_context(self, context: LaunchContext):
100124
- performs substitutions on various properties
101125
"""
102126
self.__expand_substitutions(context)
103-
process_event_args = self.__process_event_args
104-
if process_event_args is None:
105-
raise RuntimeError('process_event_args unexpectedly None')
106127

107128
def __expand_substitutions(self, context):
108129
# expand substitutions in arguments to async_execute_process()
109-
cmd = [perform_substitutions(context, x) for x in self.__cmd]
130+
cmd = ' '.join([perform_substitutions(context, x) for x in self.__cmd])
131+
cmd = shlex.split(cmd)
132+
self.__final_cmd = cmd
110133
name = os.path.basename(cmd[0]) if self.__name is None \
111134
else perform_substitutions(context, self.__name)
112-
with _global_process_counter_lock:
113-
global _global_process_counter
114-
_global_process_counter += 1
115-
self.__name = '{}-{}'.format(name, _global_process_counter)
135+
with _executable_process_counter_lock:
136+
global _executable_process_counter
137+
_executable_process_counter += 1
138+
self.__final_name = f":{name}-{_executable_process_counter}"
116139
cwd = None
117140
if self.__cwd is not None:
118141
cwd = ''.join([context.perform_substitution(x) for x in self.__cwd])
142+
self.__final_cwd = cwd
119143
env = None
120144
if self.__env is not None:
121145
env = {}
@@ -128,14 +152,4 @@ def __expand_substitutions(self, context):
128152
for key, value in self.__additional_env:
129153
env[''.join([context.perform_substitution(x) for x in key])] = \
130154
''.join([context.perform_substitution(x) for x in value])
131-
# store packed kwargs for all ProcessEvent based events
132-
self.__process_event_args = {
133-
'description': self,
134-
'name': self.__name,
135-
'cmd': cmd,
136-
'cwd': cwd,
137-
'env': env,
138-
# pid is added to the dictionary in the connection_made() method of the protocol.
139-
}
140-
141-
155+
self.__final_env = env

launch/test/test_executable.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# import pytest
2+
from launch.descriptions.executable import Executable
3+
from launch.launch_context import LaunchContext
4+
5+
6+
def test_executable():
7+
exe = Executable(cmd="test")
8+
assert exe is not None
9+
10+
11+
def test_cmd_simple_string():
12+
exe = Executable(cmd='ls "my/subdir/with spaces/"')
13+
exe.apply_context(LaunchContext())
14+
assert all([a == b for a, b in zip(exe.final_cmd, ['ls', 'my/subdir/with spaces/'])])
15+
16+
17+
def test_cmd_string_in_list():
18+
exe = Executable(cmd=['ls "my/subdir/with spaces/"'])
19+
exe.apply_context(LaunchContext())
20+
assert all([a == b for a, b in zip(exe.final_cmd, ['ls', 'my/subdir/with spaces/'])])
21+
22+
23+
def test_cmd_strings_in_list():
24+
exe = Executable(cmd=['ls', '"my/subdir/with spaces/"'])
25+
exe.apply_context(LaunchContext())
26+
assert all([a == b for a, b in zip(exe.final_cmd, ['ls', 'my/subdir/with spaces/'])])
27+
28+
29+
def test_cmd_multiple_arguments_in_string():
30+
exe = Executable(cmd=['ls', '-opt1', '-opt2', '-opt3'])
31+
exe.apply_context(LaunchContext())
32+
assert all([a == b for a, b in zip(exe.final_cmd, ['ls', '-opt1', '-opt2', '-opt3'])])

0 commit comments

Comments
 (0)