Note: This is part of a series where I follow along with the ROS 2 Humble and Gazebo Fortress tutorials and demos. This is not original content.

In today’s post we’ll follow along with this tutorial from the official ROS 2 Humble docs, which will show us how to create and build our own workspace using the ROS 2 build tool, colcon. Let’s dive in.

Previously, ROS used a build system called catkin and a tool suite called ament_tools. The full list of catkin/ament tools was:

  • catkin_make
  • catkin_make_isolated
  • catkin_tools
  • ament_tools

To simplify and streamline the build process, these tools have been superceded by colcon. Before we can use colcon, we need to make sure we have the prerequisites installed:

sudo apt install python3-colcon-common-extensions

Of course we need to have ROS 2 installed as well, but I’m assuming you’ve already done that! If not, you can take a look at this earlier blog post.

Workspace structure Link to heading

colcon will build packages from source using a ROS workspace. A ROS workspace is just a directory which contains code (and other artefacts) used to build ROS packages. ROS workspaces will usually contain a src directory. This is where the source code of the ROS package is stored.

In addition to src, colcon will create the following directories:

  • build, which will hold intermediate files (usually one subfolder will be created for each package that is part of the workspace)
  • install, which is where completed (built) packages will be installed
  • log, which is where logging information will be stored each time colcon is run

Note: Unlike catkin, the colcon build tool does not create a devel directory.

Create a workspace Link to heading

We start by creating a workspace. A common (default) name for ROS 2 workspaces is ros2_ws so let’s use that:

mkdir -p ~/ros2_ws/src
cd ~/ros2_ws

Awesome. We now have a workspace directory ros2_ws which contains a single empty subdirectory, src. We need to add some source code to src so that colcon has something to build. Rather than do the build ourselves, we’ll use an example that’s been built for us:

git clone https://github.com/ros2/examples src/examples -b humble

If we run the handy tree -L 3 command (install with sudo apt-get install tree if you don’t already have it) from inside ros2_ws we should see something like this:

ubuntu@ip-10-0-3-184:~/ros2_ws$ tree -L 3
.
└── src
    └── examples
        ├── CONTRIBUTING.md
        ├── launch_testing
        ├── LICENSE
        ├── rclcpp
        ├── rclpy
        └── README.md

5 directories, 3 files

“Underlays” Link to heading

We don’t want to build our package in isolation. Rather, we want to be able to depend on pre-built ROS 2 packages and functionality, so we don’t have to “reinvent the wheel”. To do this, we need to source an underlay.

Our new ros2_ws workspace is what we call an overlay….it will contain additional ROS 2 packages we use alongside our underlay (the packages that are part of the default ROS 2 humble installation).

Source your underlay with:

source /opt/ros/humble/setup.bash

Now it’s time to run colcon build, which will biuld our package.

Building with colcon build Link to heading

Note: Unlike catkin which had a concept of a devel directory, some colcon build types such as ament_cmake don’t support this and actually require packages to be installed when they are built. To allow us to continue altering packges in the source space even after the package is built (i.e. for making changes to scripts in non-compiled languages like Python), we can add the --symlink-install flag, like this:

colcon build --symlink-install

Note: The command above sould be executed from the root of our workspace, ros2_ws. The build process will output a lot of messages. If it succeeds, you’ll see something like this:

Successful build messages

After the build, we should see build, install, and log directories. Here I have run tree -L 1 to get a look at these new directories:

ubuntu@ip-10-0-3-184:~/ros2_ws$ tree -L 1
.
├── build
├── install
├── log
└── src

4 directories, 0 files

Running tests Link to heading

It’s always a good idea to run any tests you have defined:

colcon test

You should see some output like this:

Successful test messages

Sourcing the new environment Link to heading

Before we can use our shiny new packages, we need to add them to our executable and/or library paths. The build process will have created new setup.bat and setup.bash scripts in the install director, so we need to source those:

source install/setup.bash

Great, now we’re ready to use our packages.

Make sure you run that source install/setup.bash in two separate terminals, then (again in two separate terminals), run:

ros2 run examples_rclcpp_minimal_subscriber subscriber_member_function

and

ros2 run examples_rclcpp_minimal_publisher publisher_member_function

Once you’ve run both of these commands, the subscriber should start receiving messages from the publisher. The subscriber terminal will show something like this:

Subscriber terminal showing received messages

And the terminal where the publisher is running will show:

Publisher terminal showing sent messages

Creating a package Link to heading

So far we have built the code and tested it, but haven’t actually packaged it up as a ROS package. We can do that useing the package.xml specification defined in REP 149 (“format 2” is also supported).

Again, a note about build types (this matters when creating the package). The supported build types are:

  • ament_cmake
  • ament_python
  • Pure cmake

For an example of a package built with ament_python, take a look at ament_index_python_package. That package uses setup.py as its primary entry point for the build (a Python script).

The demo_nodes_cpp package, on the other hand, uses the ament_cmake built type, and uses CMake to perform the actual build.

For convenience, we can just create packges with ros2 pkg create which will create a new package from a template.

