catkin vs colcon: The Mistakes ROS 1 Teams Make When Porting a Workspace to ROS 2
A ROS 2 workspace built with colcon looks familiar at first glance: a src directory full of packages. The output is organised differently, though. colcon creates build, install and log next to src and, unlike catkin, no devel directory. Packages must be installed and are used by sourcing install/setup.bash. Add the package.xml format 2 rules, the switch from catkin to ament_cmake, and an apt conflict that can block ROS 1 packages entirely, and it is easy to lose a day to avoidable problems.
This guide is for teams in UK universities, research centres and engineering firms with ROS 1 code to bring across. It relies on the migration guides and colcon tutorials in the ROS 2 Lyrical documentation, which are searchable in the ROS 2 documentation base.
Why colcon behaves differently
The documentation presents colcon as an iteration on the earlier ROS build tools: catkin_make, catkin_make_isolated, catkin_tools and ament_tools. It performs out-of-source builds and supports several build types. The recommended ones are ament_cmake and ament_python, and pure cmake packages are supported too. Because ament_cmake has no concept of a devel space and requires packages to be installed, colcon has to offer other ways to iterate quickly.
The two workspaces side by side
catkin (ROS 1) vs colcon (ROS 2), according to the ROS 2 documentation
| Aspect | catkin | colcon |
|---|---|---|
| Build command | catkin_make, catkin_make_isolated or catkin build | colcon build |
| Output directories | Includes a devel space | build, install and log, with no devel directory |
| Environment script | devel/setup.bash | install/setup.bash or install/local_setup.bash |
| Fast iteration on scripts | Run from devel | colcon build --symlink-install |
| Create a package | catkin_create_package | ros2 pkg create |
| package.xml | Format 1 accepted | Format 2 or higher (REP 149 used by colcon) |
| CMake helper | catkin_package() | ament_package() and ament_export_targets() |
Seven mistakes and how to avoid them
- Treating a missing devel folder as a failed build. It is expected. Look in install, and use --symlink-install so edits to Python files and other non-compiled resources apply without rebuilding.
- Sourcing the overlay in the build terminal. The tutorial says to open a new terminal before sourcing an overlay, because sourcing where you built, or building where an overlay is sourced, may create complex issues. Source the underlay (source /opt/ros/lyrical/setup.bash), then source install/local_setup.bash.
- Confusing setup and local_setup. local_setup adds only the overlay's packages. setup adds the overlay and the underlay it was built against.
- Rebuilding everything. Use colcon build --packages-up-to <pkg> to build one package and its dependencies, or --packages-select <pkg> to build only the named package.
- Running a parallel build on a single-board computer. On CPU-, RAM- and I/O-limited systems such as a Raspberry Pi, colcon build may freeze the screen and mouse. Add --executor sequential.
- Forgetting Windows specifics. Windows builds use --merge-install because of path-length limits, need a Visual Studio environment, and need administrator rights or developer mode for symbolic links.
- Relicensing during the port. ROS 2 recommends Apache 2.0 for new projects. When migrating, though, the docs advise keeping the existing licence, since changing it requires permission from all contributors.
Bringing package.xml up to format 2
ROS 2 only supports package.xml format 2 and above. If the file opens with <package> or <package format="1">, set format="2", then work through the dependency tags:
- run_depend is no longer allowed. Replace it with exec_depend for dependencies needed at run time and build_export_depend for dependencies that packages building against yours will need. The guide suggests using both if you are unsure.
- Test-only dependencies previously declared as build_depend should become test_depend.
- Documentation tools go into doc_depend.
- Three identical build_depend, build_export_depend and exec_depend tags collapse into one depend.
A useful tip from the guide: keep a ROS 1 installation to hand while converting. ROS 1 supports all package.xml formats, so you can check that the new file is valid by building and testing it with catkin_make, catkin_make_isolated or the catkin tool before moving it across.
Porting CMakeLists.txt from catkin to ament_cmake
In package.xml, remove the catkin buildtool dependency, add ament_cmake_ros, and declare <build_type>ament_cmake</build_type> under export. In CMakeLists.txt:
- Replace find_package(catkin REQUIRED COMPONENTS foo bar std_msgs) with one find_package() per dependency, starting with find_package(ament_cmake_ros REQUIRED).
- Delete include_directories() calls and add target_include_directories() for each library.
- Change target_link_libraries(my_library ${catkin_LIBRARIES} ...) to modern targets such as std_msgs::std_msgs. Choose PUBLIC if downstream users need the dependency and PRIVATE if it is only used internally.
- Replace catkin_package(LIBRARIES ...) with install(TARGETS my_library EXPORT export_my_package ...) plus ament_export_targets() using the same export name.
- Map the CATKIN_*_DESTINATION variables. For example, CATKIN_PACKAGE_SHARE_DESTINATION becomes share/${PROJECT_NAME} and CATKIN_PACKAGE_LIB_DESTINATION becomes lib.
- Add ament_package() at the bottom of the file.
After the build: launch files and parameters
A clean colcon build is only half the job. ROS 1 launch files were always XML, whereas ROS 2 accepts XML, YAML and Python. The migration guide recommends XML or YAML for typical cases and keeps Python for when you need more flexibility. Porting an XML launch file is mostly mechanical. The node attribute type becomes exec, ns becomes namespace, and required="true" becomes on_exit="shutdown". machine, respawn_delay and clear_params have no equivalent.
The bigger conceptual shift is that there is no roscore, so there is no global parameter server either. Each node owns its parameters, which are configurable at run time through ROS services. YAML parameter files must name the node and place values under ros__parameters. Code that read shared values from the old parameter server needs redesigning. The documentation's fallback is a dedicated node acting as a blackboard, and ROS 2 provides one in demo_nodes_cpp: ros2 run demo_nodes_cpp parameter_blackboard.
Finally, re-establish your test routine before porting further. Resolve dependencies with rosdep install -i --from-path src --rosdistro lyrical -y, run colcon test across the workspace, and keep test-only dependencies inside an if(BUILD_TESTING) block in CMake, as the package.xml guide advises.
The apt trap: catkin-pkg-modules and ros1_bridge
Teams that try to keep ROS 1 and ROS 2 on one Ubuntu 22.04 machine hit a specific conflict. The documentation explains that Humble on Jammy was the first ROS 2 release on a platform with no official ROS 1 release, and that ROS 1 is available there only as upstream Debian and Ubuntu packages. The version of catkin-pkg-modules in the Ubuntu repository conflicts with the one in the ROS 2 package repository. While the ROS 2 apt source is present, no ROS 1 packages can be installed, and apt reports that ros-core-dev depends on catkin, which is not installable.
The documented route is to remove packages.ros.org from your apt sources (for instance /etc/apt/sources.list.d/ros2.list) and build ROS 2 from source. Since the ROS 2 apt repository is no longer used, install colcon from PyPI with python3 -m pip install -U colcon-common-extensions vcstool. Then install ros-core-dev from Ubuntu, clone ros1_bridge into its own workspace (~/ros1_bridge/src), source the ROS 2 workspace and run colcon build. The guide is specific to Ubuntu 22.04. The base does not describe an equivalent procedure for Lyrical on Ubuntu 26.04, so confirm current ros1_bridge support before committing to a bridged architecture.
Port from the leaves upwards
The migration guide's prerequisite is that every dependency of a package must already be available in ROS 2. Dependency names resolved through rosdep should not need to change, but some released packages have different names in ROS 2, so check them before you start.
Check a migration detail in seconds
Kopik's ROS 2 base indexes the official migration guides, colcon tutorials and release notes, and quotes the passage behind every answer. Try “I'm migrating a ROS 1 package.xml that uses <run_depend> tags. What do I replace them with for ROS 2's format 2?”
Other expert bases are listed in the catalogue, and the ROS 2 base can also be wired into internal tools through the developer API.
Frequently asked questions
Is colcon the same as catkin_tools?
No. The documentation describes colcon as an iteration on catkin_make, catkin_make_isolated, catkin_tools and ament_tools. It builds out of source and creates build, install and log directories, with no devel directory.
Which build types does colcon support?
The recommended build types are ament_cmake and ament_python, and pure cmake packages are also supported.
Should I change my package's licence to Apache 2.0 when moving to ROS 2?
Apache 2.0 is recommended for new projects, but for migrated packages the documentation recommends keeping the existing licence, because changing it requires permission from all contributors.
How do I install ROS 1 and ROS 2 together on Ubuntu 22.04 for ros1_bridge?
The documented approach removes the ROS 2 apt source to avoid the catkin-pkg-modules conflict, builds ROS 2 from source, installs ROS 1 from Ubuntu's ros-core-dev package, and builds ros1_bridge in a separate colcon workspace.
What happens to metapackages in ROS 2?
ROS 2 has no special metapackage type. Remove the <metapackage /> tag, and the package becomes a regular package that contains only runtime dependencies.
How do I run the tests of a single package with colcon?
Run colcon test --packages-select YOUR_PKG_NAME --ctest-args -R YOUR_TEST_IN_PKG for a CMake test, or colcon test on its own for the whole workspace. On Windows, add --merge-install if you built with it.
Get the Kopik newsletter
New knowledge bases, RAG guides and product news. One email every week or two, unsubscribe in one click.
By subscribing you agree to receive our newsletter. We never share your address.