Working with ROS2 packages: Part 1

21 Feb 2021 - John Z. Li

Recall that a ROS workspace is nothing but a collection of ROS packages. Technically, a ROS workspace doesn’t have to be a directory on the OS’s filesystem. Though it is a convention to start with a new and empty directory each time you want to create a new workspace. In this way, we can just use the name of the directory as the name of workspace.

Like the conception of inheritance in Object Oriented Programming, with ROS, we can develop a new workspace on top of another workspace. Notice that the ROS documentation does not call this inheritance. Instead, the “inherited” workspace is called the underlay, and the inheriting workspace is called the overlay. To work on top of an underlay, source the setup script of that workspace before starting work with the overlay. For example, one can use the following to start an overlay

mkdir -p  ~/ros_examples/ex1/src
# if you are using bash, use setup.bash
source /opt/ros/dashing/setup.zsh

Now the directory of ~/ros_examples/ex1 will be the root of the overlay.

The structure of a ROS package

cd into the root of the newly created ROS workspace, that is, in our case, the ~/ros_examples/ex1 directory. All source code of each package the workspace is going to contain is located in the src subfolder of the workspace. Typically, each package occupies its own subfolder under src. If one would like to group packages together, he can contain different one or more packages in different subfolder of src. For example, the following is perfectly OK:

|src
├──subfolder1
|   ├──package1
|   ├──package2
|──subfolder2
    ├──package3
    ├──package4
    ├──|package5

The above will be effectively the same as the below from the perspective of users:

|src
├──package1
├──package2
├──package3
├──package4
├──package5

To understand the structure of a ROS package, we start with some packages that are created by others.

cd ~/ros_examples/ex1/src
git clone git clone https://github.com/ros/ros_tutorials.git -b dashing-devel

The above create a subfolder called ros_tutorials in src, and under the subfolder populated four packages:

|src/ros_tutorials
├── roscpp_tutorials
├── rospy_tutorials
├── ros_tutorials
└── turtlesim

The structure of a C++ ROS2 project.

The “turtlesim” subfolder is a C++ ROS package. cd into the folder to check its structure. Note that the “turtlesim” is the package’s root.

A ROS2 C++ package usually contains the following:

package.xml
(optional) colcon.pkg
CMakeList.txt
(optional) README
(optional) CHANGELOG
(optional) COLCON_IGNORE
C++ source and headers folder(s)

The manifesto file of a package.

The “package.xml” file is called the ROS package manifest of the package. It is required that each ROS package should have exactly one of the manifest. The file contains the following information:

Various tools rely on this file to work correctly, like rosdep, colcon or bloom. The manifesto has standardized semantics REP 127 manifest format, REP 140 manifest format and REP 149 manifest format.

The colcon.pkg file

A colcon.pkg file is used in two cases:

The documentation of the format of the file can be found here. Note that if this file exists, it must exist in the root of the package. This means, either you have write access to the project, so you can add a colcon.pkg file to its root, or you have to fork a project and add it to the forked project and update it when the upstream is update. If you don’t have write access to the project and you don’t want to maintain a forked version of a project, here is another way to make it work with ROS.

Putting a package_name.meta file outside the source tree, and use colcon --metas <path/to/file> <subcommand> to build, test, and install the package.

The CMakeList.txt file

This file is supposed to to consumed by ament_cmake, which is CMake with some features added to facilitate ROS package development.

Other files.

The “README” and “CHANGELOG” files don’t need explanation. They could be plain text files, of markdown format or of reStructureText format.

The structure of a Python ROS2 project

A python ROS2 project contains the following files/directories:

package.xml
(optional) colcon.pkg
setup.py
setup.cfg
(optional) README
(optional) CHANGELOG
(optional) COLCON_IGNORE
/<package_name>

Note that according to PEP 517, PEP 518 and PEP 621, future Python projects might use a pyproject.toml file instead of setup.py and setup.cfg.

Create a new package from command line

