Repository: ros/ros_comm

Issue:
[Feature request] --all-nodes-required
roslaunch can handle a node attribute required:

> required="true"(optional)
> 
> ROS 0.10: If node dies, kill entire roslaunch.

However in some situations all nodes a required. E.g. when running as a systemd service (systemd can then trigger a restart if the process died with an error code)
Or when running CI to check if your roslaunch works.

But enabling required=true for all nodes is impractical
- when launching locally, you might not want this behavior
- when using launch files provided by others.

Therefor I suggest an argument `--all-nodes-required` 


Discussion:
This seems reasonable. There's prior art in the `--screen` flag, which is similarly equivalent to adding `output="screen"` to every node element. Patches welcome!

Resolving patch:
diff --git a/tools/roslaunch/src/roslaunch/__init__.py b/tools/roslaunch/src/roslaunch/__init__.py
--- a/tools/roslaunch/src/roslaunch/__init__.py
+++ b/tools/roslaunch/src/roslaunch/__init__.py
@@ -140,6 +140,9 @@ def _get_optparse():
     parser.add_option("--screen",
                       dest="force_screen", default=False, action="store_true",
                       help="Force output of all local nodes to screen")
+    parser.add_option("--required",
+                      dest="force_required", default=False, action="store_true",
+                      help="Force all nodes to be required")
     parser.add_option("--log",
                       dest="force_log", default=False, action="store_true",
                       help="Force output of all local nodes to log")
@@ -322,7 +325,8 @@ def main(argv=sys.argv):
                     force_log=options.force_log,
                     num_workers=options.num_workers, timeout=options.timeout,
                     master_logger_level=options.master_logger_level,
-                    show_summary=not options.no_summary)
+                    show_summary=not options.no_summary,
+                    force_required=options.force_required)
             p.start()
             p.spin()
 
diff --git a/tools/roslaunch/src/roslaunch/parent.py b/tools/roslaunch/src/roslaunch/parent.py
--- a/tools/roslaunch/src/roslaunch/parent.py
+++ b/tools/roslaunch/src/roslaunch/parent.py
@@ -73,7 +73,7 @@ class ROSLaunchParent(object):
     """
 
     def __init__(self, run_id, roslaunch_files, is_core=False, port=None, local_only=False, process_listeners=None,
-            verbose=False, force_screen=False, force_log=False, is_rostest=False, roslaunch_strs=None, num_workers=NUM_WORKERS, timeout=None, master_logger_level=False, show_summary=True):
+            verbose=False, force_screen=False, force_log=False, is_rostest=False, roslaunch_strs=None, num_workers=NUM_WORKERS, timeout=None, master_logger_level=False, show_summary=True, force_required=False):
         """
         @param run_id: UUID of roslaunch session
         @type  run_id: str
@@ -107,6 +107,8 @@ def __init__(self, run_id, roslaunch_files, is_core=False, port=None, local_only
         @throws RLException
         @param master_logger_level: Specify roscore's rosmaster.master logger level, use default if it is False.
         @type master_logger_level: str or False
+        @param force_required: (optional) whether to make all nodes required
+        @type force_required: boolean
         """
         
         self.logger = logging.getLogger('roslaunch.parent')
@@ -130,6 +132,7 @@ def __init__(self, run_id, roslaunch_files, is_core=False, port=None, local_only
         # allow alternate config loaders to be passed in.
         self.force_screen = force_screen
         self.force_log = force_log
+        self.force_required = force_required
         
         # flag to prevent multiple shutdown attempts
         self._shutting_down = False
@@ -147,6 +150,12 @@ def _load_config(self):
         if self.force_log:
             for n in self.config.nodes:
                 n.output = 'log'
+        if self.force_required:
+            for n in self.config.nodes:
+                n.required = True
+                if n.respawn and n.required:
+                  raise ValueError("respawn and required cannot both be set to true")
+
 
     def _start_pm(self):
         """


Test patch: