ros / ros_comm

ROS communications-related packages, including core client libraries (roscpp, rospy, roslisp) and graph introspection tools (rostopic, rosnode, rosservice, rosparam).
http://wiki.ros.org/ros_comm
745 stars 911 forks source link

[roslaunch] raise `ArgException` in `_arg_tag` to avoid unexpected error in `roslaunch.config.load_config_default` #2371

Open knorth55 opened 2 months ago

knorth55 commented 2 months ago

this PR fix a bug in roslaunch.config.load_config_default

Overview

When we have one arg with no default value, and another arg defined by the former arg, _arg_tag in roslaunch.xmlloader.XmlLoader raises XmlParseException, even when ignore_unset_args is True.

this PR change to raise ArgException as expected not to raise the error when ignore_unset_args is True. this function is used in roslaunch_add_file_check in cmake, and test always fail when we have this kind of launches.

How to reproduce the error

launch file

<launch>
  <arg name="input1"/>
  <arg name="input2" value="$(arg input1)"/>

  <node name="usb_cam_node" pkg="usb_cam" type="usb_cam_node">
    <remap from="~input" to="$(arg input2)"/>
  </node>
</launch>

ipython

In [1]: from rosmaster import DEFAULT_MASTER_PORT

In [2]: import roslaunch

In [3]: roslaunch.config.load_config_default(['./sample.launch'], DEFAULT_MASTER_PORT, verbose=False, ignore_unset_args=True)
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
File /opt/ros/noetic/lib/python3/dist-packages/roslaunch/substitution_args.py:278, in _eval_arg(name, args)
    277 try:
--> 278     return args[name]
    279 except KeyError:

KeyError: 'input1'

During handling of the above exception, another exception occurred:

ArgException                              Traceback (most recent call last)
File /opt/ros/noetic/lib/python3/dist-packages/roslaunch/xmlloader.py:297, in XmlLoader._arg_tag(self, tag, context, ros_config, verbose)
    296 (name,) = self.reqd_attrs(tag, context, ('name',))
--> 297 value, default, doc = self.opt_attrs(tag, context, ('value', 'default', 'doc'))
    299 if value is not None and default is not None:

File /opt/ros/noetic/lib/python3/dist-packages/roslaunch/xmlloader.py:208, in XmlLoader.opt_attrs(self, tag, context, attrs)
    207         return None
--> 208 return [self.resolve_args(tag_value(tag,a), context) for a in attrs]

File /opt/ros/noetic/lib/python3/dist-packages/roslaunch/xmlloader.py:208, in <listcomp>(.0)
    207         return None
--> 208 return [self.resolve_args(tag_value(tag,a), context) for a in attrs]

File /opt/ros/noetic/lib/python3/dist-packages/roslaunch/xmlloader.py:189, in XmlLoader.resolve_args(self, args, context)
    188     context.resolve_dict['filename'] = context.filename
--> 189     return substitution_args.resolve_args(args, context=context.resolve_dict, resolve_anon=self.resolve_anon)
    190 else:

File /opt/ros/noetic/lib/python3/dist-packages/roslaunch/substitution_args.py:387, in resolve_args(arg_str, context, resolve_anon, filename)
    380 commands = {
    381     'env': _env,
    382     'optenv': _optenv,
   (...)
    385     'arg': _arg,
    386 }
--> 387 resolved = _resolve_args(arg_str, context, resolve_anon, commands)
    388 # then resolve 'find' as it requires the subsequent path to be expanded already

File /opt/ros/noetic/lib/python3/dist-packages/roslaunch/substitution_args.py:405, in _resolve_args(arg_str, context, resolve_anon, commands)
    404     if command in commands:
--> 405         resolved = commands[command](resolved, a, args, context)
    406 return resolved

File /opt/ros/noetic/lib/python3/dist-packages/roslaunch/substitution_args.py:296, in _arg(resolved, a, args, context)
    295     context['arg'] = {}
--> 296 return resolved.replace("$(%s)" % a, _eval_arg(name=args[0], args=context['arg']))

