Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion launch/launch/actions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from .for_loop import ForLoop
from .group_action import GroupAction
from .include_launch_description import IncludeLaunchDescription
from .include_scoped_launch_description import ScopedIncludeLaunchDescription
from .log import Log
from .log import LogDebug
from .log import LogError
Expand All @@ -35,13 +36,16 @@
from .push_environment import PushEnvironment
from .push_launch_configurations import PushLaunchConfigurations
from .register_event_handler import RegisterEventHandler
from .register_global_launch_configuration import RegisterGlobalLaunchConfiguration
from .reset_environment import ResetEnvironment
from .reset_launch_configurations import ResetLaunchConfigurations
from .set_environment_variable import SetEnvironmentVariable
from .set_global_launch_configuration import SetGlobalLaunchConfiguration
from .set_launch_configuration import SetLaunchConfiguration
from .shutdown_action import Shutdown
from .timer_action import TimerAction
from .unregister_event_handler import UnregisterEventHandler
from .unregister_global_launch_configuration import UnregisterGlobalLaunchConfiguration
from .unset_environment_variable import UnsetEnvironmentVariable
from .unset_launch_configuration import UnsetLaunchConfiguration

Expand All @@ -66,14 +70,18 @@
'PopLaunchConfigurations',
'PushEnvironment',
'PushLaunchConfigurations',
'RegisterEventHandler',
'RegisterGlobalLaunchConfiguration',
'ResetEnvironment',
'ResetLaunchConfigurations',
'RegisterEventHandler',
'ScopedIncludeLaunchDescription',
'SetEnvironmentVariable',
'SetGlobalLaunchConfiguration',
'SetLaunchConfiguration',
'Shutdown',
'TimerAction',
'UnregisterEventHandler',
'UnregisterGlobalLaunchConfiguration',
'UnsetEnvironmentVariable',
'UnsetLaunchConfiguration',
]
101 changes: 101 additions & 0 deletions launch/launch/actions/include_scoped_launch_description.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Copyright 2025 Open Source Robotics Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Module for the ScopedIncludeLaunchDescription action."""

# from typing import override # Available starting from Python3.12
from typing import Dict
from typing import List
from typing import Text

from .include_launch_description import IncludeLaunchDescription
from .pop_environment import PopEnvironment
from .pop_launch_configurations import PopLaunchConfigurations
from .push_environment import PushEnvironment
from .push_launch_configurations import PushLaunchConfigurations
from .reset_environment import ResetEnvironment
from .reset_launch_configurations import ResetLaunchConfigurations
from .set_launch_configuration import SetLaunchConfiguration
from ..frontend import expose_action
from ..launch_context import LaunchContext
from ..launch_description_entity import LaunchDescriptionEntity
from ..some_substitutions_type import SomeSubstitutionsType
from ..utilities import normalize_to_list_of_substitutions
from ..utilities import perform_substitutions


@expose_action('scoped_include')
class ScopedIncludeLaunchDescription(IncludeLaunchDescription):
# TODO(SuperJappie08) Propper Documentation

# NOTE(SuperJappie08) __init__ is not required since the function signature will be the same
# However maybe it is interresting for documentation purposes

def get_sub_entities(self):
"""Get subentities."""
# ret = super().get_sub_entities()
# TODO(SuperJappie08)? Do these internals need to be hidden?
# print(self.launch_arguments)
return [
PushLaunchConfigurations(),
PushEnvironment(),
ResetEnvironment(),
# NOTE(SuperJappie08) Need weird remap, since AnySubstitution type can be a List which
# is not Hashable.
# ResetLaunchConfigurations({k: v for k, v in self.launch_arguments}),
# *ret,
ResetLaunchConfigurations(),
*[SetLaunchConfiguration(k, v) for k, v in self.launch_arguments],
*super().get_sub_entities(),
PopEnvironment(),
PopLaunchConfigurations(),
]

def execute(self, context: LaunchContext) -> List[LaunchDescriptionEntity]:
"""Execute the action."""
evaluated_configurations: Dict[SomeSubstitutionsType, SomeSubstitutionsType] = {}

for name in getattr(context.locals, 'globals', set()):
expanded_name = perform_substitutions(
context,
normalize_to_list_of_substitutions(name)
)

if expanded_name in context.launch_configurations:
evaluated_configurations[expanded_name] = \
context.launch_configurations[expanded_name]

for k, v in self.launch_arguments:
# Perform substitutions, since the required launch configurations might not be
# available in the inner scope.
evaluated_k = perform_substitutions(context, normalize_to_list_of_substitutions(k))
evaluated_v = perform_substitutions(context, normalize_to_list_of_substitutions(v))
evaluated_configurations[evaluated_k] = evaluated_v

return [
PushLaunchConfigurations(),
PushEnvironment(),
ResetEnvironment(),
ResetLaunchConfigurations(evaluated_configurations),
IncludeLaunchDescription(
launch_description_source=self.launch_description_source,
launch_arguments=self.launch_arguments,
condition=self.condition),
PopEnvironment(),
PopLaunchConfigurations()
]

def __repr__(self) -> Text:
"""Return a description of this ScopedIncludeLaunchDescription as a string."""
return f'ScopedIncludeLaunchDescription({self.launch_description_source.location})'
59 changes: 59 additions & 0 deletions launch/launch/actions/register_global_launch_configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Copyright 2025 Open Source Robotics Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Module for the RegisterGlobalLaunchConfiguration."""

from typing import List