Note: Users of the catkin build tool would use catkin_create_package instead.

(Optional) create a package Link to heading

Let’s test out the package creation process, in a new, isolated directory. We should leave ros2_ws first:

cd ..
ros2 pkg create myfirstpackage

If everything worked well, you should see some output like this:

going to create a new package
package name: myfirstpackage
destination directory: /home/ubuntu
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['ubuntu <ubuntu@todo.todo>']
licenses: ['TODO: License declaration']
build type: ament_cmake
dependencies: []
creating folder ./myfirstpackage
creating ./myfirstpackage/package.xml
creating source and include folder
creating folder ./myfirstpackage/src
creating folder ./myfirstpackage/include/myfirstpackage
creating ./myfirstpackage/CMakeLists.txt

[WARNING]: Unknown license 'TODO: License declaration'.  This has been set in the package.xml, but no LICENSE file has been created.
It is recommended to use one of the ament license identitifers:
Apache-2.0
BSL-1.0
BSD-2.0
BSD-2-Clause
BSD-3-Clause
GPL-3.0-only
LGPL-3.0-only
MIT
MIT-0

You should now have a directory called myfirstpackage in the same directory where you created ros2_ws. Taking a look inside myfirstpackage, you should see:

ubuntu@ip-10-0-3-184:~/myfirstpackage$ tree
.
├── CMakeLists.txt
├── include
│   └── myfirstpackage
├── package.xml
└── src

3 directories, 2 files

src and include directories have been created for us automatically, and a CMakeLists.txt and package.xml package spec file have also been set up. If you take a look inside the package.xml file, you’ll see that placeholder values have been inserted for most fields:

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>myfirstpackage</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintainer email="ubuntu@todo.todo">ubuntu</maintainer>
  <license>TODO: License declaration</license>

  <buildtool_depend>ament_cmake</buildtool_depend>

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

By default, the command has created an ament_cmake package.

Set up colcon_cd Link to heading

Now, back to ros2_ws. We can use colcon_cd to quickly change the current working directory of our shell (terminal) to the directory of a package. This is handy when we need to look at the code for a specific package but don’t want to type in long or cumbersome paths. This also saves us the difficulty of needing to know where every single ROS package is located! colcon_cd will take us right where we need to go.

To get this working we’ll need to add two lines to our .bashrc file:

echo "source /usr/share/colcon_cd/function/colcon_cd.sh" >> ~/.bashrc
echo "export _colcon_cd_root=/opt/ros/humble/" >> ~/.bashrc

Setting up colcon tab completion Link to heading

The colcon command supports tab completion in bash-like shells. You need to have the colcon-argcomplete package installed for this to work. See the docs for complete setup instructions.

In my case, I also want to enable autocomplete for colcon_cd, so I run the two commands below:

echo "source /usr/share/colcon_argcomplete/hook/colcon-argcomplete.bash" >> ~/.bashrc
echo "source /usr/share/colcon_cd/function/colcon_cd-argcomplete.bash" >> ~/.bashrc

And of course restart your shell (or run source ~/.bashrc) to make sure the changes take effect.

Great! Now both colcon and colcon_cd should offer autocompletions when you hit the Tab key. Now when I type in colcon_cd and hit Tab I see a list of completions:

ubuntu@ip-10-0-3-184:~$ colcon_cd 
base_control_ros2                       examples_rclpy_executors                --reset
--base-paths                            examples_rclpy_guard_conditions         robot_description
costmap_converter                       examples_rclpy_minimal_action_client    robot_navigation_ros2
costmap_converter_msgs                  examples_rclpy_minimal_action_server    robot_vision_ros2
examples_rclcpp_async_client            examples_rclpy_minimal_client           roscpp_tutorials
examples_rclcpp_cbg_executor            examples_rclpy_minimal_publisher        rospy_tutorials
examples_rclcpp_minimal_action_client   examples_rclpy_minimal_service          ros_tutorials
examples_rclcpp_minimal_action_server   examples_rclpy_minimal_subscriber       sclidar_ros2
examples_rclcpp_minimal_client          examples_rclpy_pointcloud_publisher     --set
examples_rclcpp_minimal_composition     -h                                      slam_gmapping
examples_rclcpp_minimal_publisher       --help                                  sllidar_ros2
examples_rclcpp_minimal_service         launch_testing_examples                 teb_local_planner
examples_rclcpp_minimal_subscriber      myfirstpackage                          teb_msgs
examples_rclcpp_minimal_timer           nanocarpro_description                  turtlesim
examples_rclcpp_multithreaded_executor  nvilidar_ros2                           ydlidar_ros2_driver
examples_rclcpp_wait_set                openslam_gmapping 

Let’s try moving to the directory where the examples_rclcpp_minimal_publisher package lives:

colcon_cd examples_rclcpp_minimal_publisher

After the command executes, running pwd shows:

/opt/ros/humble/share/examples_rclcpp_minimal_publisher

So I have moved to the location of this package.

In our next post we’ll take a look at creating a more complete workspace, following along with this doc.