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:
- Index - parse
.reposfiles and generate a lockfile mapping every package to its repository + pinned commit SHA (like aCargo.lockorpixi.lockfor your source workspace) - Resolve - parse the launch file (XML or Python), evaluate conditionals and substitutions, follow includes recursively, and collect the set of packages actually referenced
- Fetch - sparse-checkout only those packages from git (no full clone)
- 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:
.reposmanifest 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.