def test_valid_substitutions(): """Test the normalize_to_list_of_substitutions() function with valid input.""" class MockSubstitution(Substitution): pass sub = MockSubstitution() norm_sub = normalize_to_list_of_substitutions(sub) assert isinstance(norm_sub, list) assert len(norm_sub) == 1 assert isinstance(norm_sub[0], Substitution) text_sub = 'foo' norm_text_sub = normalize_to_list_of_substitutions(text_sub) assert isinstance(norm_text_sub, list) assert len(norm_text_sub) == 1 assert isinstance(norm_text_sub[0], Substitution) sub_list = ['bar', MockSubstitution()] norm_sub_list = normalize_to_list_of_substitutions(sub_list) assert isinstance(norm_sub_list, list) assert len(norm_sub_list) == 2 assert isinstance(norm_sub_list[0], Substitution) assert isinstance(norm_sub_list[1], Substitution) sub_tuple = (MockSubstitution(), '') norm_sub_tuple = normalize_to_list_of_substitutions(sub_tuple) assert isinstance(norm_sub_tuple, list) assert len(norm_sub_tuple) == 2 assert isinstance(norm_sub_tuple[0], Substitution) assert isinstance(norm_sub_tuple[1], Substitution) norm_sub_empty = normalize_to_list_of_substitutions([]) assert isinstance(norm_sub_empty, list) assert len(norm_sub_empty) == 0
def __init__(self, source_file: launch.SomeSubstitutionsType, param_rewrites: Dict, root_key: Optional[launch.SomeSubstitutionsType] = None, key_rewrites: Optional[Dict] = None, convert_types=False) -> None: super().__init__() """ Construct the substitution :param: source_file the original YAML file to modify :param: param_rewrites mappings to replace :param: root_key if provided, the contents are placed under this key :param: key_rewrites keys of mappings to replace :param: convert_types whether to attempt converting the string to a number or boolean """ from launch.utilities import normalize_to_list_of_substitutions # import here to avoid loop self.__source_file = normalize_to_list_of_substitutions(source_file) self.__param_rewrites = {} self.__key_rewrites = {} self.__convert_types = convert_types self.__root_key = None for key in param_rewrites: self.__param_rewrites[key] = normalize_to_list_of_substitutions( param_rewrites[key]) if key_rewrites is not None: for key in key_rewrites: self.__key_rewrites[key] = normalize_to_list_of_substitutions( key_rewrites[key]) if root_key is not None: self.__root_key = normalize_to_list_of_substitutions(root_key)
def expand_dict(input_dict): expanded_dict = {} for k, v in input_dict.items(): # Key (parameter/group name) can only be a string/Substitutions that evaluates # to a string. expanded_key = perform_substitutions( context, normalize_to_list_of_substitutions(k)) if isinstance(v, dict): # Expand the nested dict. expanded_value = expand_dict(v) elif isinstance(v, list): # Expand each element. expanded_value = [] for e in v: if isinstance(e, list): raise TypeError( 'Nested lists are not supported for parameters: {} found in {}' .format(e, v)) expanded_value.append( perform_substitution_if_applicable(context, e)) # Tuples are treated as Substitution(s) to be concatenated. elif isinstance(v, tuple): for e in v: ensure_argument_type( e, SomeSubstitutionsType_types_tuple, 'parameter dictionary tuple entry', 'Node') expanded_value = perform_substitutions( context, normalize_to_list_of_substitutions(v)) else: expanded_value = perform_substitution_if_applicable( context, v) expanded_dict[expanded_key] = expanded_value return expanded_dict
def _normalize_parameter_array_value( value: SomeParameterValue) -> ParameterValue: """Normalize substitutions while preserving the type if it's not a substitution.""" if not isinstance(value, Sequence): raise TypeError('Value {} must be a sequence'.format(repr(value))) # Figure out what type the list should be has_types = set() # type: Set[type] for subvalue in value: allowed_subtypes = (float, int, str, bool) + SomeSubstitutionsType_types_tuple ensure_argument_type(subvalue, allowed_subtypes, 'subvalue') if isinstance(subvalue, Substitution): has_types.add(Substitution) elif isinstance(subvalue, str): has_types.add(str) elif isinstance(subvalue, bool): has_types.add(bool) elif isinstance(subvalue, int): has_types.add(int) elif isinstance(subvalue, float): has_types.add(float) elif isinstance(subvalue, Sequence): has_types.add(tuple) else: raise RuntimeError('Failed to handle type {}'.format( repr(subvalue))) if {int} == has_types: # everything is an integer make_mypy_happy_int = cast(List[int], value) return tuple(int(e) for e in make_mypy_happy_int) elif has_types in ({float}, {int, float}): # all were floats or ints, so return floats make_mypy_happy_float = cast(List[Union[int, float]], value) return tuple(float(e) for e in make_mypy_happy_float) elif Substitution in has_types and has_types.issubset( {str, Substitution, tuple}): # make a list of substitutions forming a single string return tuple( normalize_to_list_of_substitutions( cast(SomeSubstitutionsType, value))) elif {bool} == has_types: # all where bools return tuple(bool(e) for e in value) else: # Should evaluate to a list of strings # Normalize to a list of lists of substitutions new_value = [] # type: List[SomeSubstitutionsType] for element in value: if isinstance(element, float) or isinstance( element, int) or isinstance(element, bool): new_value.append(str(element)) else: new_value.append(element) return tuple(normalize_to_list_of_substitutions(e) for e in new_value)
def __init__(self, source_file: launch.SomeSubstitutionsType, replacements: Dict) -> None: super().__init__() from launch.utilities import normalize_to_list_of_substitutions # import here to avoid loop self.__source_file = normalize_to_list_of_substitutions(source_file) self.__replacements = {} for key in replacements: self.__replacements[key] = normalize_to_list_of_substitutions( replacements[key])
def normalize_remap_rule(remap_rule: SomeRemapRule) -> RemapRule: """Normalize a remap rule to a specific type.""" ensure_argument_type(remap_rule, (tuple), 'remap_rule') if len(remap_rule) != 2: raise TypeError( 'remap_rule must be a tuple of length 2, got length {}'.format( len(remap_rule))) from_sub = tuple(normalize_to_list_of_substitutions(remap_rule[0])) to_sub = tuple(normalize_to_list_of_substitutions(remap_rule[1])) return from_sub, to_sub
def __init__(self, source_file: launch.SomeSubstitutionsType, rewrites: Dict, convert_types=False) -> None: super().__init__() from launch.utilities import normalize_to_list_of_substitutions # import here to avoid loop self.__source_file = normalize_to_list_of_substitutions(source_file) self.__rewrites = {} self.__convert_types = convert_types for key in rewrites: self.__rewrites[key] = normalize_to_list_of_substitutions( rewrites[key])
def __init__( self, *, package: SomeSubstitutionsType, node_executable: SomeSubstitutionsType, node_name: Optional[SomeSubstitutionsType] = None, node_namespace: SomeSubstitutionsType = '', parameters: Optional[SomeParameters] = None, remappings: Optional[SomeRemapRules] = None, arguments: Optional[Iterable[SomeSubstitutionsType]] = None, ) -> None: """ Construct a SandboxedNode description. The actual node execution is delegated to the sandboxing environment defined by the policy. :param: package is the name of the node's package and is required for resolving the node. :param: node_executable is the name of the node's executable and is required for resolving the node. :param: node_name is an optional name attached to the node when it is launched. Defaults to NONE. :param: node_namespace is an optional namespace attached to the node when it is launched. Defaults to empty string. :param: parameters are the optional runtime configurations for the node, read from a YAML file. Defaults to NONE. :param: remappings are the ordered list of 'to' and 'from' string pairs to be passed to a node as ROS remapping rules. """ self.__package = \ normalize_to_list_of_substitutions(package) self.__node_executable = \ normalize_to_list_of_substitutions(node_executable) self.__node_name = None if node_name is not None: self.__node_name = normalize_to_list_of_substitutions(node_name) self.__node_namespace = None if node_namespace is not None: self.__node_namespace = \ normalize_to_list_of_substitutions(node_namespace) self.__parameters = None if parameters is not None: self.__parameters = normalize_parameters(parameters) self.__remappings = None if remappings is not None: self.__remappings = normalize_remap_rules(remappings)
def __init__( self, package: SomeSubstitutionsType, ) -> None: """Constructor.""" super().__init__() self.__package = normalize_to_list_of_substitutions(package)
def __init__(self, *, sandbox_name: Optional[SomeSubstitutionsType] = None, policy: Optional[Policy] = None, node_descriptions: Optional[List[SandboxedNode]] = None, **kwargs) -> None: """ Initialize the SandboxedNodeContainer. :param: sandbox_name is an optional name assigned to the sandbox environment. :param: policy defines the sandboxing strategy used by the sandbox environment. :param: node_descriptions are the list of nodes to launch inside the sandbox environment. """ super().__init__(**kwargs) self.__sandbox_name = None if sandbox_name is not None: self.__sandbox_name = normalize_to_list_of_substitutions( sandbox_name) self.__node_descriptions = None if node_descriptions is not None: self.__node_descriptions = node_descriptions self.__policy = policy
def __init__(self, name: SomeSubstitutionsType, value: SomeParameterValue, **kwargs) -> None: """Create a SetParameter action.""" super().__init__(**kwargs) normalized_name = normalize_to_list_of_substitutions(name) self.__param_dict = normalize_parameter_dict( {tuple(normalized_name): value})
def __init__(self, *, command: str, arguments: Iterable[SomeSubstitutionsType], actions: Iterable[Action] = [], expected_output: Iterable[str] = [], description: Optional[str] = None): """ Constructor. :param command: `ros2` command to be tested. :param description: description of the test being done. command to be tested. It usually contains the verb and arguments being tested. :param arguments: A list of `SomeSubstitutionsType`, which are passed as arguments to the command. :param actions: A list of actions, which are launched before the `ros2` command. :param expected_output: A list of str, which are checked to be contained in the output of the `ros2` command. """ self.command = command self.arguments = [ normalize_to_list_of_substitutions(arg) for arg in arguments ] self.actions = actions self.expected_output = expected_output self.description = description def describe(some_subs: SomeSubstitutionsType): return ''.join([sub.describe() for sub in some_subs]) if description is None: self.description = 'ros2 {} {}'.format( command, ' '.join([describe(some_subs) for some_subs in self.arguments]))
def execute(self, context: LaunchContext) -> Optional[List[Action]]: """Execute the action.""" # resolve target container node name if is_a_subclass(self.__target_container, ComposableNodeContainer): self.__final_target_container_name = self.__target_container.node_name elif isinstance(self.__target_container, SomeSubstitutionsType_types_tuple): subs = normalize_to_list_of_substitutions(self.__target_container) self.__final_target_container_name = perform_substitutions( context, subs) else: self.__logger.error( 'target container is neither a ComposableNodeContainer nor a SubstitutionType' ) return # Create a client to load nodes in the target container. self.__rclpy_load_node_client = get_ros_node(context).create_client( composition_interfaces.srv.LoadNode, '{}/_container/load_node'.format( self.__final_target_container_name)) # Generate load requests before execute() exits to avoid race with context changing # due to scope change (e.g. if loading nodes from within a GroupAction). load_node_requests = [ get_composable_node_load_request(node_description, context) for node_description in self.__composable_node_descriptions ] context.add_completion_future( context.asyncio_loop.run_in_executor(None, self._load_in_sequence, load_node_requests, context))
def normalize_parameters(parameters: SomeParameters) -> Parameters: """ Normalize the types used to store parameters to substitution types. The passed parameters must be an iterable where each element is a path to a yaml file or a dict. The normalized parameters will have all paths converted to a list of :class:`Substitution`, and dictionaries normalized using :meth:`normalize_parameter_dict`. """ if isinstance(parameters, str) or not isinstance(parameters, Sequence): raise TypeError( 'Expecting list of parameters, got {}'.format(parameters)) normalized_params = [] # type: List[Union[ParameterFile, ParametersDict]] for param in parameters: if isinstance(param, Mapping): normalized_params.append(normalize_parameter_dict(param)) else: # It's a path, normalize to a list of substitutions if isinstance(param, pathlib.Path): param = str(param) ensure_argument_type(param, SomeSubstitutionsType_types_tuple, 'parameters') normalized_params.append( tuple(normalize_to_list_of_substitutions(param))) return tuple(normalized_params)
def __init__(self, name: SomeSubstitutionsType, value: SomeValueType, *, value_type: Optional[AllowedTypesType] = None) -> None: """ Construct a parameter description. :param name: Name of the parameter. :param value: Value of the parameter. :param value_type: Used when `value` is a substitution, to coerce the result. Can be one of: - A scalar type: `int`, `str`, `float`, `bool`. `bool` are written like in `yaml`. Both `1` and `1.` are valid floats. - An uniform list: `List[int]`, `List[str]`, `List[float]`, `List[bool]`. Lists are written like in `yaml`. - `None`, which means that yaml rules will be used. The result of the convertion must be one of the above types, if not `ValueError` is raised. If value is not a substitution and this parameter is provided, it will be used to check `value` type. """ ensure_argument_type(name, SomeSubstitutionsType_types_tuple, 'name') self.__name = normalize_to_list_of_substitutions(name) self.__parameter_value = ParameterValue(value, value_type=value_type) self.__evaluated_parameter_name: Optional[Text] = None self.__evaluated_parameter_rule: Optional[Tuple[ Text, 'EvaluatedParameterValue']] = None
def execute( self, context: LaunchContext ) -> Optional[List[Action]]: """Execute the action.""" # resolve target container node name if is_a_subclass(self.__target_container, ComposableNodeContainer): self.__final_target_container_name = self.__target_container.node_name elif isinstance(self.__target_container, SomeSubstitutionsType_types_tuple): subs = normalize_to_list_of_substitutions(self.__target_container) self.__final_target_container_name = perform_substitutions( context, subs) else: self.__logger.error( 'target container is neither a ComposableNodeContainer nor a SubstitutionType') return # Create a client to load nodes in the target container. self.__rclpy_load_node_client = get_ros_node(context).create_client( composition_interfaces.srv.LoadNode, '{}/_container/load_node'.format( self.__final_target_container_name ) ) context.add_completion_future( context.asyncio_loop.run_in_executor( None, self._load_in_sequence, self.__composable_node_descriptions, context ) )
def __init__(self, param_file: Union[FilePath, SomeSubstitutionsType], *, allow_substs: [bool, SomeSubstitutionsType] = False) -> None: """ Construct a parameter file description. :param param_file: Path to a parameter file. :param allow_subst: Allow substitutions in the parameter file. """ # In Python, __del__ is called even if the constructor throws an # exception. It is possible for ensure_argument_type() below to # throw an exception and try to access these member variables # during cleanup, so make sure to initialize them here. self.__evaluated_param_file: Optional[Path] = None self.__created_tmp_file = False ensure_argument_type( param_file, SomeSubstitutionsType_types_tuple + (os.PathLike, bytes), 'param_file', 'ParameterFile()') ensure_argument_type(allow_substs, bool, 'allow_subst', 'ParameterFile()') self.__param_file: Union[List[Substitution], FilePath] = param_file if isinstance(param_file, SomeSubstitutionsType_types_tuple): self.__param_file = normalize_to_list_of_substitutions(param_file) self.__allow_substs = normalize_typed_substitution(allow_substs, data_type=bool) self.__evaluated_allow_substs: Optional[bool] = None
def __init__( self, namespace: SomeSubstitutionsType, **kwargs ) -> None: """Create a PushRosNamespace action.""" super().__init__(**kwargs) self.__namespace = normalize_to_list_of_substitutions(namespace)
def __init__( self, namespace: SomeSubstitutionsType, **kwargs ) -> None: """Constructor.""" super().__init__(**kwargs) self.__namespace = normalize_to_list_of_substitutions(namespace)
def test_invalid_substitutions(): """Test the normalize_to_list_of_substitutions() function with invalid input.""" with pytest.raises(TypeError): normalize_to_list_of_substitutions(None) with pytest.raises(TypeError): normalize_to_list_of_substitutions(1) with pytest.raises(TypeError): normalize_to_list_of_substitutions([1.4142, 'bar']) with pytest.raises(TypeError): normalize_to_list_of_substitutions(['foo', ['bar']])
def __init__( self, *, package: SomeSubstitutionsType, node_plugin: SomeSubstitutionsType, node_name: Optional[SomeSubstitutionsType] = None, node_namespace: Optional[SomeSubstitutionsType] = None, parameters: Optional[SomeParameters] = None, remappings: Optional[SomeRemapRules] = None, extra_arguments: Optional[SomeParameters] = None, ) -> None: """ Initialize a ComposableNode description. :param package: name of the ROS package the node plugin lives in :param node_plugin: name of the plugin to be loaded :param node_name: name the node should have :param node_namespace: namespace the node should create topics/services/etc in :param parameters: list of either paths to yaml files or dictionaries of parameters :param remappings: list of from/to pairs for remapping names :param extra_arguments: container specific arguments to be passed to the loaded node """ self.__package = normalize_to_list_of_substitutions(package) self.__node_plugin = normalize_to_list_of_substitutions(node_plugin) self.__node_name = None # type: Optional[List[Substitution]] if node_name is not None: self.__node_name = normalize_to_list_of_substitutions(node_name) self.__node_namespace = None # type: Optional[List[Substitution]] if node_namespace is not None: self.__node_namespace = normalize_to_list_of_substitutions( node_namespace) self.__parameters = None # type: Optional[Parameters] if parameters is not None: self.__parameters = normalize_parameters(parameters) self.__remappings = None # type: Optional[RemapRules] if remappings: self.__remappings = normalize_remap_rules(remappings) self.__extra_arguments = None # type: Optional[Parameters] if extra_arguments: self.__extra_arguments = normalize_parameters(extra_arguments)
def _perform_substitutions(self, context: LaunchContext) -> None: try: if self.__substitutions_performed: # This function may have already been called by a subclass' `execute`, for example. return self.__substitutions_performed = True if self.__node_name is not None: self.__expanded_node_name = perform_substitutions( context, normalize_to_list_of_substitutions(self.__node_name)) validate_node_name(self.__node_name) self.__expanded_node_name.lstrip('/') if self.__node_namespace is not None: self.__expanded_node_namespace = perform_substitutions( context, normalize_to_list_of_substitutions(self.__node_namespace)) if not self.__expanded_node_namespace.startswith('/'): self.__expanded_node_namespace = '/' + self.__expanded_node_namespace validate_namespace(self.__expanded_node_namespace) except Exception: print( "Error while expanding or validating node name or namespace for '{}':" .format( 'package={}, node_executable={}, name={}, namespace={}'. format( self.__package, self.__node_executable, self.__node_name, self.__node_namespace, ))) raise self.__final_node_name = '' if self.__expanded_node_namespace not in ['', '/']: self.__final_node_name += self.__expanded_node_namespace self.__final_node_name += '/' + self.__expanded_node_name # expand remappings too if self.__remappings is not None: self.__expanded_remappings = {} for k, v in self.__remappings: key = perform_substitutions( context, normalize_to_list_of_substitutions(k)) value = perform_substitutions( context, normalize_to_list_of_substitutions(v)) self.__expanded_remappings[key] = value
def perform_substitution_if_applicable(context, var): if isinstance(var, (int, float, str)): # No substitution necessary. return var if isinstance(var, Substitution): return perform_substitutions( context, normalize_to_list_of_substitutions(var)) if isinstance(var, tuple): try: return perform_substitutions( context, normalize_to_list_of_substitutions(var)) except TypeError: raise TypeError( 'Invalid element received in parameters dictionary ' '(not all tuple elements are Substitutions): {}'. format(var)) else: raise TypeError( 'Unsupported type received in parameters dictionary: {}' .format(type(var)))
def __init__(self, source_file: launch.SomeSubstitutionsType, node_name: Text) -> None: super().__init__() """ Construct the substitution :param: source_file the parameter YAML file :param: node_name the name of the node to check """ from launch.utilities import normalize_to_list_of_substitutions # import here to avoid loop self.__source_file = normalize_to_list_of_substitutions(source_file) self.__node_name = node_name
def execute(self, context: LaunchContext): continue_after_fail = self.__continue_after_fail if not isinstance(continue_after_fail, bool): continue_after_fail = perform_substitutions( context, normalize_to_list_of_substitutions( continue_after_fail)).lower() if continue_after_fail in ['true', 'on', '1']: continue_after_fail = True elif continue_after_fail in ['false', 'off', '1']: continue_after_fail = False else: raise ValueError( 'continue_after_fail should be a boolean, got {}'.format( continue_after_fail)) on_first_action_exited = OnProcessExit( target_action=self.__actions[0], on_exit=lambda event, context: ([InOrderGroup(self.__actions[1:])] if event.exitcode == 0 or continue_after_fail else [])) return [ self.__actions[0], RegisterEventHandler(on_first_action_exited) ]
def __init__(self, param_file: Union[FilePath, SomeSubstitutionsType], *, allow_substs: [bool, SomeSubstitutionsType] = False) -> None: """ Construct a parameter file description. :param param_file: Path to a parameter file. :param allow_subst: Allow substitutions in the parameter file. """ ensure_argument_type( param_file, SomeSubstitutionsType_types_tuple + (os.PathLike, bytes), 'param_file', 'ParameterFile()') ensure_argument_type(allow_substs, bool, 'allow_subst', 'ParameterFile()') self.__param_file: Union[List[Substitution], FilePath] = param_file if isinstance(param_file, SomeSubstitutionsType_types_tuple): self.__param_file = normalize_to_list_of_substitutions(param_file) self.__allow_substs = normalize_typed_substitution(allow_substs, data_type=bool) self.__evaluated_allow_substs: Optional[bool] = None self.__evaluated_param_file: Optional[Path] = None self.__created_tmp_file = False
def execute(self, context: LaunchContext) -> Optional[List[Action]]: """Execute the action.""" # resolve target container node name if is_a_subclass(self.__target_container, ComposableNodeContainer): self.__final_target_container_name = self.__target_container.node_name elif isinstance(self.__target_container, SomeSubstitutionsType_types_tuple): subs = normalize_to_list_of_substitutions(self.__target_container) self.__final_target_container_name = perform_substitutions( context, subs) else: self.__logger.error( 'target container is neither a ComposableNodeContainer nor a SubstitutionType' ) return # Create a client to load nodes in the target container. self.__rclpy_load_node_client = context.locals.launch_ros_node.create_client( composition_interfaces.srv.LoadNode, '{}/_container/load_node'.format( self.__final_target_container_name)) # Assume user has configured `LoadComposableNodes` to happen after container action self._load_in_sequence(self.__composable_node_descriptions, context)
def _perform_substitutions(self, context: LaunchContext) -> None: # Here to avoid cyclic import from ..descriptions import Parameter try: if self.__substitutions_performed: # This function may have already been called by a subclass' `execute`, for example. return self.__substitutions_performed = True if self.__node_name is not None: self.__expanded_node_name = perform_substitutions( context, normalize_to_list_of_substitutions(self.__node_name)) validate_node_name(self.__expanded_node_name) self.__expanded_node_name.lstrip('/') expanded_node_namespace: Optional[Text] = None if self.__node_namespace is not None: expanded_node_namespace = perform_substitutions( context, normalize_to_list_of_substitutions(self.__node_namespace)) base_ns = context.launch_configurations.get('ros_namespace', None) expanded_node_namespace = make_namespace_absolute( prefix_namespace(base_ns, expanded_node_namespace)) if expanded_node_namespace is not None: self.__expanded_node_namespace = expanded_node_namespace cmd_extension = ['-r', LocalSubstitution("ros_specific_arguments['ns']")] self.cmd.extend([normalize_to_list_of_substitutions(x) for x in cmd_extension]) validate_namespace(self.__expanded_node_namespace) except Exception: self.__logger.error( "Error while expanding or validating node name or namespace for '{}':" .format('package={}, executable={}, name={}, namespace={}'.format( self.__package, self.__node_executable, self.__node_name, self.__node_namespace, )) ) raise self.__final_node_name = prefix_namespace( self.__expanded_node_namespace, self.__expanded_node_name) # expand global parameters first, # so they can be overriden with specific parameters of this Node global_params = context.launch_configurations.get('ros_params', None) if global_params is not None or self.__parameters is not None: self.__expanded_parameter_arguments = [] if global_params is not None: param_file_path = self._create_params_file_from_dict(global_params) self.__expanded_parameter_arguments.append((param_file_path, True)) cmd_extension = ['--params-file', f'{param_file_path}'] self.cmd.extend([normalize_to_list_of_substitutions(x) for x in cmd_extension]) assert os.path.isfile(param_file_path) # expand parameters too if self.__parameters is not None: evaluated_parameters = evaluate_parameters(context, self.__parameters) for params in evaluated_parameters: is_file = False if isinstance(params, dict): param_argument = self._create_params_file_from_dict(params) is_file = True assert os.path.isfile(param_argument) elif isinstance(params, pathlib.Path): param_argument = str(params) is_file = True elif isinstance(params, Parameter): param_argument = self._get_parameter_rule(params, context) else: raise RuntimeError('invalid normalized parameters {}'.format(repr(params))) if is_file and not os.path.isfile(param_argument): self.__logger.warning( 'Parameter file path is not a file: {}'.format(param_argument), ) continue self.__expanded_parameter_arguments.append((param_argument, is_file)) cmd_extension = ['--params-file' if is_file else '-p', f'{param_argument}'] self.cmd.extend([normalize_to_list_of_substitutions(x) for x in cmd_extension]) # expand remappings too global_remaps = context.launch_configurations.get('ros_remaps', None) if global_remaps or self.__remappings: self.__expanded_remappings = [] if global_remaps: self.__expanded_remappings.extend(global_remaps) if self.__remappings: self.__expanded_remappings.extend([ (perform_substitutions(context, src), perform_substitutions(context, dst)) for src, dst in self.__remappings ]) if self.__expanded_remappings: cmd_extension = [] for src, dst in self.__expanded_remappings: cmd_extension.extend(['-r', f'{src}:={dst}']) self.cmd.extend([normalize_to_list_of_substitutions(x) for x in cmd_extension])
def _perform_substitutions(self, context: LaunchContext) -> None: try: if self.__substitutions_performed: # This function may have already been called by a subclass' `execute`, for example. return self.__substitutions_performed = True if self.__node_name is not None: self.__expanded_node_name = perform_substitutions( context, normalize_to_list_of_substitutions(self.__node_name)) validate_node_name(self.__expanded_node_name) self.__expanded_node_name.lstrip('/') self.__expanded_node_namespace = perform_substitutions( context, normalize_to_list_of_substitutions(self.__node_namespace)) if not self.__expanded_node_namespace.startswith('/'): base_ns = context.launch_configurations.get( 'ros_namespace', '') self.__expanded_node_namespace = ( base_ns + '/' + self.__expanded_node_namespace).rstrip('/') if (self.__expanded_node_namespace != '' and not self.__expanded_node_namespace.startswith('/')): self.__expanded_node_namespace = '/' + self.__expanded_node_namespace if self.__expanded_node_namespace != '': cmd_extension = [ '-r', LocalSubstitution("ros_specific_arguments['ns']") ] self.cmd.extend([ normalize_to_list_of_substitutions(x) for x in cmd_extension ]) validate_namespace(self.__expanded_node_namespace) except Exception: self.__logger.error( "Error while expanding or validating node name or namespace for '{}':" .format( 'package={}, node_executable={}, name={}, namespace={}'. format( self.__package, self.__node_executable, self.__node_name, self.__node_namespace, ))) raise self.__final_node_name = '' if self.__expanded_node_namespace not in ['', '/']: self.__final_node_name += self.__expanded_node_namespace self.__final_node_name += '/' + self.__expanded_node_name # expand parameters too if self.__parameters is not None: self.__expanded_parameter_files = [] evaluated_parameters = evaluate_parameters(context, self.__parameters) for params in evaluated_parameters: if isinstance(params, dict): param_file_path = self._create_params_file_from_dict( params) elif isinstance(params, pathlib.Path): param_file_path = str(params) else: raise RuntimeError( 'invalid normalized parameters {}'.format( repr(params))) if not os.path.isfile(param_file_path): self.__logger.warning( 'Parameter file path is not a file: {}'.format( param_file_path), ) # Don't skip adding the file to the parameter list since space has been # reserved for it in the ros_specific_arguments. self.__expanded_parameter_files.append(param_file_path) # expand remappings too if self.__remappings is not None: self.__expanded_remappings = [] for k, v in self.__remappings: key = perform_substitutions( context, normalize_to_list_of_substitutions(k)) value = perform_substitutions( context, normalize_to_list_of_substitutions(v)) self.__expanded_remappings.append((key, value))
def __init__(self, filename: SomeSubstitutionsType, **kwargs) -> None: """Create a SetParameterFromFile action.""" super().__init__(**kwargs) self._input_file = normalize_to_list_of_substitutions(filename)