Launch-plus: Bazel-like selective builds for ROS 2 that works with your existing project, today

A selective fetch/build tool for large workspaces: Treating ROS 2 launch files as build targets

Hi all,

If you work with a multi-repository ROS 2 workspace (Autoware, Open-RMF, or your own .repos-managed project), you’ve probably run into this cycle:

vcs import src < project.repos       # clone ~30 repos
rosdep install --from-paths src      # install ALL system deps
colcon build                         # build ~450 packages (1 hour~)
ros2 launch my_pkg my_launch.xml     # ...only to use 1/10 of them

The standard toolchain doesn’t know which packages your launch file actually references, so it builds everything. For a workspace like Autoware (~450 packages across ~30 repos), this means cloning and building a full autonomous driving stack even if you only need e.g. a planning module.

I’ve been working on a tool called launch-plus that flips things around: it reads your launch file first, traces the dependency graph, and then fetches and builds only the packages that are actually reachable.

How it works

launch-plus index project.repos                     # generate lockfile (one-time, can be updated)
launch-plus build my_pkg my_launch.xml arg:=value    # fetch + build only what's needed

Under the hood:

  1. Index - parse .repos files and generate a lockfile mapping every package to its repository + pinned commit SHA (like a Cargo.lock or pixi.lock for your source workspace)
  2. Resolve - parse the launch file (XML or Python), evaluate conditionals and substitutions, follow includes recursively, and collect the set of packages actually referenced
  3. Fetch - sparse-checkout only those packages from git (no full clone)
  4. Build - build the minimal transitive dependency set

For Python launch files, the resolver executes generate_launch_description() with mocked(intercepted) launch/launch_ros imports, including OpaqueFunction callbacks, without requiring the full ROS 2 Python stack to be installed. This means it can analyze launch files before any packages are built.

(Needless to say, some preconditions must be satisfied; e.g. the launcher files and param files should be present in the source repository, not generated artifacts. Autoware was a very successful integration target.)

The Bazel analogy

Although the complexity for now is incomparable, many features of this tool were inspired from how Bazel works.

A launch file declares its dependencies implicitly through <include>, <node pkg="...">, and $(find-pkg-share ...) - just as a BUILD file declares deps. launch-plus resolve is analogous to bazel query, and launch-plus build is analogous to bazel build - both figure out the minimal dependency graph from a target.

Restoring (and extending) launch introspection

I somewhat feel like preaching to a choir, but let’s dive into the history.

ROS 1 had a variety of launch inspection tools: from flags like --nodes, --dump-params, --files, to roslaunch-deps and roslaunch-check. These were all possible because launcher files were pure XML with a small, deterministic substitution language.

ROS 2’s move to Python launch files made most of these tools impossible to reimplement generically - arbitrary Python in OpaqueFunction callbacks, runtime conditionals, and dynamic substitutions mean you can’t analyze a launch file without executing it. Of the original inspection tools, only --show-args survived the transition.

launch-plus restores these capabilities and goes further - it can resolve the full launch graph (XML and Python, including OpaqueFunction) into a single flat representation, even before build, which none of the ROS 1 tools could do either.

Flattened, resolved output

The resolver produces a fully flattened, resolved XML - all includes inlined, all conditionals evaluated, all variables substituted. This gives you a complete, auditable view of what a launch configuration actually does: every node, parameter, remap, and namespace in one file. You can see the Autoware example here.

Now you might get overwhelmed with 6k lines of resolved xml file, but the output granularity is configurable - --show-args and --inline-params add argument declarations and parameter file contents respectively, so you can choose the level of detail. If you disable both, then you will get 2k lines of output.

For safety-critical systems or complex multi-team integrations, having a single document that captures the full runtime intent of a launch configuration is valuable for review and certification. Future work includes rendering this as an interactive HTML diagram for easier navigation.

We’ve found this useful for debugging argument propagation issues and for CI validation (diff the resolved output between branches to see what actually changed in the launch graph).

Some event-based entries such as LifecycleNode or OnStateTransition do not have their xml equivalent, and are not directly available to launch in the existing ros2launch. I am planning more systematic support for these event based entries in the future.

Current status

The tool is functional and tested against the full Autoware launch graph (the most complex open-source ROS 2 launch system I’m aware of). Implemented:

  • Indexer: .repos manifest parsing and lockfile generation with pinned SHAs
  • Resolver: XML and Python launch files, including OpaqueFunction, conditional evaluation, and recursive include expansion
  • Builder: selective build driven by resolved launch dependencies

