Why does the ROS2 Python launch files look like XML written in Python?

I’ve spent sometime working both with ROS1 and ROS2’s launch files and gotten somewhat comfortable with them. Is it me, or is writing launch file for ROS2 very awkward? My main source of complaint is that the Python-based launch file doesn’t seem to really leverage the advantage of Python. For example, instead of using the if of the Python language, there’s a custom class called IfCondition (see this question for confusion about this). Further, instead of being able to import another launch file, perhaps via something like import (and then constructing an object, or call a function), we have IncludeLaunchDescription. There are a lot of other examples of this as well. Having such custom construct, in my opinion, makes the resulting “code” extremely unpythonic. Common operations like trying to get the value of a configuration value is hidden behind very unintuitive methods like LaunchConfiguration(...).perform(context), as opposed to something similar to the argparse interface. For me, ROS2 launch files are almost like learning an entirely different language and it is made worse by the lack of documentation for some of these constructs. Others also appeared to have complained about this.

In fact, if you squint a bit, the existing system look basically exactly like XML, but written in Python: each action/object added to the LaunchDescription can be represented by a XML node. While I haven’t tried this, I suspect that I can invent a XML format (probably quite similar to ROS1’s launchfile) that can be parsed by a Python program which then constructs a LaunchDescription based on the parsed results. While the XML format has its own issues, I feel like writing XML in Python is significantly worse, as there are a lot of Python that I cannot easily access with this style of writing code.

It’s very possible that I’m just not “getting it”. So my question is: why is the ROS2 launch file designed the way it is? What is meant to be its strength and weakness? Is my assessment of it correct? I want to be wrong and learn its strengths so I can like it better, as I find the existing system to be a significant displeasure to work with.

11 Likes

I find that there are huge advantages to having ROS2 launch files in Python. Yes, the syntax is still largely declarative like XML is, but you can build it dynamically with all the power of a general-purpose programming language. You can read arguments, and then define custom functions to generate actions right in the launch file. Sure, there are some things that may be a little unintuitive, but Python provides a much more flexible base to build those sorts of features into versus XML. For example, while IncludeLaunchDescription may seem more cumbersome than a language construct like import, since it just a class that you instantiate, you can create them programmatically, or provide arguments dynamically.

An example of this flexibility is evidenced by what you mentioned about IfCondition. If you really wanted to use the language-level if, you could always use if to check a condition, and then optionally append an action to the list that you provide to LaunchDescription.

2 Likes

As for documentation, yes, the ROS2 launch documentation is still rather lacking, or at least is not very discoverable. I find the best way to learn how to use them is to look directly at the source, and the doc comments in there. For example: launch/include_launch_description.py at 84ab8bee0be4bdcdd2f4f4b742e7bd10752a92ef · ros2/launch · GitHub

1 Like

For example, instead of using the if of the Python language, there’s a custom class called IfCondition

If I understand correctly, the ROS 2 Python launch files create a description of how to launch and keep the system running. The launch file themselves aren’t really what does the work of launching. Their work is over once they generate the description. It is launch that takes that description and executes it. That’s why there’s IfCondition to describe what to do if something should happen instead of a Python if to actually do it.

Further, instead of being able to import another launch file, perhaps via something like import (and then constructing an object, or call a function), we have IncludeLaunchDescription

If writing Python directly I would only recommend this Action when including a machine-specific launch file. It does the include at launch time, which is great if there’s a machine specific configuration, but not so great when trying to introspect launch files with things like ros2 launch --print. A real Python import is fine in a lot of cases in my opinion.

While I haven’t tried this, I suspect that I can invent a XML format (probably quite similar to ROS1’s launchfile) that can be parsed by a Python program which then constructs a LaunchDescription based on the parsed results

Your intuition is correct. It exists since ROS Eloquent in the launch_xml package. Here’s an example.

ros2 launch demo_nodes_cpp talker_listener.launch.xml

For me, ROS2 launch files are almost like learning an entirely different language and it is made worse by the lack of documentation for some of these constructs