C++ ROS2 packages

For C++ ROS2 packages, in the “src” subfolder of the workspace, run

ros2 pkg create --build-type ament_cmake <package_name>

For example, the following

cd ~/ros_examples/ex1/src
ros2 pkg create --build-type ament_cmake my_cpp_ros_project

populates an empty package with the following directory structure:

my_cpp_ros_project
├── CMakeLists.txt
├── include
│   └── my_cpp_ros_project
├── package.xml
└── src

Python ROS2 packages

For Python ROS2 packages, in the “src” subfolder of the workspace, run

ros2 pkg create --build-type ament_python <package_name>

For example, the following

cd ~/ros_examples/ex1/src
ros2 pkg create --build-type ament_python my_python_ros_project

populates an empty package with the following directory structure:

my_python_ros_project
├── my_python_ros_project
│   └── __init__.py
├── package.xml
├── resource
│   └── my_python_ros_project
├── setup.cfg
├── setup.py
└── test
    ├── test_copyright.py
    ├── test_flake8.py
    └── test_pep257.py

Create a package with given node name

If the package is supposed to implement a node, we can use the --node-name option along with ros2 pkg create. For example

# create a cpp ROS2 project with node name being "my_node".
ros2 pkg create --build-type ament_cmake --node-name my_node my_cpp_ros_project_new
# create a Python ROS2 project with node name being "my_node"
ros2 pkg create --build-type ament_python --node-name my_node my_python_ros_project_new

Build ROS2 packages

Build a workspace

To build a workspace, cd into the corresponding directory, and run colcon build. Foe example

cd ~/ros_examples/ex1
colcon build

will build all the projects in workspace ex1. This will create another three sub-directories:

To accelerating building, one can use the following to build the workspace instead:

colcon build --symlink-install

The --symlink-install option means that when installing the package, use symbolic links when possible. This avoids the cost of file copying. Without this option, even the projects of a workspace is only mildly modified, many files will be copied over again with each building.

Build only one project

Usually, when we work in a ROS2 workspace, we only work on one package at a time. It does not make sense to re-build every package in the workspace every time. Thus it is better to only build one (or more) selected package. Use the below command to build only selected packages.

colcon build --packages-select my_package

Source the built workspace

After successfully building a workspace, one has to make the workspace be known by ROS. To do this, one has to source the relative “setup” scripts. For example,

# if you want to use the "underlay" and the "overlay" at the same time
# use the *.bash version if you are using bash
source ~/ros_examples/ex1/install/setup.zsh
# if you only want to use the workspace where the script in in.
source ~/ros_examples/ex1/install/setup.zsh

The latter is equivalent to sourcing all the underlays of the workspace, from the first in the chain to the current workspace where the script is in.

Another thing to know is that, just like inheritance in OOP, if there is a package in the overlay which has the same name with a package in the underlay, when calling the package, the one in the overlay will be used. In other words, the package in the overlay will shadow that with the same name in the underlay. To illustrate this

vim  ~/ros_examples/ex1/src/ros_tutorials/turtlesim/src/turtle_frame.cpp

and replace setWindowTitle("TurtleSim"); in function TurtleFrame to setWindowTitle("My TurtleSim");. Then do the following:

cd ~/ros_examples/ex1
colcon build
source ./install/setup.zsh
ros2 run turtlesim turtlesim_node

You will find that the title of the TurtleSim window has changed to “My TurtleSim”. One can use

ros2 pkg list

to check which package has been included currently.

Run a package with ros2 run

Recall that

ros2 run <package_name> <executable_name>

will run the executable with the given name in that package. If a package contains multiple executables, one can run each of them by specifying its name.

If a package is created with the --node-nameoption, one can launch the node with

ros2 run <package_name> <node_name>

Update relevant files if you want to publish your packages.

Always remember to update relevant files, such as the manifest file, README, CHANGELOG, etc. If you want to publish your packages. All information should stay sync with each release.