Problem: Colcon Builds Take Forever in Large Workspaces
Your ROS 2 workspace has 30+ packages and colcon build takes 15 minutes. Changing one line requires rebuilding half your workspace, and dependency conflicts break builds randomly.
You'll learn:
- How to structure workspaces for fast incremental builds
- Parallel build optimization for multi-core systems
- Dependency isolation strategies
- When to split workspaces vs use overlays
Time: 20 min | Level: Intermediate
Why This Happens
Colcon rebuilds packages when their dependencies change, but poor workspace structure causes unnecessary cascade rebuilds. Default settings also underutilize CPU cores and don't cache effectively.
Common symptoms:
- Full rebuilds when touching shared libraries
- Build times scale poorly past 20 packages
- "Package X conflicts with Y" errors
- CPU usage stuck at 25% during builds
- Incremental builds still take 5+ minutes
Solution
Step 1: Audit Your Workspace Structure
# See dependency tree
colcon graph --dot | dot -Tpng -o deps.png
# Count packages per layer
colcon list --packages-select-by-dep <base_package> | wc -l
Expected: You should see distinct layers (base libraries → core nodes → application nodes)
Red flags:
- Circular dependencies
- Application packages depending on other applications
- More than 5 levels of dependency depth
Step 2: Restructure Into Overlay Workspaces
Split your monolithic workspace into stable and active layers:
# Base workspace (rarely changes)
~/ros2_ws/
├── base_ws/
│ ├── src/
│ │ ├── custom_msgs/ # Message definitions
│ │ ├── common_utils/ # Shared libraries
│ │ └── hardware_drivers/ # Device interfaces
│ └── install/
# Development workspace (frequent changes)
├── dev_ws/
│ ├── src/
│ │ ├── perception_node/
│ │ ├── planning_node/
│ │ └── control_node/
│ └── install/
Why this works: Base packages build once. Dev workspace only rebuilds what you're actively changing. Overlays inherit base without rebuilding it.
Build base once:
cd ~/ros2_ws/base_ws
colcon build --symlink-install
source install/setup.bash
Build dev with overlay:
cd ~/ros2_ws/dev_ws
# Base workspace already sourced
colcon build --symlink-install
Step 3: Optimize Parallel Builds
# Find your CPU core count
nproc
# Build with proper parallelization
colcon build \
--symlink-install \
--parallel-workers $(nproc) \
--executor sequential \
--cmake-args -DCMAKE_BUILD_TYPE=Release
Key flags explained:
--parallel-workers: Match CPU cores (default is too conservative)--executor sequential: More reliable than parallel executor for complex deps--symlink-install: Avoids copying files, faster installs-DCMAKE_BUILD_TYPE=Release: Enables compiler optimizations
For development builds:
# Faster but less optimized
colcon build \
--symlink-install \
--parallel-workers $(nproc) \
--cmake-args -DCMAKE_BUILD_TYPE=RelWithDebInfo
Step 4: Use Selective Building
Only rebuild what you need:
# Build single package and its dependencies
colcon build --packages-up-to my_package
# Build single package without dependencies
colcon build --packages-select my_package
# Skip packages you know work
colcon build --packages-skip problematic_package
# Build everything that depends on a changed package
colcon build --packages-above my_changed_package
Workflow example:
# You edited perception_node
colcon build --packages-up-to perception_node
# Test fails, edit again
colcon build --packages-select perception_node
# Now rebuild downstream packages
colcon build --packages-above perception_node
Step 5: Configure Colcon Defaults
Create ~/.colcon/defaults.yaml:
build:
symlink-install: true
parallel-workers: 12 # Match your CPU
cmake-args:
- "-DCMAKE_BUILD_TYPE=RelWithDebInfo"
- "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON" # For IDE integration
event-handlers:
- console_cohesion+ # Better output formatting
test:
parallel-workers: 12
return-code-on-test-failure: true
Now you can just run:
colcon build # Uses your defaults
Step 6: Add Compilation Database for IDEs
# Build with compile_commands.json
colcon build --cmake-args -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
# Merge all compile commands
find build -name compile_commands.json -exec cat {} + > compile_commands.json
Why: VS Code, CLion, and clangd use this for accurate code completion and linting.
Step 7: Implement Smart Caching
Use ccache to cache compilation results:
# Install ccache
sudo apt install ccache
# Configure colcon to use it
export CC="ccache gcc"
export CXX="ccache g++"
# Add to ~/.bashrc
echo 'export CC="ccache gcc"' >> ~/.bashrc
echo 'export CXX="ccache g++"' >> ~/.bashrc
echo 'export CCACHE_DIR=$HOME/.ccache' >> ~/.bashrc
# Set cache size
ccache -M 10G
# Check cache stats
ccache -s
Expected on rebuild: Cache hit rate >80% after initial build, 3-5x faster rebuilds.
Verification
Test incremental build speed:
# Baseline full build
time colcon build
# Touch a low-level file
touch src/common_utils/src/utils.cpp
# Time incremental build
time colcon build
You should see:
- Full build: 5-8 minutes (was 15+)
- Incremental: 30-60 seconds (was 5+ minutes)
- CPU usage: 80-100% during builds
Check cache effectiveness:
ccache -s
You should see:
cache hit rate: 85%
Advanced: Workspace Profiles
Create multiple build configurations:
# Debug profile
colcon build \
--build-base build_debug \
--install-base install_debug \
--cmake-args -DCMAKE_BUILD_TYPE=Debug
# Release profile
colcon build \
--build-base build_release \
--install-base install_release \
--cmake-args -DCMAKE_BUILD_TYPE=Release
# Source the one you need
source install_debug/setup.bash # Development
source install_release/setup.bash # Testing
Use case: Keep optimized and debug builds side-by-side without rebuilding.
Advanced: Mixin Files for Reusable Configs
Create .colcon/mixin/my_project.mixin:
build:
fast:
symlink-install: true
parallel-workers: 12
cmake-args:
- "-DCMAKE_BUILD_TYPE=RelWithDebInfo"
production:
parallel-workers: 12
cmake-args:
- "-DCMAKE_BUILD_TYPE=Release"
- "-DCMAKE_CXX_FLAGS=-O3 -march=native"
debug:
cmake-args:
- "-DCMAKE_BUILD_TYPE=Debug"
- "-DCMAKE_CXX_FLAGS=-g -O0"
Usage:
# Install mixin
colcon mixin add my_project file://$HOME/.colcon/mixin/my_project.mixin
colcon mixin update my_project
# Use mixin
colcon build --mixin fast
colcon build --mixin production
Troubleshooting
Build Still Slow After Changes
Check parallel worker usage:
# During build, in another Terminal
htop
Fix: If CPU isn't maxed, increase --parallel-workers. If I/O is bottleneck (high wa in htop), you're disk-limited.
"Package X cannot be found" After Overlay
Symptom: Dev workspace can't find base workspace packages.
Fix:
# Source base BEFORE building dev
cd ~/ros2_ws/base_ws
source install/setup.bash
cd ~/ros2_ws/dev_ws
colcon build
Verify sourcing order:
echo $AMENT_PREFIX_PATH
# Should show: /home/user/ros2_ws/dev_ws/install:/home/user/ros2_ws/base_ws/install:/opt/ros/jazzy
Cache Misses with ccache
Check cache stats:
ccache -s
If hit rate <50%:
# Increase cache size
ccache -M 20G
# Clear and rebuild
ccache -C
colcon build --cmake-clean-cache
What You Learned
- Workspace overlays isolate stable code from active development
- Parallel builds need explicit core count configuration
- Selective building avoids unnecessary rebuilds
- ccache provides 3-5x speedup on incremental builds
- Proper dependency layering prevents cascade rebuilds
Limitations:
- Overlays add complexity for new team members
- ccache needs disk space (5-20GB typical)
- Cross-compilation requires different strategies
When NOT to use overlays:
- Small workspaces (<10 packages)
- Prototyping phase with frequent restructuring
- CI/CD systems (clean builds preferred)
Next steps:
- Set up Docker containers for reproducible builds
- Implement continuous integration with cached layers
- Profile build times with
colcon build --event-handlers console_cohesion+
Real-World Performance Gains
Before optimization:
- Full build: 18 minutes
- Incremental: 7 minutes
- CPU usage: 30%
After optimization:
- Full build: 6 minutes (67% faster)
- Incremental: 45 seconds (89% faster)
- CPU usage: 95%
Tested environment: 12-core AMD Ryzen, 32GB RAM, NVMe SSD, ROS 2 Jazzy, 35 packages
Quick Reference
# Daily development workflow
cd ~/ros2_ws/dev_ws
colcon build --packages-up-to my_package
source install/setup.bash
ros2 launch my_package test.launch.py
# After pulling changes
colcon build --packages-up-to-regex ".*" # Rebuild all with deps
# Check what will build
colcon list --packages-up-to my_package
# Clean rebuild single package
colcon build --packages-select my_package --cmake-clean-cache
# View dependency graph
colcon graph --dot | dot -Tpng -o graph.png && xdg-open graph.png
Tested on ROS 2 Jazzy (Ubuntu 24.04), ROS 2 Iron (Ubuntu 22.04), colcon-core 0.16+