That’s fair criticism. I don’t find working with the Python description directly very fun either. I think the tutorials should recommend the xml frontend instead.

Here’s a few relevant articles you may or may not have seen.

3 Likes

I think that many projects that use a structured data format for configuration (XML, JSON, YAML, etc.) eventually lead to some sort of solution to generate or template that configuration to gain more flexibility. While some may prefer this method, I personally do not. Rather than having a configuration language and a template language, I think it is better to gain the flexibility by simply putting everything into a general-purpose language like ROS2 launch does. Another example project that follows this paradigm in a similar (but not identical) way to ROS2 launch is AWS CDK, which uses general purpose programming languages versus the more traditional Cloudformation JSON/YAML templates.

1 Like

I also find the python launch scripts overly complex. It’s ok and cool to give advanced options for launching things but it fails on the principle of making simple things simple to do.

@sloretz is correct, this is the purpose of the Python API provided by launch, i.e. to create descriptions of what you want to do, not do it. That’s why it reads like a markup language like XML or JSON, because that’s what it was intended to do.

There’s a little bit about it here: ROS 2 Launch System

Agreed, that’s definitely a documentation issue. For some context, we started by only having the Python API due to time constraints, so we documented how to use it for making launch files a bit, in order to have something. Later we finished the frontends which let you use XML and YAML. I’ve always believed these should be what most people should use most of the time.

I get where you’re coming from, but when doing research for ros launch in ROS 2, I evaluated this approach, and I came to two conclusions:

  • First, we should follow the pattern set forth by large ecosystems like web (html + css for config + js for scripting, where our current launch frontends like XML are equivalent to html).

Separating describing what you want from actually making it happen (launching for launch, rendering for html) lets you have more standardization and also lets you have different backends, e.g. different browsers for html and different launch implementations for ROS 2 (imagine a tool that reads launch descriptions from py or xml and runs them using some OS dependent process management system).

Also, as systems get large and complex, having a separation from describing what you’re going to do and doing it let’s you debug it more easily. We haven’t yet gotten to writing those tools (the --print options is pretty naive and not super useful), but you can imagine a GUI that shows what a launch file will do and visualize it doing it as it launches, etc.

  • Second, having this Python API and XML/YAML frontends does not prevent you from having a dynamic “API-like” interface for launching things.

You, or someone else, could write a Python API that works more like how you’re imaging. You’d just loose the ability to introspect it because running it is the only way to know what’s in it.

I agree, but hopefully it’s clearer now, that this is not the original purpose of the launch API. I think the XML frontend does manage this, however. It could always be better of course.

6 Likes

Thanks for this super detailed reply!
I remember you mentioning the XML frontend at the 2018 ROSCon but have not seen updates on it since. Now I’m off to simplify some launch files :wink:

2 Likes

It is complicated, requires reading the source code and takes some getting used to, but I have created some nice launch scripts for bringup of a certain robot that does a fair bit of configuration and error checking before it begins the normal robot processes. Having it in Python and being able to create OpaqueFunctions that do certain tasks with Python libraries I have created, and creating log folders and touching files is great.

2 Likes

Some folks already made an effort of easing the pain:

2 Likes

You can write ros2 launch files in xml, it’s documented here: Migrating launch files from ROS 1 to ROS 2 — ROS 2 Documentation: Foxy documentation

What I don’t understand is why this is not best practice, or recommended more.

@wjwwood I understand the reasoning about how python is not actually executing the code but rather creating a description, however, I think there should be a way to do this with native python constructs. Take, for example, Tensorflow 2.0. TF 2.0 allows you to write things such as conditionals in native python, even though it is simply generating a GPU graph that will execute the conditional rather than a python interpreter executing the conditional. I’d have to do more research into the mechanism but I would think launch files could work a similar way

That sounds interesting, though I’d be curious what the limitations of that are, since there are lots of features in Python that are not represented in launch in anyway. Some simple things, like conditionals, might be possible though. I’d argue that then you’re not writing python anymore, you’re writing something that looks like Python but actually does something else.

1 Like