On the roadmap:

  • Built-in executor and direct process lifecycle management
  • Direct ament builds for finer dependency-type control, replacing colcon backend
  • HTML visualization of resolved launch graphs
  • CI integration and GitHub Actions

Works with standard .repos files (vcs2l compatible), standard launch.xml/launch.py and standard package.xml dependency declarations. No special annotations or breaking changes required for existing launch files.

launch-plus takes compatibility-first approach for better integration experience, which is fundamentally different from ‘break stuffs to make launch system better’ proposals, including Better Launch , Autoware System Designer , or even Bazel itself.

Complementary to Pixi/RoboStack

While Pixi is developing on its own pixi-build-ros Building a ROS Package - Pixi that aims to replace rosdep and colcon, at this moment a hybrid approach is more feasible for handling a meta-repository with many packages repositories should be updated together in SHA-level granularity.

Although the exact integration still has more way to go, I am expecting this kind of collaboration: Pixi manages the binary environment (base ROS install, compilers, system deps). launch-plus manages which source packages to fetch and build for a specific launch target. launch-plus can run inside a pixi-managed environment.

Who might find this useful?

  • Teams with large multi-repo workspaces who want faster iteration - especially if your launch system has grown complex enough that no single person understands the full graph
  • CI/CD pipelines that want to build and test only the packages affected by a launch configuration change
  • System integrators and safety engineers who need a clear, auditable view of what a launch file actually does at runtime
  • Multi-ECU deployments where each compute unit runs a different launch target - perception on one ECU, planning on another, for example. Each ECU only needs its own package subset; there’s no reason to build the full stack on every board

…and anyone tired of waiting hours for a full workspace build when you only need a handful of packages!

Try it

The repo is at https://github.com/paulsohn/launch-plus - it includes a bundled Autoware example with a pre-generated lockfile so you can try the resolver immediately without writing a manifest or running index.

I am relatively new to the robotics community outside Autoware, and I’d love to hear thoughts - especially from anyone who’s dealt with the “build everything” problem in their own projects, or who has opinions on whether the launch-file-as-build-target model makes sense beyond Autoware-scale workspaces.

1 Like

Your project sounds really interesting, if only for getting back the lost functionality you mention.

A quick search of the Github repository suggests some of it can be used without using the rest of the tool and workflow, which is great I believe.

This would also be my main question/comment at this point: if I only wanted to tweak the vcs import, rosdep install and colcon build lines in the example setting-up-a-ros-workspace commands you show, by passing which repositories to clone, for which packages to install dependencies and which packages to build, your tool would immediately become very interesting even outside of “setting up a workspace for big projects with .repos” scenarios.

In other words: could this (be made to) output just a list of package names or just a YAML snippet (to pipe to vcs over stdin fi) without using it as the one-tool-to-build-it-all?

Tell me to RTFM please if that’s documented somewhere, I couldn’t quickly find it.

First of all, thank you for showing interest.

May I ask you to elaborate on your intended usecase? You mentioned ‘outside of “setting up a workspace for big projects with .repos" scenarios’, but you want list of package names (maybe repository names, in .repos format?) to pipe into vcstool. Without the base .repos file input to indicate from which repository we should find packages, the tool is clueless to find the entrypoint.

This project is still in an early development stage, so some documents might have poor readability. The ‘lost functionality’ workflow might be launch-plus resolve, which is described here: launch-plus/docs/architecture.md at devel · paulsohn/launch-plus · GitHub

The resolver needs the actual launch file on the disk, so starting from entrypoint it sparse-checkouts all packages in the include chain and reads the corresponding launch files. Thus, after the resolution is complete, src directory will already have each packages sparse-cloned, without any need to pipe anything into vcstool.

rosdep install is also built-in (as --rosdep option), because without it the resolver cannot determine whether an unknown dependency is actually installable or just a typo.

Now if you don’t want our build pipeline and want to directly use colcon, then you might be interested in launch-plus build --dry-run, which resolves, fetches additional build dependencies, and prints colcon build command instead of directly executing it. Note that the build backend (currently colcon) may change in future versions, but the tool will always have an option to display which packages are being built.

I hope this answer helps!