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.
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
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.
@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.
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.
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.
@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.
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:
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;
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.
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.
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.