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.
Hello again! Let’s take a look at how to create ROS 2 packages using ROS 2 Humble. We’ll follow along with this official tutorial.
Let’s dive in.
What are packages? Link to heading
Packages are nicely organized chunks of ROS 2 code. They help make it easier to install new ROS code and share it with ohters. The build system used to create and install packages is called ament
. There are two build types officially supported by the build system:
- CMake
- Python
Technically there are others also, but these two are most widely used.
Depending on the build system you use, there will be a different set of minimum required files that will make up a ROS package. Here are those files (quoted directly from the documentation).
CMake packages Link to heading
The reuqired files are:
CMakeLists.txt
file that describes how to build the code within the packageinclude/<package_name>
directory containing the public headers for the packagepackage.xml
file containing meta information about the packagesrc
directory containing the source code for the package
Which means the simplest possible package structure would be:
my_package/
CMakeLists.txt
include/my_package/
package.xml
src/
Python packages Link to heading
The required files are:
package.xml
file containing meta information about the packageresource/<package_name>
marker file for the packagesetup.cfg
is required when a package has executables, so ros2 run can find themsetup.py
containing instructions for how to install the package<package_name>
which is a directory with the same name as your package, used by ROS 2 tools to find your package, contains__init__.py
Which means the simplest possible package structure would be:
my_package/
package.xml
resource/my_package
setup.cfg
setup.py
my_package/
You can have as many packages as you like in one workspace and you can even use different build systems (you can mix and match the CMake and Python build types…as each package in the workspace will be built independently according to its own configuration). However, you cannot next packages.
Pro tip: It’s a best practice to put your packages into a src
directory within your workspace.
Creating a package Link to heading
If you followed along with my last ROS 2 blog post yous should already have a workspace called ros2_ws
set up. You’ll need one to continue, so if you haven’t followed the steps in that previous blog post, do so now.
Ok, let’s move into ros2_ws/src
:
cd ~/ros2_ws/src
Let’s actually create two packages, so we can experiment with both CMake and Python.
Create a CMake package Link to heading
ros2 pkg create --build-type ament_cmake --license Apache-2.0 --node-name my_node_cmake my_package_cmake
Which should produce some output like this:
my_node_cmake my_package_cmake
going to create a new package
package name: my_package_cmake
destination directory: /home/ubuntu/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['ubuntu <ubuntu@todo.todo>']
licenses: ['Apache-2.0']
build type: ament_cmake
dependencies: []
node_name: my_node_cmake
creating folder ./my_package_cmake
creating ./my_package_cmake/package.xml
creating source and include folder
creating folder ./my_package_cmake/src
creating folder ./my_package_cmake/include/my_package_cmake
creating ./my_package_cmake/CMakeLists.txt
creating ./my_package_cmake/src/my_node_cmake.cpp
Create a Python package Link to heading
ros2 pkg create --build-type ament_python --license Apache-2.0 --node-name my_node_python my_package_python
Which should produce some output like this:
my_node_python my_package_python
going to create a new package
package name: my_package_python
destination directory: /home/ubuntu/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['ubuntu <ubuntu@todo.todo>']
licenses: ['Apache-2.0']
build type: ament_python
dependencies: []
node_name: my_node_python
creating folder ./my_package_python
creating ./my_package_python/package.xml
creating source folder
creating folder ./my_package_python/my_package_python
creating ./my_package_python/setup.py
creating ./my_package_python/setup.cfg
creating folder ./my_package_python/resource
creating ./my_package_python/resource/my_package_python
creating ./my_package_python/my_package_python/__init__.py
creating folder ./my_package_python/test
creating ./my_package_python/test/test_copyright.py
creating ./my_package_python/test/test_flake8.py
creating ./my_package_python/test/test_pep257.py
creating ./my_package_python/my_package_python/my_node_python.py
Note: The --node-name
flag has the effect of producing a simple “hello world” executable as part of the newly created package.
Building packages Link to heading
Because we have placed our packages together into a single workspace and our best-practice src
directory, we can run colcon build
from the root of the workspace to build all the packages in the workspace at once:
cd ~/ros2_ws
colcon build
In my case I get some warnings about my packages overriding packages that are part of the underlay at /opt/ros/humble
. This is because my src
directory still contains the ros_tutorials
package from when I created my workspace in my last blog post. That package rebuilds turtle
(which is already part of /opt/ros/humble
in this case, hence the warning). Note that it is completely OK to override a package by building a modified version of it as part of your own workspace (the overlay). This gives you a lot of flexibility as you can override or modify built-in functionality as needed without affecting the underlay directly.
Here’s my output from colcon build
:
[0.961s] WARNING:colcon.colcon_core.package_selection:Some selected packages are already built in one or more underlay workspaces:
'turtlesim' is in: /opt/ros/humble
If a package in a merged underlay workspace is overridden and it installs headers, then all packages in the overlay must sort their include directories by workspace order. Failure to do so may result in build failures or undefined behavior at run time.
If the overridden package is used by another package in any underlay, then the overriding package in the overlay must be API and ABI compatible or undefined behavior at run time may occur.
If you understand the risks and want to override a package anyways, add the following to the command line:
--allow-overriding turtlesim
This may be promoted to an error in a future release of colcon-override-check.
Starting >>> my_package_cmake
Starting >>> my_package_python
Starting >>> turtlesim
Finished <<< my_package_python [1.72s]
Finished <<< my_package_cmake [1.90s]
Finished <<< turtlesim [1.91s]
Summary: 3 packages finished [2.67s]
Building individual packages in the workspace Link to heading
Let’s say we have now modified just one of our packages and we want to rebuild it, but we don’t want to waste time rebuilding all the other, unchanged packages. How can we do this? We can use the --package-select
flag, like this:
colcon build --packages-select my_package_cmake
Which of course gives this output (as it only rebuilds one package):
Starting >>> my_package_cmake
Finished <<< my_package_cmake [0.16s]
Summary: 1 package finished [0.49s]
So what do we do if we want to run our freshly built packages with ros2 run
? Well, we need to open up a new terminal, source our underlay (ROS 2 humble’s base packages), then source our overlay (our workspace). Here’s how. In a new terminal, type:
cd ~/ros2_ws
source /opt/ros/humble/setup.bash
source install/local_setup.bash
We can now try running one of our new packages with:
ros2 run my_package_cmake my_node_cmake
Which gives this output:
hello world my_package_cmake package
Note: What we actually did was run a node that was part of our package. We have to run a ROS component created by the package. We can’t just run the package itself.
To perform the same process for our Python package, we’d simply run:
ros2 run my_package_python my_node_python
Which outputs:
Hi from my_package_python.
Looking at what we’ve built Link to heading
Let’s take a closer look at the contents of each package, using the tree
command.
Here’s a look inside the CMake package:
.
├── CMakeLists.txt
├── include
│ └── my_package_cmake
├── LICENSE
├── package.xml
└── src
└── my_node_cmake.cpp
3 directories, 4 files
And here’s a look inside the Python package:
.
├── LICENSE
├── my_package_python
│ ├── __init__.py
│ └── my_node_python.py
├── package.xml
├── resource
│ └── my_package_python
├── setup.cfg
├── setup.py
└── test
├── test_copyright.py
├── test_flake8.py
└── test_pep257.py
3 directories, 10 files
Both packages havea package.xml
file. This file includes metadata about the package, such as contact details for the package maintainer (you!) and lists of dependencies for the package.
Here’s the packages.xml
file for the CMake package:
<?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>my_package_cmake</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="ubuntu@todo.todo">ubuntu</maintainer>
<license>Apache-2.0</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>
ubuntu@ip-10
And here’s packages.xml
for the Python package:
<?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>my_package_python</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="ubuntu@todo.todo">ubuntu</maintainer>
<license>Apache-2.0</license>
<test_depend>ament_copyright</test_depend>
<test_depend>ament_flake8</test_depend>
<test_depend>ament_pep257</test_depend>
<test_depend>python3-pytest</test_depend>
<export>
<build_type>ament_python</build_type>
</export>
</package>
An important note for Python packages! There are some fields in setup.py
which MUST match package.xml
EXACTLY. Quoting from the official docs:
The setup.py file contains the same description, maintainer and license fields as package.xml, so you need to set those as well. They need to match exactly in both files. The version and name (package_name) also need to match exactly, and should be automatically populated in both files.
That’s it! See you next time, where we’ll look at writing a publisher/subscriber in C++.