Thanks for the responses and great discussion everyone! I’m glad that I’m not completely crazy. There are a number of topics in this conversation and it is becoming a relatively complex topic:

  1. The current pain point involving the simplicity of the launch files, its documentations, a simpler and declarative front end, and recommendation/best practice guides for users;
  2. Flexibility for very-advanced users who needs “turing-complete”-level flexibility over what they want to launch (not necessarily how to launch it, as the current launch API only provides a description), not the actual action.

For 1, it seems that most are in agreement is such that we improve on the documentations and possibly recommend a frontend (if available) for the majority users. I think this would be an extremely valuable effort to do, as writing launchfile is almost one of the first thing you do. Due to the modular nature of ROS packages, it’s possible to create robots without writing much code other than launch files. I think it will help the “first impression” of ROS2 significantly, which may help adoption and porting from ROS1 → 2.

For 2, I think this is a bit more contentious, and rightly so. The topic of declarative vs imperative launch configuration has been going on for probably decades. Without much proof, my impression is that general software engineering community are moving towards a declarative/functional system of specifying configuration. Some recent and related example could be: (1) systemd with its declarative process launch system (which also computes a DAG) and (2) kubernetes with its declarative cluster topology configuration (if you can call it that) and its controllers. That said, most of these systems have “escape hatches” that allow you to mix in conditionals, or even loops (ansible is especially guilty of this). Without making a judgement on whether or not the industry is moving in the right direction and focus purely on the ROS launch system, my feeling is that we have the worst of both worlds: a declarative config system in an imperative language (hence my title of claiming it looks like XML). The ROS community should pick and stick to it, whichever way it decides to go.

To maintain the most flexibility, I personally would agree with what @wjwwood said above about having a “frontend” and “backend”, where (my interpretation is that) the “frontend” provides a declarative system that solves most of the uses cases and a “backend” that is more flexible. My argument for this is because I perceive the entire software community to be moving towards declarative configurations, and I think it would be a good idea for ROS to stick to this “social norm”, to make it easier to onboard people. One thing we would need to do is document both systems, which is a bit more work.

There may be other points that I missed, but this seems like the majority of it? TL;DR: IMO ROS should pick either a declarative or imperative configuration system and not mix the two.

2 Likes

I vouch for the ROS2 XML launch files too, as something to continue to support. Have been using them quite a bit and find them to even be a minor improvement over the ROS1 launch files (for example, a different way to specify input arguments vs constants), and still simple enough to port over from ROS1, with no reduction in overall expressiveness. For example, I have a pretty complex setup that I ported over from ROS1 to 2 without issues.

1 Like

Can you do conditioning / event handling in ROS2 XML Launch files, as we can do in Python Launch files?

Yes, as far as I know, ROS2 XML launch files have the same functionality but with a slightly different syntax. if= and unless= still work as they did before.

I wasn’t able to find resources on how to do event handling on lifecycle nodes, inside ROS2 XML Launch files. Can you share some?

It appears there’s no current way to invoke event handling on lifecycle nodes from the XML files:
https://answers.ros.org/question/352771/which-ros2-launch-is-preferable-python-or-xml/?answer=352774#post-id-352774

In regards to overall functionality, here’s a link to the XML schema which I had found to be extremely helpful:
https://design.ros2.org/articles/roslaunch_xml.html

Playing the role of a compiler frontend when reading / writing Python launch files is a miserable experience. By contrast, the YAML frontend is quite pleasant. The XML frontend is bearable as well – which says a lot about the first alternative.

Unfortunately it’s currently impossible for a beginner to start writing one’s launch files in YAML / XML before learning a good deal about the raw API as well as digging through code to find out how exactly things are supposed to map over.

I wish for this to be rectified in the future while appreciating that writing (good) documentation is a boring and thankless work.

Until then, an advice for people wishing to use the available frontends is to search the launch and launch_ros packages for @expose_action or get_attr strings, plus the action or attribute name, then reading the parse() methods to figure out the details. It’s annoying but not very hard to learn things this way.

3 Likes