Skip to content

Commit e36a19e

Browse files
author
Roger Strain
committed
Adding Executable description class
Part of implementation of refactor for ros2#114 Distro A; OPSEC #2893 Signed-off-by: Roger Strain <[email protected]>
1 parent 4f32af5 commit e36a19e

File tree

3 files changed

+152
-0
lines changed

3 files changed

+152
-0
lines changed

launch/launch/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from . import actions
1818
from . import conditions
19+
from . import descriptions
1920
from . import events
2021
from . import frontend
2122
from . import logging
@@ -40,6 +41,7 @@
4041
__all__ = [
4142
'actions',
4243
'conditions',
44+
'descriptions',
4345
'events',
4446
'frontend',
4547
'logging',
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
"""descriptions Module."""
3+
4+
from .executable import Executable
5+
6+
7+
__all__ = [
8+
'Executable'
9+
]
+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
"""Module for a description of an Executable."""
2+
3+
import os
4+
import shlex
5+
import threading
6+
from typing import Dict
7+
from typing import Iterable
8+
from typing import List
9+
from typing import Optional
10+
from typing import Tuple
11+
12+
from launch.some_substitutions_type import SomeSubstitutionsType
13+
from launch.substitution import Substitution
14+
from launch.launch_context import LaunchContext
15+
from launch.utilities import normalize_to_list_of_substitutions
16+
from launch.utilities import perform_substitutions
17+
18+
_global_process_counter_lock = threading.Lock()
19+
_global_process_counter = 0 # in Python3, this number is unbounded (no rollover)
20+
21+
class Executable:
22+
"""Describes an executable (typically a single process) which may be run by the launch system."""
23+
24+
def __init__(
25+
self, *,
26+
cmd: Iterable[SomeSubstitutionsType],
27+
name: Optional[SomeSubstitutionsType] = None,
28+
cwd: Optional[SomeSubstitutionsType] = None,
29+
env: Optional[Dict[SomeSubstitutionsType, SomeSubstitutionsType]] = None,
30+
additional_env: Optional[Dict[SomeSubstitutionsType, SomeSubstitutionsType]] = None,
31+
) -> None:
32+
"""
33+
Initialize an Executable description.
34+
35+
:param cmd: A list where the first item is the executable and the rest are
36+
arguments to the executable, each item may be a string or a list of strings
37+
and Substitutions to be resolved at runtime
38+
:param name: The label used to represent the process, as a string or a Substitution
39+
to be resolved at runtime, defaults to the basename of the executable
40+
:param cwd: The directory in which to run the executable
41+
:param env: Dictionary of environment variables to be used, starting from a clean
42+
environment. If None, the current environment is used.
43+
:param additional_env: Dictionary of environment variables to be added. If env was
44+
None, they are added to the current environment. If not, env is updated with
45+
additional_env.
46+
"""
47+
self.__cmd = [normalize_to_list_of_substitutions(x) for x in cmd]
48+
self.__name = name if name is None else normalize_to_list_of_substitutions(name)
49+
self.__cwd = cwd if cwd is None else normalize_to_list_of_substitutions(cwd)
50+
self.__env = None # type: Optional[List[Tuple[List[Substitution], List[Substitution]]]]
51+
if env is not None:
52+
self.__env = []
53+
for key, value in env.items():
54+
self.__env.append((
55+
normalize_to_list_of_substitutions(key),
56+
normalize_to_list_of_substitutions(value)))
57+
self.__additional_env: Optional[List[Tuple[List[Substitution], List[Substitution]]]] = None
58+
if additional_env is not None:
59+
self.__additional_env = []
60+
for key, value in additional_env.items():
61+
self.__additional_env.append((
62+
normalize_to_list_of_substitutions(key),
63+
normalize_to_list_of_substitutions(value)))
64+
65+
@property
66+
def name(self):
67+
"""Getter for name."""
68+
return self.__name
69+
70+
@property
71+
def cmd(self):
72+
"""Getter for cmd."""
73+
return self.__cmd
74+
75+
@property
76+
def cwd(self):
77+
"""Getter for cwd."""
78+
return self.__cwd
79+
80+
@property
81+
def env(self):
82+
"""Getter for env."""
83+
return self.__env
84+
85+
@property
86+
def additional_env(self):
87+
"""Getter for additional_env."""
88+
return self.__additional_env
89+
90+
@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
94+
95+
def apply_context(self, context: LaunchContext):
96+
"""
97+
Prepares an executable description for execution in a given environment.
98+
99+
This does the following:
100+
- performs substitutions on various properties
101+
"""
102+
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')
106+
107+
def __expand_substitutions(self, context):
108+
# expand substitutions in arguments to async_execute_process()
109+
cmd = [perform_substitutions(context, x) for x in self.__cmd]
110+
name = os.path.basename(cmd[0]) if self.__name is None \
111+
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)
116+
cwd = None
117+
if self.__cwd is not None:
118+
cwd = ''.join([context.perform_substitution(x) for x in self.__cwd])
119+
env = None
120+
if self.__env is not None:
121+
env = {}
122+
for key, value in self.__env:
123+
env[''.join([context.perform_substitution(x) for x in key])] = \
124+
''.join([context.perform_substitution(x) for x in value])
125+
if self.__additional_env is not None:
126+
if env is None:
127+
env = dict(os.environ)
128+
for key, value in self.__additional_env:
129+
env[''.join([context.perform_substitution(x) for x in key])] = \
130+
''.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+

0 commit comments

Comments
 (0)