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 setuptoolsentry_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.