Architecture of launch
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. 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.
Launch Entities and Launch Descriptions
The main object in launch is the launch.LaunchDescriptionEntity
, from which other entities that are “launched” inherit.
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.
A launch description entity has its launch.LaunchDescriptionEntity.visit()
method called during “launching”, and has any of the “describe” methods called during “introspection”.
It may also provide a asyncio.Future
with the launch.LaunchDescriptionEntity.get_asyncio_future()
method, if it has on-going asynchronous activity after returning from visit.
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 launch.LaunchDescription
is provided to start the launch process.
The launch.LaunchDescription
class encapsulates the intent of the user as a list of discrete launch.Action
’s, which are also derived from launch.LaunchDescriptionEntity
.
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.
Additionally, launch descriptions, and the actions that they contain, can have references to launch.Substitution
’s within them.
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.
Launch descriptions, and the actions contained therein, can either be introspected directly or launched by a launch.LaunchService
.
A launch service is a long running activity that handles the event loop and dispatches actions.
Actions
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.
Actions can have direct side effects (e.g. run a process or set a configuration variable) and as well they can yield additional actions. The latter can be used to create “syntactic sugar” actions which simply yield more verbose actions.
Actions may also have arguments, which can affect the behavior of the actions.
These arguments are where launch.Substitution
’s can be used to provide more flexibility when describing reusable launch descriptions.
Basic Actions
launch provides the foundational actions on which other more sophisticated actions may be built. This is a non-exhaustive list of actions that launch may provide:
launch.actions.IncludeLaunchDescription
This action will include another launch description as if it had been copy-pasted to the location of the include action.
launch.actions.SetLaunchConfiguration
This action will set a
launch.LaunchConfiguration
to a specified value, creating it if it doesn’t already exist.These launch configurations can be accessed by any action via a substitution, but are scoped by default.
launch.actions.DeclareLaunchArgument
This action will declare a launch description argument, which can have a name, default value, and documentation.
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.
launch.actions.SetEnvironmentVariable
This action will set an environment variable by name.
launch.actions.AppendEnvironmentVariable
This action will set an environment variable by name if it does not exist, otherwise it appends to the existing value using a platform-specific separator.
There is also an option to prepend instead of appending and to provide a custom separator.
launch.actions.GroupAction
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.
launch.actions.TimerAction
This action will yield other actions after a period of time has passed without being canceled.
launch.actions.ExecuteProcess
This action will execute a process given its path and arguments, and optionally other things like working directory or environment variables.
launch.actions.RegisterEventHandler
This action will register an
launch.EventHandler
class, which takes a user defined lambda to handle some event.It could be any event, a subset of events, or one specific event.
launch.actions.UnregisterEventHandler
This action will remove a previously registered event.
launch.actions.EmitEvent
This action will emit an
launch.Event
based class, causing all registered event handlers that match it to be called.
launch.actions.LogInfo
:This action will log a user defined message to the logger, other variants (e.g. LogWarn) could also exist.
launch.actions.RaiseError
This action will stop execution of the launch system and provide a user defined error message.
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.
Base Action
All actions need to inherit from the launch.Action
base class, so that some common interface is available to the launch system when interacting with actions defined by external packages.
Since the base action class is a first class element in a launch description it also inherits from launch.LaunchDescriptionEntity
, which is the polymorphic type used when iterating over the elements in a launch description.
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 launch.Condition
, like the launch.IfCondition
class or the launch.UnlessCondition
class.
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.
If an action is associated with a condition, that condition is evaluated to determine if the action is executed or not. Even if the associated action evaluates to false the action will be available for introspection.
Substitutions
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.
There are many possible variations of a substitution, but here are some of the core ones implemented by launch (all of which inherit from launch.Substitution
):
launch.substitutions.Text
This substitution simply returns the given string when evaluated.
It is usually used to wrap literals in the launch description so they can be concatenated with other substitutions.
launch.substitutions.PythonExpression
This substitution will evaluate a python expression and get the result as a string.
You may pass a list of Python modules to the constructor to allow the use of those modules in the evaluated expression.
launch.substitutions.LaunchConfiguration
This substitution gets a launch configuration value, as a string, by name.
launch.substitutions.IfElseSubstitution
This substitution takes a substitution, and if it evaluates to true, then the result is the if_value, else the result is the else_value.
launch.substitutions.LaunchDescriptionArgument
This substitution gets the value of a launch description argument, as a string, by name.
launch.substitutions.LocalSubstitution
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.
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.
launch.substitutions.EnvironmentVariable
This substitution gets an environment variable value, as a string, by name.
launch.substitutions.FindExecutable
This substitution locates the full path to an executable on the PATH if it exists.
The base substitution class provides some common introspection interfaces (which the specific derived substitutions may influence).
The Launch Service
The launch service is responsible for processing emitted events, dispatching them to event handlers, and executing actions as needed. The launch service offers three main services:
include a launch description
can be called from any thread
run event loop
shutdown
cancels any running actions and event handlers
then breaks the event loop if running
can be called from any thread
A typical use case would be:
create a launch description (programmatically or based on a markup file)
create a launch service
include the launch description in the launch service
add a signal handler for SIGINT that calls shutdown on the launch service
run the event loop on the launch service
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. Remember that a launch description can contain actions to register event handlers, emit events, run processes, etc. 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.
Event Handlers
Event handlers are represented with the launch.EventHandler
base class.
Event handlers define two main methods, the launch.EventHandler.matches()
method and the launch.EventHandler.handle()
method.
The matches method gets the event as input and must return True if the event handler matches that event, or False otherwise.
The handle method gets the event and launch context as input, and can optionally (in addition to any side effects) return a list of launch.LaunchDescriptionEntity
objects to be visited by the launch service.
Event handlers do not inherit from 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.
Extension Points
In order to allow customization of how launch is used in specific domains, extension of the core categories of features is provided. External Python packages, through extension points, may add:
new actions
must directly or indirectly inherit from
launch.Action
new events
must directly or indirectly inherit from
launch.Event
new substitutions
must directly or indirectly inherit from
launch.Substitution
kinds of entities in the launch description
must directly or indirectly inherit from
launch.LaunchDescriptionEntity
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.