from ..action import Action
from ..frontend import Entity
from ..frontend import expose_action
from ..frontend import Parser
from ..launch_context import LaunchContext
from ..some_substitutions_type import SomeSubstitutionsType
from ..substitution import Substitution
from ..utilities import normalize_to_list_of_substitutions
from ..utilities import register_global


@expose_action('register-global')
@expose_action('register_global')
class RegisterGlobalLaunchConfiguration(Action):
"""
Action that add a launch configuration to the globals set by name.

This allows the configuration to be overscoped when including a scoped launch description.
"""

def __init__(self, name: SomeSubstitutionsType, **kwargs) -> None:
"""Create a RegisterGlobalLaunchConfiguration action."""
super().__init__(**kwargs)
self.__name = normalize_to_list_of_substitutions(name)

@classmethod
def parse(cls, entity: Entity, parser: Parser):
"""Return `RegisterGlobalLaunchConfiguration` action and kwargs for constructing it."""
name = parser.parse_substitution(entity.get_attr('name'))
_, kwargs = super().parse(entity, parser)
kwargs['name'] = name
return cls, kwargs

@property
def name(self) -> List[Substitution]:
"""Getter for self.__name."""
return self.__name

def execute(self, context: LaunchContext):
"""Execute the action."""
register_global(context, self.name)
47 changes: 47 additions & 0 deletions launch/launch/actions/set_global_launch_configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright 2025 Open Source Robotics Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Module for the SetGlobalLaunchConfiguration action."""

from .set_launch_configuration import SetLaunchConfiguration
from ..frontend import expose_action
from ..launch_context import LaunchContext
from ..utilities import register_global


@expose_action('global')
class SetGlobalLaunchConfiguration(SetLaunchConfiguration):
"""
Action that sets a global launch configuration by name.

Launch configurations can be accessed by the LaunchConfiguration
substitution and are accessible after being set, even in included
LaunchDescription's, but can be scoped with groups.

A global launch configuration is registered in the globals group
to allow to in scoped included launch descriptions.
"""

def __init__(
self,
**kwargs
) -> None:
"""Create a SetGlobalLaunchConfiguration action."""
super().__init__(**kwargs)

def execute(self, context: LaunchContext):
"""Execute the action."""
register_global(context, self.name)

super().execute(context)
55 changes: 55 additions & 0 deletions launch/launch/actions/unregister_global_launch_configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Copyright 2025 Open Source Robotics Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Module for the UnregisterGlobalLaunchConfiguration."""

from typing import List

from ..action import Action
from ..frontend import Entity
from ..frontend import expose_action
from ..frontend import Parser
from ..launch_context import LaunchContext
from ..some_substitutions_type import SomeSubstitutionsType
from ..substitution import Substitution
from ..utilities import normalize_to_list_of_substitutions
from ..utilities import unregister_global


@expose_action('unregister-global')
@expose_action('unregister_global')
class UnregisterGlobalLaunchConfiguration(Action):
"""Action that add a launch configuration to the globals set by name."""

def __init__(self, name: SomeSubstitutionsType, **kwargs) -> None:
"""Create a UnregisterGlobalLaunchConfiguration action."""
super().__init__(**kwargs)
self.__name = normalize_to_list_of_substitutions(name)

@classmethod
def parse(cls, entity: Entity, parser: Parser):
"""Return `UnregisterGlobalLaunchConfiguration` action and kwargs for constructing it."""
name = parser.parse_substitution(entity.get_attr('name'))
_, kwargs = super().parse(entity, parser)
kwargs['name'] = name
return cls, kwargs

@property
def name(self) -> List[Substitution]:
"""Getter for self.__name."""
return self.__name

def execute(self, context: LaunchContext):
"""Execute the action."""
unregister_global(context, self.name)
3 changes: 3 additions & 0 deletions launch/launch/utilities/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from .class_tools_impl import is_a, is_a_subclass, isclassinstance
from .ensure_argument_type_impl import ensure_argument_type
from .global_launch_configurations import register_global, unregister_global
from .normalize_to_list_of_entities_impl import normalize_to_list_of_entities
from .normalize_to_list_of_substitutions_impl import normalize_to_list_of_substitutions
from .perform_substitutions_impl import perform_substitutions
Expand All @@ -32,4 +33,6 @@
'normalize_to_list_of_substitutions',
'normalize_to_list_of_entities',
'visit_all_entities_and_collect_futures',
'register_global',
'unregister_global'
]
39 changes: 39 additions & 0 deletions launch/launch/utilities/global_launch_configurations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Copyright 2025 Open Source Robotics Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Set
from typing import Text

from .normalize_to_list_of_substitutions_impl import normalize_to_list_of_substitutions
from .perform_substitutions_impl import perform_substitutions
from ..launch_context import LaunchContext
from ..some_substitutions_type import SomeSubstitutionsType


def register_global(context: LaunchContext, name: SomeSubstitutionsType) -> None:
"""
Register a launch configuration to the globals set.

This allows it to be accessible in when scoped including a launch description.
"""
globals_set: Set[Text] = getattr(context.locals, 'globals', {'globals'})
globals_set.add(perform_substitutions(context, normalize_to_list_of_substitutions(name)))
context.extend_locals({'globals': globals_set})


def unregister_global(context: LaunchContext, name: SomeSubstitutionsType) -> None:
"""Unregisters a launch configuration to the globals set."""
globals_set: Set[Text] = getattr(context.locals, 'globals', {'globals'})
globals_set.discard(perform_substitutions(context, normalize_to_list_of_substitutions(name)))
context.extend_locals({'globals': globals_set})
Loading