{"id":42079596,"url":"https://github.com/dfki-ric/better_launch","last_synced_at":"2026-01-29T17:13:46.930Z","repository":{"id":307145298,"uuid":"1024736597","full_name":"dfki-ric/better_launch","owner":"dfki-ric","description":"A better replacement for the ROS2 launch system: intuitive, simple, memorable.","archived":false,"fork":false,"pushed_at":"2026-01-25T15:57:00.000Z","size":18306,"stargazers_count":258,"open_issues_count":11,"forks_count":23,"subscribers_count":5,"default_branch":"main","last_synced_at":"2026-01-26T22:21:15.613Z","etag":null,"topics":["launch","ros2"],"latest_commit_sha":null,"homepage":"https://dfki-ric.github.io/better_launch/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dfki-ric.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.rst","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-07-23T07:06:18.000Z","updated_at":"2026-01-22T03:23:37.000Z","dependencies_parsed_at":"2025-08-20T17:16:10.623Z","dependency_job_id":"b7183a76-d792-450f-901a-6755c502cf49","html_url":"https://github.com/dfki-ric/better_launch","commit_stats":null,"previous_names":["dfki-ric/better_launch"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/dfki-ric/better_launch","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dfki-ric%2Fbetter_launch","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dfki-ric%2Fbetter_launch/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dfki-ric%2Fbetter_launch/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dfki-ric%2Fbetter_launch/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dfki-ric","download_url":"https://codeload.github.com/dfki-ric/better_launch/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dfki-ric%2Fbetter_launch/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28881474,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-29T16:41:59.663Z","status":"ssl_error","status_checked_at":"2026-01-29T16:39:39.641Z","response_time":59,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["launch","ros2"],"created_at":"2026-01-26T09:43:51.387Z","updated_at":"2026-01-29T17:13:46.912Z","avatar_url":"https://github.com/dfki-ric.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"![Logo](media/logo_text.png)\n\n\n[About](#about) | [Why?](#why-not-improve-the-existing-ros2-launch) | [Features](#okay-what-can-i-do-with-it) | [Usage](#how-do-i-use-it) | [TUI](#the-tui) | [Differences](#what-are-the-differences) | [Performance](#performance) | [Installation](#installation) | [ROS2](#whats-so-bad-about-ros2-launch) | [Contributors](#contributors)\n\n\n\u003e [!TIP]\n\u003e Just looking for the [documentation](https://dfki-ric.github.io/better_launch/)? \n\u003e We also have multiple [examples](examples/)!\n\n\n# 🧭 About\nLet's face it: ROS2 has been a severe downgrade in terms of usability compared to ROS1. While there are many considerable improvements, the current launch system is borderline unusable. I've listed my personal gripes below, but if you're here you likely feel the same. This is why I wrote ***better_launch***.\n\nInstead of dozens of imports and class instances for even the most basic tasks, your launch files could look as simple and beautiful as this:\n\n```python\nfrom better_launch import BetterLaunch, launch_this\n\n@launch_this(ui=True)\ndef my_main(enable_x: bool = True):\n    \"\"\"\n    This is how nice your launch files could be!\n    \"\"\"\n    bl = BetterLaunch()\n\n    if enable_x:\n        bl.node(\n            \"examples_rclpy_minimal_publisher\",\n            \"publisher_local_function\",\n            \"example_publisher\",\n        )\n\n    # Include other launch files, even regular ROS2 launch files!\n    bl.include(\"better_launch\", \"ros2_turtlesim.launch.py\")\n```\n\n```bash\n$\u003e bl my_package my_launch_file.py --enable_x True\n```\n\n*Do I have your attention? Read on to learn more!*\n\n\n# 🤔 Why not improve the existing ROS2 launch?\nBecause I think it is beyond redemption and no amount of refactoring and REPs (ROS enhancement proposals) will turn the sails. Tools like the highly rated [simple_launch](https://github.com/oKermorgant/simple_launch) or [launch-generator](https://github.com/Tacha-S/launch_generator/) exist, but still use ROS2 launch under the hood and so inherit much of its clunkiness. Rather than fixing an inherently broken solution, I decided to make a RAP - a ROS abandonment proposal :)\n\nEssentially, *better_launch* is what I wish ROS2 launch would be: intuitive to use, simple to understand, easy to remember. This is why *better_launch* is **not** yet another abstraction layer over ROS2 launch; it is a **full replacement** with no required dependencies on the existing launch system.\n\n\n# 🧩 Okay, what can I do with it?\nEverything you would expect and a little more! The `BetterLaunch` instance allows you to\n- create *subscribers*, *publishers*, *services*, *service clients*, *action servers* and *action clients* on the fly\n- start and stop *nodes*\n- start and stop *lifecycle nodes* and manage their lifecycle stage\n- start and stop *composers* and load *components* into them\n- organize your nodes in *groups*\n- define hasslefree *topic remaps* for nodes and groups\n- *pass any arguments* from the command line without having to declare them\n- easily *load parameters* from yaml files\n- *locate files* based on filenames and package names\n- use *string substitutions* to resolve e.g. paths\n- include other *better_launch launch files*\n- include other *ROS2 launch files*\n- let regular ROS2 launch files *include your better_launch launch files*\n- configure *logging* just as you would in ROS2, yet have much more readable output\n- manage your node using a nice [terminal UI](#the-tui) reminiscent of [rosmon](https://github.com/xqms/rosmon)\n\nFor a quick comparison, bravely unfold the sections below:\n\u003cdetails\u003e\n  \u003csummary\u003eROS2\u003c/summary\u003e\n\n```python\n# Taken from https://docs.ros.org/en/jazzy/Tutorials/Intermediate/Launch/Using-Substitutions.html\nfrom launch_ros.actions import Node\n\nfrom launch import LaunchDescription\nfrom launch.actions import DeclareLaunchArgument, ExecuteProcess, TimerAction\nfrom launch.conditions import IfCondition\nfrom launch.substitutions import LaunchConfiguration, PythonExpression\n\n\ndef generate_launch_description():\n    turtlesim_ns = LaunchConfiguration('turtlesim_ns')\n    use_provided_red = LaunchConfiguration('use_provided_red')\n    new_background_r = LaunchConfiguration('new_background_r')\n\n    turtlesim_ns_launch_arg = DeclareLaunchArgument(\n        'turtlesim_ns',\n        default_value='turtlesim1'\n    )\n    use_provided_red_launch_arg = DeclareLaunchArgument(\n        'use_provided_red',\n        default_value='False'\n    )\n    new_background_r_launch_arg = DeclareLaunchArgument(\n        'new_background_r',\n        default_value='200'\n    )\n\n    turtlesim_node = Node(\n        package='turtlesim',\n        namespace=turtlesim_ns,\n        executable='turtlesim_node',\n        name='sim'\n    )\n    spawn_turtle = ExecuteProcess(\n        cmd=[[\n            'ros2 service call ',\n            turtlesim_ns,\n            '/spawn ',\n            'turtlesim/srv/Spawn ',\n            '\"{x: 2, y: 2, theta: 0.2}\"'\n        ]],\n        shell=True\n    )\n    change_background_r = ExecuteProcess(\n        cmd=[[\n            'ros2 param set ',\n            turtlesim_ns,\n            '/sim background_r ',\n            '120'\n        ]],\n        shell=True\n    )\n    change_background_r_conditioned = ExecuteProcess(\n        condition=IfCondition(\n            PythonExpression([\n                new_background_r,\n                ' == 200',\n                ' and ',\n                use_provided_red\n            ])\n        ),\n        cmd=[[\n            'ros2 param set ',\n            turtlesim_ns,\n            '/sim background_r ',\n            new_background_r\n        ]],\n        shell=True\n    )\n\n    return LaunchDescription([\n        turtlesim_ns_launch_arg,\n        use_provided_red_launch_arg,\n        new_background_r_launch_arg,\n        turtlesim_node,\n        spawn_turtle,\n        change_background_r,\n        TimerAction(\n            period=2.0,\n            actions=[change_background_r_conditioned],\n        )\n    ])\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003ebetter_launch\u003c/summary\u003e\n\n```python\nfrom better_launch import BetterLaunch, launch_this\nfrom rclpy import Timer\n\n@launch_this\ndef my_start(\n    # Launch arguments in function signature\n    turtlesim_ns: str = \"turtlesim1\", \n    use_provided_red: bool = False, \n    new_background_r: int = 200,\n):\n    bl = BetterLaunch()\n\n    # Pythonic AF\n    with bl.group(turtlesim_ns):\n        turtle_node = bl.node(\n            package=\"turtlesim\",\n            executable=\"turtlesim_node\",\n            name=\"sim\",\n            # Pass parameters directly \n            params={\"background_r\": 120}\n        )\n\n        # Convenient API for common tasks\n        bl.call_service(\n            topic=f\"/{turtlesim_ns}/spawn\",\n            service_type=\"turtlesim/srv/Spawn\",\n            # No weird types like passing dicts as strings\n            request_args={\"x\": 2.0, \"y\": 2.0, \"theta\": 0.2},\n        )\n\n        if new_background_r == 200 and use_provided_red:\n            turtle_node.is_ros2_connected(timeout=None)\n            turtle_node.set_live_params({\"background_r\": new_background_r})\n```\n\u003c/details\u003e\n\n\n# 🛠️ How do I use it?\nThe best way to get to know *better_launch* is to explore the included [examples](examples/). Unlike ROS2, all examples and functions come with proper [documentation](https://dfki-ric.github.io/better_launch/). If anything is left unclear, feel free to contact me.\n\nYou will mainly interact with *better_launch* through the following classes and modules:\n- [@launch_this](better_launch/wrapper.py): decorator to create a launch file from a function.\n- [BetterLaunch](better_launch/launcher.py): to create and start nodes, include other launch files, find and load parameters, etc.\n- [convenience.py](better_launch/convenience.py): convenience functions to start rviz, robot state publishers, read urdf/xacro files, and more.\n- [gazebo.py](better_launch/gazebo.py): functions and helpers for starting and populating gazebo simulations as well as bridging topics.\n\nNote that you are not forced to choose between *better_launch* and the ROS2 launch system. In fact, *better_launch* launch files can be run via `ros2 launch` and even be included from ROS2 launch files! However, this means running two launch systems on top of each other, so there is some overhead. The auto completion of `ros2 launch` is also slow as hell, cluttering the terminal with useless command line options yet is unable to discover the arguments you have declared inside your launch files. For these reasons, *better_launch* comes with the `bl` script, which fixes all of the above and then some. Once you have sourced your workspace you can use it as follows:\n\n```bash\n# Try \u003ctab\u003e\u003ctab\u003e for autocomplete and check the example launch file for details!\nbl better_launch 05_launch_arguments.py --help\n```\n\n*better_launch* also reacts to the following environment variables:\n- `BL_UI_OVERRIDE` (*enable|disable*): enables or disables the UI for all launch files. Superseded by the `--bl_ui_override` argument.\n- `BL_COLORMODE_OVERRIDE` (*default|severity|source|none|rainbow*): overrides the colormode for all launch files. Superseded by the `--bl_colormode_override` argument.\n- `BL_SCREEN_LOG_FORMAT_OVERRIDE`: overrides the format for messages logged to the terminal. Check the [PrettyLogFormatter](better_launch/utils/better_logging.py) for valid syntax.\n- `BL_FILE_LOG_FORMAT_OVERRIDE`: overrides the format for messages logged to log files. Check the [PrettyLogFormatter](better_launch/utils/better_logging.py) for valid syntax.\n\n\n# 📟 The TUI\n*better_launch* comes with a sneaky, unobstrusive TUI (terminal user interface) based on [prompt_toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit), which will hover below the log output. You can start it by either passing `ui=True` to the `launch_this` wrapper, or by adding `--bl_ui_override=enable` on the command line. Use *\\\u003ctab\u003e* to switch between menu items.\n\n![TUI](media/tui.png)\n\nSee the single line of shortcuts at the bottom? That's the TUI, and it will never take up more than 3 lines! Despite its simplicity, the TUI allows you a comfortable degree of control over all nodes managed by the *better_launch* process it is running in:\n- listing a node's services and topics\n- starting and stopping nodes\n- triggering lifecycle transitions\n- changing the log level\n- etc.\n\n```bash\n# Run this line to see it in action!\nbl better_launch 02_ui.launch.py\n```\n\nThe TUI is also able to manage nodes started from different shells and processes, even if they have been started by ROS2 or other means. To do so, pass the `manage_foreign_nodes` flag to the wrapper or command line. Be aware though that this will not capture their output - to get their output you will have to use the *takeover* action from the TUI, which will restart the node process with the original arguments.\n\n\u003e Foreign node processes are identified by having one of the following parameters in their arguments: `__ns`, `__name`, `__node`, `--ros-args`. This is always true for nodes started from launch files, but fails when they were started via `ros2 run` or other means. As far as I'm aware, there is no better way right now.\n\n\n# ⚖️ What are the differences?\nBecause *better_launch* does not use the ROS2 launch system, some aspects work differently from what you may be used to.\n\n\n## Action immediacy\nIn ROS2 launch, launch files create tasks that are then passed to an asynchronous event loop. This is the reason why e.g. checking for launch parameter values is so incredibly weird - they simply don't exist yet by the time you define the actions. In *better_launch* however, all actions are taken immediately: if you create a node, its process is started right away; if you include another *better_launch* launch file, its contents will be handled before the function returns. \n\nThe only exception to this is adding ROS2 launch actions, e.g. including regular ROS2 launch files. Since these still rely on the ROS2 launch system, they need to be turned into asynchronous tasks and passed to the event loop. Usually a ROS2 `LaunchService` sub-process is started the first time a ROS2 action is passed to *better_launch*. From then on this process will handle all ROS2 actions asynchronously in the background. \n\n\u003e While the output of the ROS2 launch service process (and its nodes) is captured and formatted by *better_launch* just like for all other nodes, these will usually appear and behave as one single `launch_service` unit in the TUI (unless `manage_foreign_nodes` is true, see above).\n\n\n## Lifecycle nodes\nLifecycle nodes differ from regular nodes in that they don't become fully active after their process starts. Instead you have to call one of their lifecycle management services, usually via additional code in your launch file or the `ros2 lifecycle` CLI. However, in the end they are still just nodes.\n\n*better_launch* makes no distinction between regular and lifecycle nodes. Instead, all \"lifecyclable\" objects (e.g. nodes and components) provide a `LifecycleManager` object via their `lifecycle` member. This will be `None` if the object has not been identified (yet) as a lifecycle-thing - otherwise you can use it to manage the object's lifecycle. Additionally, all objects that turn out to be lifecyclable will transition to their *ACTIVE* state by default, unless you pass a different target state on instantiation.\n\n\n## Type checking\nWhen passing arguments to a node in ROS2, in the end everything is passed as stringified command line arguments. So why bother with overly strict type checking? Why do I have to turn half the parameters into strings myself? *better_launch* does not impose a flawed type sytem on you and will happily accept `int`, `string`, `float`, etc. where appropriate. In addition, sensible and *unsurprising* types have been chosen for all arguments you may provide (e.g. remaps are defined as a `dict[str, str]`, floats are happy to accept ints, launch arguments are not required to be strings, etc.).\n\n\n## Declaring launch arguments\nSimply put: you don't. *better_launch* will check the signature of your launch function and turn all arguments into launch arguments. For example, if your launch function has an `enable_x` argument, you will be able to pass it with `--enable_x` from the command line. Under the hood *better_launch* is using [click](https://click.palletsprojects.com/), so every launch file you write comes with proper CLI support. \n\n\u003e Tip: try adding a docstring to your launch function and call your launch file with `--help`!\n\n\n## Parameter files\nYou do **not** have to put `ros__parameters` in your configs anymore when using `BetterLaunch.load_params`. Hooray! You still can do so of course if you feel slightly masochistic. In fact, *better_launch* supports the full param syntax for mapping params to nodes, including namespace wildcards. See the `load_params` documentation for details.\n\n\n## Logging\nJust like ROS2 launch, *better_launch* takes care of managing loggers and redirecting everything where it belongs (in fact that part is largely copied from ROS2 launch). However, I did away with the in my opinion not very useful separation between a node's `stdout` and `stderr`, since nodes apparently write their log output to `stderr` by default. \n\nI also added a reformatting layer so that colors and nicer screen output are possible. The format can be customized by passing your own logging format strings to `launch_this`. Alternatively, you may set the `OVERRIDE_SCREEN_LOG_FORMAT` and `OVERRIDE_FILE_LOG_FORMAT` environment variables.\n\n\n## Abandoned processes\nROS2 launch has a bad reputation of leaving stale and abandoned processes behind after terminating. In my testing so far this has never been an issue with *better_launch* yet - except when you hard kill (-9) its process.\n\n\n# 💯 Performance\nI am not an expert on profiling code. Even though *better_launch* uses synchronous calls (or classic threads if necessary), and does some additional work to reformat output from nodes, it was able to achieve similar performance to `ros2 launch`. The scripts and results from the benchmarks can be found under [media/benchmarks](media/benchmarks/). This section will only show the most relevant parts.\n\n\u003e `bl` is just a script to locate the launch file and then run it, so I decided to not use `bl` for these benchmarks and instead run the launch file directly; otherwise the resources used by the launch file will not be visible to most profilers.\n\n\u003cdetails\u003e\n  \u003csummary\u003ememray\u003c/summary\u003e\n\n[memray](https://github.com/bloomberg/memray) reports that *better_launch* uses about 30% less memory than `ros2 launch`.\n\n|                   | better_launch | ros2 launch |\n| ----------------- | ------------- | ----------- |\n| allocations       | 48196         | 60943       |\n| peak memory usage | 6.6 MiB       | 9.7 MiB     |\n| details           | [link](docs/benchmarks/results/memray/memray-flamegraph-bl.html) | [link](docs/benchmarks/results/memray/memray-flamegraph-ros2.html) |\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003epsutil\u003c/summary\u003e\n\n[psutil](https://psutil.readthedocs.io/en/latest/index.html#psutil.Process.memory_full_info) shows that *better_launch* uses more CPU in the beginning and more memory in total compared to `ros2 launch`. The memory reported is the unique set size (see the previous link). I'm not sure how these results relate to the memray statistics above. \n\n![](docs/benchmarks/results/psutil/cpu_usage.png)\n\n![](docs/benchmarks/results/psutil/memory_usage.png)\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003epy-spy\u003c/summary\u003e\n\nI use [py-spy](https://github.com/benfred/py-spy) to see where *better_launch* is using resources that can still be optimized. The speedscope files can be visualized on [speedscope.app](https://www.speedscope.app/).\n\n![](docs/benchmarks/results/pyspy/bl.svg)\n\n![](docs/benchmarks/results/pyspy/ros2.svg)\n\n\u003c/details\u003e\n\n\n# 📥 Installation\nI'm working on getting a .deb package up and running. Until then you may follow the steps below!\n\n*better_launch* is a regular ROS2 package, which means you can install it in your workspace and then use it in all launch files within that workspace. \n\nROS2 is slowly [moving towards pixi](https://docs.ros.org/en/kilted/Installation/Windows-Install-Binary.html) as the main python3 environment, but I have not tested it yet. However, by now all the dependencies have been added into rosdep, so the following should get you up and running:\n\n```bash\n# Install dependencies\nsudo apt update\nrosdep update\nrosdep install --from-paths src --ignore-src -y\n```\n\n\n\u003cdetails\u003e\n    \u003csummary\u003ePython venv\u003c/summary\u003e\n\nIf you prefer a python virtual environment instead, here is a setup that works for us:\n\n```bash\n# Install some prerequisites\nsudo apt install python3-pip python3-venv\n\n# Create a virtual environment for your workspace\ncd your/ros2/workspace/\nmkdir venv\npython3 -m venv ./venv --system-site-packages --symlinks\ntouch venv/COLCON_IGNORE\n\n# Activate the venv\nsource ./venv/bin/activate\n\n# Activate your ROS2 workspace\nsource ./install/setup.bash\n\n# Install the dependencies into your venv\npip install -r path/to/better_launch/requirements.txt\n```\n\u003c/details\u003e\n\n---\nNo matter which path you choose, once all the dependencies are installed you should build *better_launch* / your workspace.\n\n```bash\n# Get better_launch into your workspace src folder\ncd \u003cyour/ros2/workspace\u003e/src\ngit clone https://github.com/dfki-ric/better_launch.git\n```\n\n```bash\n# Build the better_launch package\ncd \u003cyour/ros2/workspace\u003e\ncolcon build --packages-up-to better_launch\nsource install/setup.bash\n```\n\n```bash\n# Verify installation\nbl --help\n```\n\n\n# 📢 What's so bad about ROS2 launch?\nHere is a \"simple\" launch file from the [official documentation](https://docs.ros.org/en/jazzy/Tutorials/Intermediate/Launch/Using-Substitutions.html) that does nothing but include another launch file:\n\n```python\nfrom launch_ros.substitutions import FindPackageShare\n\nfrom launch import LaunchDescription\nfrom launch.actions import IncludeLaunchDescription\nfrom launch.launch_description_sources import PythonLaunchDescriptionSource\nfrom launch.substitutions import PathJoinSubstitution, TextSubstitution\n\n\ndef generate_launch_description():\n    colors = {\n        'background_r': '200'\n    }\n\n    return LaunchDescription([\n        IncludeLaunchDescription(\n            PythonLaunchDescriptionSource([\n                PathJoinSubstitution([\n                    FindPackageShare('launch_tutorial'),\n                    'launch',\n                    'example_substitutions_launch.py'\n                ])\n            ]),\n            launch_arguments={\n                'turtlesim_ns': 'turtlesim2',\n                'use_provided_red': 'True',\n                'new_background_r': TextSubstitution(text=str(colors['background_r']))\n            }.items()\n        )\n    ])\n```\n\nI think we can agree that this is not exactly elegant - including another launch file should be doable within a single line, not 10 plus 5 imports. Other terrible decisions within ROS2 launch include, but are not limited to:\n- a weird fetish for unintuitive import statements (see above)\n- unneccesarily strict type checking (why use python if I have to verify everything?)\n- nonsensical argument types (e.g. remaps are a *list of tuples* instead of simply a *dict*)\n- using asyncio may be slightly faster, but prevents normal variable interactions (ever wondered why you always see these weird `Condition` classes instead of a simple `if my_arg:`?)\n- horrendous API for starting lifecycle nodes (also, why the hell are there two completely separate base interfaces?)\n- the list goes on...\n\nFor comparison, here is what the above launch file will look like in *better_launch*:\n\n```python\nfrom better_launch import BetterLaunch, launch_this\n\n@launch_this\ndef main(turtlesim_ns = \"turtlesim2\", use_provided_red = True, new_background_r = 200):\n    bl = BetterLaunch()\n\n    bl.include(\n        \"launch_tutorial\", \n        \"example_substitutions.launch.py\",\n        pass_all_args=True,  # or pass as keyword arguments\n    )\n```\n\nOverall, ROS2 launch seems like a system architect's wet fever dream, and I don't enjoy it.\n\n\n# 🌱 Contributors\n*Author:* [Nikolas Dahn](https://github.com/ndahn/)\n\n*Testing \u0026 Feedback:*\n- [Tom Creutz](https://github.com/tomcreutz)\n- [Prithvi Sanghamreddy](https://github.com/Prithvi-Sanghamreddy)\n- [Sebastian Kasperski](https://github.com/skasperski)\n\n*better_launch* was initiated and is currently developed at the [Robotics Innovation Center](http://robotik.dfki-bremen.de/en/startpage.html) of the [German Research Center for Artificial Intelligence (DFKI)](http://www.dfki.de) in Bremen.\n\n---\n\n*Copyright 2025, [DFKI GmbH](http://www.dfki.de) / [Robotics Innovation Center](http://robotik.dfki-bremen.de/en/startpage.html)*\n\n![dfki-logo](media/dfki.png)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdfki-ric%2Fbetter_launch","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdfki-ric%2Fbetter_launch","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdfki-ric%2Fbetter_launch/lists"}