|
19 | 19 | import logging |
20 | 20 | import os |
21 | 21 | import platform |
| 22 | +import shlex |
22 | 23 | import signal |
23 | 24 | import traceback |
24 | 25 | from typing import Any # noqa: F401 |
@@ -82,6 +83,8 @@ def __init__( |
82 | 83 | *, |
83 | 84 | process_description: Executable, |
84 | 85 | shell: bool = False, |
| 86 | + split_arguments: SomeSubstitutionsType = LaunchConfiguration( |
| 87 | + 'split_arguments', default=False), |
85 | 88 | sigterm_timeout: SomeSubstitutionsType = LaunchConfiguration( |
86 | 89 | 'sigterm_timeout', default=5), |
87 | 90 | sigkill_timeout: SomeSubstitutionsType = LaunchConfiguration( |
@@ -150,6 +153,24 @@ def __init__( |
150 | 153 | :param: process_description the `launch.descriptions.Executable` to execute |
151 | 154 | as a local process |
152 | 155 | :param: shell if True, a shell is used to execute the cmd |
| 156 | + :param: split_arguments if True, and shell=False, the arguments are |
| 157 | + split by whitespace as if parsed by a shell, e.g. like shlex.split() |
| 158 | + from Python's standard library. |
| 159 | + Useful if the arguments need to be split, e.g. if a substitution |
| 160 | + evaluates to multiple whitespace separated arguments but shell=True |
| 161 | + cannot be used. |
| 162 | + Usually it does not make sense to split the arguments if shell=True |
| 163 | + because the shell will split them again, e.g. the single |
| 164 | + substitution '--opt1 --opt2 "Hello world"' would become ['--opt1', |
| 165 | + '--opt2', '"Hello World"'] if split_arguments=True, and then become |
| 166 | + ['--opt1', '--opt2', 'Hello', 'World'] because of shell=True, which |
| 167 | + is likely not what was originally intended. |
| 168 | + Therefore, when both shell=True and split_arguments=True, the |
| 169 | + arguments will not be split before executing, depending on the |
| 170 | + shell to split the arguments instead. |
| 171 | + If not explicitly passed, the LaunchConfiguration called |
| 172 | + 'split_arguments' will be used as the default, and if that |
| 173 | + LaunchConfiguration is not set, the default will be False. |
153 | 174 | :param: sigterm_timeout time until shutdown should escalate to SIGTERM, |
154 | 175 | as a string or a list of strings and Substitutions to be resolved |
155 | 176 | at runtime, defaults to the LaunchConfiguration called |
@@ -188,6 +209,7 @@ def __init__( |
188 | 209 | super().__init__(**kwargs) |
189 | 210 | self.__process_description = process_description |
190 | 211 | self.__shell = shell |
| 212 | + self.__split_arguments = normalize_to_list_of_substitutions(split_arguments) |
191 | 213 | self.__sigterm_timeout = normalize_to_list_of_substitutions(sigterm_timeout) |
192 | 214 | self.__sigkill_timeout = normalize_to_list_of_substitutions(sigkill_timeout) |
193 | 215 | self.__emulate_tty = emulate_tty |
@@ -234,6 +256,11 @@ def shell(self): |
234 | 256 | """Getter for shell.""" |
235 | 257 | return self.__shell |
236 | 258 |
|
| 259 | + @property |
| 260 | + def split_arguments(self): |
| 261 | + """Getter for split_arguments.""" |
| 262 | + return self.__split_arguments |
| 263 | + |
237 | 264 | @property |
238 | 265 | def emulate_tty(self): |
239 | 266 | """Getter for emulate_tty.""" |
@@ -547,14 +574,27 @@ async def __execute_process(self, context: LaunchContext) -> None: |
547 | 574 | raise RuntimeError('process_event_args unexpectedly None') |
548 | 575 |
|
549 | 576 | cmd = process_event_args['cmd'] |
| 577 | + if evaluate_condition_expression(context, self.__split_arguments): |
| 578 | + if self.__shell: |
| 579 | + self.__logger.debug( |
| 580 | + "Ignoring 'split_arguments=True' because 'shell=True'." |
| 581 | + ) |
| 582 | + else: |
| 583 | + expanded_cmd = [] |
| 584 | + for token in cmd: |
| 585 | + expanded_cmd.extend(shlex.split(token)) |
| 586 | + cmd = expanded_cmd |
550 | 587 | cwd = process_event_args['cwd'] |
551 | 588 | env = process_event_args['env'] |
552 | 589 | if self.__log_cmd: |
553 | | - self.__logger.info("process details: cmd='{}', cwd='{}', custom_env?={}".format( |
554 | | - ' '.join(filter(lambda part: part.strip(), cmd)), |
555 | | - cwd, |
556 | | - 'True' if env is not None else 'False' |
557 | | - )) |
| 590 | + self.__logger.info( |
| 591 | + "process details: cmd=['{}'], cwd='{}', shell='{}', custom_env?={}".format( |
| 592 | + "', ' ".join(cmd), |
| 593 | + cwd, |
| 594 | + 'True' if self.__shell else 'False', |
| 595 | + 'True' if env is not None else 'False', |
| 596 | + ) |
| 597 | + ) |
558 | 598 |
|
559 | 599 | emulate_tty = self.__emulate_tty |
560 | 600 | if 'emulate_tty' in context.launch_configurations: |
|
0 commit comments