Skip to content

Commit 88ae20e

Browse files
authored
New launch system (ros2#74)
* update .gitignore and add default .flake8 config for 100 line length * add architecture documentation * first pass at implementation of architecture * add example and fix some imports * fix missing find/replace for launch->launch.legacy * add tests for core classes, fix some issues found testing * rename example * typo * use locals rather then a lambda in on shutdown handler * add output and log_cmd options to ExecuteProcess Output will likely be moved to an “OutputHandler” type class in the future. * add some missing attributes * add several features to the LaunchContext and LaunchService Access to argv. Access to shutdown state (from LaunchService via LaunchContext). Ability to asynchronously add completion events. Add concept of globals to go with locals. * fix style to match ament_flake8 * fix linter error * add missing dependency on osrf_pycommon * Documentation fixes * Clarify time output always goes to stderr I wondered if it was a property of prefixes because I didn't expect this * remove custom signal name lookup code in favor of signal.Signals * fix logging in one of the examples * fixup * clarify return type * fix error string message * further improve SignalProcess event class * Comment fixup * Allude to the meaning of target_action * address comments about on_process_exit.py * add copyright test * flake8 fixup * Add comment about capturing a copy * Add return option for callable handler * Avoid printing hex id of None * Clarify the 'first element' aspect of comment * Add todo for storing passed in action list * Specialise shutdown docstring * future .done() -> future.done() * Argument fixup * Update substitutions' perform docstrings * improve signal handling in LaunchService * Add timeouts types to docblock * remove .flake8 * fix signal handling of LaunchService outside of main thread and Windows * another fix for Windows
1 parent fb6cbe4 commit 88ae20e

File tree

78 files changed

+5323
-1
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+5323
-1
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
__pycache__
22
*.pyc
3+
.DS_Store
4+
.mypy_cache
5+
.pytest_cache
6+
.flake8

launch/doc/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
build

launch/doc/Makefile

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Minimal makefile for Sphinx documentation
2+
#
3+
4+
# You can set these variables from the command line.
5+
SPHINXOPTS =
6+
SPHINXBUILD = sphinx-build
7+
SPHINXPROJ = launch
8+
SOURCEDIR = source
9+
BUILDDIR = build
10+
11+
# Put it first so that "make" without argument is like "make help".
12+
help:
13+
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14+
15+
.PHONY: help Makefile
16+
17+
# Catch-all target: route all unknown targets to Sphinx using the new
18+
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19+
%: Makefile
20+
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

launch/doc/make.bat

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
@ECHO OFF
2+
3+
pushd %~dp0
4+
5+
REM Command file for Sphinx documentation
6+
7+
if "%SPHINXBUILD%" == "" (
8+
set SPHINXBUILD=sphinx-build
9+
)
10+
set SOURCEDIR=source
11+
set BUILDDIR=build
12+
set SPHINXPROJ=launch
13+
14+
if "%1" == "" goto help
15+
16+
%SPHINXBUILD% >NUL 2>NUL
17+
if errorlevel 9009 (
18+
echo.
19+
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
20+
echo.installed, then set the SPHINXBUILD environment variable to point
21+
echo.to the full path of the 'sphinx-build' executable. Alternatively you
22+
echo.may add the Sphinx directory to PATH.
23+
echo.
24+
echo.If you don't have Sphinx installed, grab it from
25+
echo.http://sphinx-doc.org/
26+
exit /b 1
27+
)
28+
29+
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
30+
goto end
31+
32+
:help
33+
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
34+
35+
:end
36+
popd

launch/doc/source/architecture.rst

+208
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
Architecture of `launch`
2+
========================
3+
4+
`launch` is designed to provide core features like describing actions (e.g. executing a process or including another launch description), generating events, introspecting launch descriptions, and executing launch descriptions.
5+
At the same time, it provides extension points so that the set of things that these core features can operate on, or integrate with, can be expanded with additional packages.
6+
7+
Launch Entities and Launch Descriptions
8+
---------------------------------------
9+
10+
The main object in `launch` is the :class:`launch.LaunchDescriptionEntity`, from which other entities that are "launched" inherit.
11+
This class, or more specifically classes derived from this class, are responsible for capturing the system architect's (a.k.a. the user's) intent for how the system should be launched, as well as how `launch` itself should react to asynchronous events in the system during launch.
12+
A launch description entity has its :meth:`launch.LaunchDescriptionEntity.visit` method called during "launching", and has any of the "describe" methods called during "introspection".
13+
It may also provide a :class:`asyncio.Future` with the :meth:`launch.LaunchDescriptionEntity.get_asyncio_future` method, if it has on-going asynchronous activity after returning from visit.
14+
15+
When visited, entities may yield additional entities to be visited, and this pattern is used from the "root" of the launch, where a special entity called :class:`launch.LaunchDescription` is provided to start the launch process.
16+
17+
The :class:`launch.LaunchDescription` class encapsulates the intent of the user as a list of discrete :class:`launch.Action`'s, which are also derived from :class:`launch.LaunchDescriptionEntity`.
18+
As "launch description entities" themselves, these "actions" can either be introspected for analysis without performing the side effects, or the actions can be executed, usually in response to an event in the launch system.
19+
20+
Additionally, launch descriptions, and the actions that they contain, can have references to :class:`launch.Substitution`'s within them.
21+
These substitutions are things that can be evaluated during launch and can be used to do various things like: get a launch configuration, get an environment variable, or evaluate arbitrary Python expressions.
22+
23+
Launch descriptions, and the actions contained therein, can either be introspected directly or launched by a :class:`launch.LaunchService`.
24+
A launch service is a long running activity that handles the event loop and dispatches actions.
25+
26+
Actions
27+
-------
28+
29+
The aforementioned actions allow the user to express various intentions, and the set of available actions to the user can also be extended by other packages, allowing for domain specific actions.
30+
31+
Actions can have direct side effects (e.g. run a process or set a configuration variable) and as well they can yield additional actions.
32+
The latter can be used to create "syntactic sugar" actions which simply yield more verbose actions.
33+
34+
Actions may also have arguments, which can affect the behavior of the actions.
35+
These arguments are where :class:`launch.Substitution`'s can be used to provide more flexibility when describing reusable launch descriptions.
36+
37+
Basic Actions
38+
^^^^^^^^^^^^^
39+
40+
`launch` provides the foundational actions on which other more sophisticated actions may be built.
41+
This is a non-exhaustive list of actions that `launch` may provide:
42+
43+
- :class:`launch.actions.IncludeLaunchDescription`
44+
45+
- This action will include another launch description as if it had been copy-pasted to the location of the include action.
46+
47+
- :class:`launch.actions.SetLaunchConfiguration`
48+
49+
- This action will set a :class:`launch.LaunchConfiguration` to a specified value, creating it if it doesn't already exist.
50+
- These launch configurations can be accessed by any action via a substitution, but are scoped by default.
51+
52+
- :class:`launch.actions.DeclareLaunchDescriptionArgument`
53+
54+
- This action will declare a launch description argument, which can have a name, default value, and documentation.
55+
- The argument will be exposed via a command line option for a root launch description, or as action configurations to the include launch description action for the included launch description.
56+
57+
- :class:`launch.actions.SetEnvironmentVariable`
58+
59+
- This action will set an environment variable by name.
60+
61+
- :class:`launch.actions.GroupAction`
62+
63+
- This action will yield other actions, but can be associated with conditionals (allowing you to use the conditional on the group action rather than on each sub-action individually) and can optionally scope the launch configurations.
64+
65+
- :class:`launch.actions.TimerAction`
66+
67+
- This action will yield other actions after a period of time has passed without being canceled.
68+
69+
- :class:`launch.actions.ExecuteProcess`
70+
71+
- This action will execute a process given its path and arguments, and optionally other things like working directory or environment variables.
72+
73+
- :class:`launch.actions.RegisterEventHandler`
74+
75+
- This action will register an :class:`launch.EventHandler` class, which takes a user defined lambda to handle some event.
76+
- It could be any event, a subset of events, or one specific event.
77+
78+
- :class:`launch.actions.UnregisterEventHandler`
79+
80+
- This action will remove a previously registered event.
81+
82+
- :class:`launch.actions.EmitEvent`
83+
84+
- This action will emit an :class:`launch.Event` based class, causing all registered event handlers that match it to be called.
85+
86+
- :class:`launch.actions.LogInfo`:
87+
88+
- This action will log a user defined message to the logger, other variants (e.g. `LogWarn`) could also exist.
89+
90+
- :class:`launch.actions.RaiseError`
91+
92+
- This action will stop execution of the launch system and provide a user defined error message.
93+
94+
More actions can always be defined via extension, and there may even be additional actions defined by `launch` itself, but they are more situational and would likely be built on top of the above actions anyways.
95+
96+
Base Action
97+
^^^^^^^^^^^
98+
99+
All actions need to inherit from the :class:`launch.Action` base class, so that some common interface is available to the launch system when interacting with actions defined by external packages.
100+
Since the base action class is a first class element in a launch description it also inherits from :class:`launch.LaunchDescriptionEntity`, which is the polymorphic type used when iterating over the elements in a launch description.
101+
102+
Also, the base action has a few features common to all actions, such as some introspection utilities, and the ability to be associated with a single :class:`launch.Conditional`, like the :class:`launch.IfCondition` class or the :class:`launch.UnlessCondition` class.
103+
104+
The action configurations are supplied when the user uses an action and can be used to pass "arguments" to the action in order to influence its behavior, e.g. this is how you would pass the path to the executable in the execute process action.
105+
106+
If an action is associated with a condition, that condition is evaluated to determine if the action is executed or not.
107+
Even if the associated action evaluates to false the action will be available for introspection.
108+
109+
Substitutions
110+
-------------
111+
112+
A substitution is something that cannot, or should not, be evaluated until it's time to execute the launch description that they are used in.
113+
There are many possible variations of a substitution, but here are some of the core ones implemented by `launch` (all of which inherit from :class:`launch.Substitution`):
114+
115+
- :class:`launch.substitutions.Text`
116+
117+
- This substitution simply returns the given string when evaluated.
118+
- It is usually used to wrap literals in the launch description so they can be concatenated with other substitutions.
119+
120+
- :class:`launch.substitutions.PythonExpression`
121+
122+
- This substitution will evaluate a python expression and get the result as a string.
123+
124+
- :class:`launch.substitutions.LaunchConfiguration`
125+
126+
- This substitution gets a launch configuration value, as a string, by name.
127+
128+
- :class:`launch.substitutions.LaunchDescriptionArgument`
129+
130+
- This substitution gets the value of a launch description argument, as a string, by name.
131+
132+
- :class:`launch.substitutions.LocalSubstitution`
133+
134+
- This substitution gets a "local" variable out of the context. This is a mechanism that allows a "parent" action to pass information to sub actions.
135+
- As an example, consider this pseudo code example `OnShutdown(actions=LogInfo(msg=["shutdown due to: ", LocalSubstitution(expression='event.reason')]))`, which assumes that `OnShutdown` will put the shutdown event in the locals before `LogInfo` is visited.
136+
137+
- :class:`launch.substitutions.EnvironmentVariable`
138+
139+
- This substitution gets an environment variable value, as a string, by name.
140+
141+
- :class:`launch.substitutions.FindExecutable`
142+
143+
- This substitution locates the full path to an executable on the PATH if it exists.
144+
145+
The base substitution class provides some common introspection interfaces (which the specific derived substitutions may influence).
146+
147+
The Launch Service
148+
------------------
149+
150+
The launch service is responsible for processing emitted events, dispatching them to event handlers, and executing actions as needed.
151+
The launch service offers three main services:
152+
153+
- include a launch description
154+
155+
- can be called from any thread
156+
157+
- run event loop
158+
- shutdown
159+
160+
- cancels any running actions and event handlers
161+
- then breaks the event loop if running
162+
- can be called from any thread
163+
164+
A typical use case would be:
165+
166+
- create a launch description (programmatically or based on a markup file)
167+
- create a launch service
168+
- include the launch description in the launch service
169+
- add a signal handler for SIGINT that calls shutdown on the launch service
170+
- run the event loop on the launch service
171+
172+
Additionally you could host some SOA (like REST, SOAP, ROS Services, etc...) server in another thread, which would provide a variety of services, all of which would end up including a launch description in the launch service asynchronously or calling shutdown on the launch service asynchronously.
173+
Remember that a launch description can contain actions to register event handlers, emit events, run processes, etc.
174+
So being able to include arbitrary launch descriptions asynchronously is the only feature you require to do most things dynamically while the launch service is running.
175+
176+
Event Handlers
177+
--------------
178+
179+
Event handlers are represented with the :class:`launch.EventHandler` base class.
180+
Event handlers define two main methods, the :meth:`launch.EventHandler.matches` method and the :meth:`launch.EventHandler.handle` method.
181+
The matches method gets the event as input and must return `True` if the event handler matches that event, or `False` otherwise.
182+
The handle method gets the event and launch context as input, and can optionally (in addition to any side effects) return a list of :class:`launch.LaunchDescriptionEntity` objects to be visited by the launch service.
183+
184+
Event handlers do not inherit from :class:`launch.LaunchDescriptionEntity`, but can similarly be "visited" for each event processed by the launch service, seeing if `matches` returns `True` and if so following up with a call to `handle`, then visiting each of the actions returned by `handle`, depth-first.
185+
186+
Extension Points
187+
----------------
188+
189+
In order to allow customization of how `launch` is used in specific domains, extension of the core categories of features is provided.
190+
External Python packages, through extension points, may add:
191+
192+
- new actions
193+
194+
- must directly or indirectly inherit from :class:`launch.Action`
195+
196+
- new events
197+
198+
- must directly or indirectly inherit from :class:`launch.Event`
199+
200+
- new substitutions
201+
202+
- must directly or indirectly inherit from :class:`launch.Substitution`
203+
204+
- kinds of entities in the launch description
205+
206+
- must directly or indirectly inherit from :class:`launch.LaunchDescriptionEntity`
207+
208+
In the future, more traditional extensions (like with `setuptools`' `entry_point` feature) may be available via the launch service, e.g. the ability to include some extra entities and event handlers before the launch description is included.

0 commit comments

Comments
 (0)