File /opt/ros/noetic/lib/python3/dist-packages/roslaunch/substitution_args.py:280, in _eval_arg(name, args)
    279 except KeyError:
--> 280     raise ArgException(name)

ArgException: input1

During handling of the above exception, another exception occurred:

XmlParseException                         Traceback (most recent call last)
File /opt/ros/noetic/lib/python3/dist-packages/roslaunch/config.py:461, in load_config_default(roslaunch_files, port, roslaunch_strs, loader, verbose, assign_machines, ignore_unset_args)
    460     logger.info('loading config file %s'%f)
--> 461     loader.load(f, config, argv=args, verbose=verbose)
    462 except roslaunch.xmlloader.XmlParseException as e:

File /opt/ros/noetic/lib/python3/dist-packages/roslaunch/xmlloader.py:763, in XmlLoader.load(self, filename, ros_config, core, argv, verbose)
    762     ros_config.add_roslaunch_file(filename)
--> 763     self._load_launch(launch, ros_config, is_core=core, filename=filename, argv=argv, verbose=verbose)
    764 except ArgException as e:

File /opt/ros/noetic/lib/python3/dist-packages/roslaunch/xmlloader.py:735, in XmlLoader._load_launch(self, launch, ros_config, is_core, filename, argv, verbose)
    734     print("WARNING: ignoring defunct <master /> tag", file=sys.stderr)
--> 735 self._recurse_load(ros_config, launch.childNodes, self.root_context, None, is_core, verbose)

File /opt/ros/noetic/lib/python3/dist-packages/roslaunch/xmlloader.py:660, in XmlLoader._recurse_load(self, ros_config, tags, context, default_machine, is_core, verbose)
    659 if name == 'arg':
--> 660     self._arg_tag(tag, context, ros_config, verbose=verbose)
    661 elif self.args_only:
    662     # do not load other tags

File /opt/ros/noetic/lib/python3/dist-packages/roslaunch/xmlloader.py:96, in ifunless.<locals>.call(*args, **kwds)
     95 if ifunless_test(args[0], args[1], args[2]):
---> 96     return f(*args, **kwds)

File /opt/ros/noetic/lib/python3/dist-packages/roslaunch/xmlloader.py:306, in XmlLoader._arg_tag(self, tag, context, ros_config, verbose)
    305 except substitution_args.ArgException as e:
--> 306     raise XmlParseException(
    307         "arg '%s' is not defined. \n\nArg xml is %s"%(e, tag.toxml()))
    308 except ResourceNotFound as e:

XmlParseException: arg 'input1' is not defined.

Arg xml is <arg name="input2" value="$(arg input1)"/>

During handling of the above exception, another exception occurred:

RLException                               Traceback (most recent call last)
Cell In[3], line 1
----> 1 roslaunch.config.load_config_default(['./sample.launch'], DEFAULT_MASTER_PORT, verbose=False, ignore_unset_args=True)

File /opt/ros/noetic/lib/python3/dist-packages/roslaunch/config.py:463, in load_config_default(roslaunch_files, port, roslaunch_strs, loader, verbose, assign_machines, ignore_unset_args)
    461     loader.load(f, config, argv=args, verbose=verbose)
    462 except roslaunch.xmlloader.XmlParseException as e:
--> 463     raise RLException(e)
    464 except roslaunch.loader.LoadException as e:
    465     raise RLException(e)

RLException: arg 'input1' is not defined.

Arg xml is <arg name="input2" value="$(arg input1)"/>

Expected behavior with this PR.

with the same launch file, load_config_default should not raise the error when ignore_unset_args is true.

In [1]: from rosmaster import DEFAULT_MASTER_PORT

In [2]: import roslaunch

In [3]: roslaunch.config.load_config_default(['./sample.launch'], DEFAULT_MASTER_PORT, verbose=False, ignore_unset_args=True)
Out[3]: <roslaunch.config.ROSLaunchConfig at 0x7f5e96cd8af0>