8.2. Creating a ROS 2 package#
Now let’s try to create a new ROS package using either CMake or Python, and run its executable.
Recall that a ROS 2 package is an organizational unit for ROS 2 code. If you want to be able to install your code in a workspace or share it with others, then you’ll need it organized in a package. With packages, you can release your ROS 2 work and allow others to build and use it easily.
You can create a package using either CMake or Python, which are officially supported, though other build types do exist. Each package can use a different language, and each language has its specific building instructions and procedures to integrate the code as a proper ROS 2 package. Package creation in ROS 2 therefore requires a build system that combines various tools, depending on the programming language. Here is a quick overview of the build system:
ROS 2’s standard meta-build tool is
colcon
. It manages the whole build process of all packages in your workspace, and will be the main tool you as user will invoke.colcon
ensures that all packages are built in the correct topological order to satisfy any package dependencies (e.g. if package B depends on package A being available,colcon
will ensure A is built before B). However,colcon
does not actually build the packages itself, instead it will in turn invoke the specific build tools that each package specifies itself.A build tool is responsible for building one package at a time. It depends on the programming language, e.g. for Python it uses Python’s
setuptools
and for C++ it uses CMake.Since
colcon
is a generic tool that could be used for non-ROS development, it relies on build helpers to configure the build tools and to integrate them to build proper ROS 2 packages. The build helper for Python is calledament_python
and for C++ it is calledament_cpp
.
This may seem daunting at first,
but if all packages are configured correctly you will just need to use one top-level colcon buid
instruction
to build your whole workspace.
Build errors or log output from the build system might originate from any of these tools
so being aware if their relation is important.
8.2.1. Structure of a package#
Each package that you want to build in your workspace will
be contained in a directory with the package name, placed inside your workspace’s src/
directory.
Irrespective of the used programming language, each ROS 2 package should have a separate package.xml
file
that contains meta information for ROS about the package,
such as the package’s maintainer, (open source) license, and package dependenies.
In addition to package.xml
, ROS 2 Python and CMake packages each have their own minimum required
contents:
resource/<package_name>
marker file for the packagesetup.cfg
is required when a package has executables, soros2 run
can find themsetup.py
containing instructions for how to install the package<package_name>
- a directory with the same name as your package, used by ROS 2 tools to find your package, contains__init__.py
CMakeLists.txt
file that describes how to build the code within the packageinclude/<package_name>
directory containing the public headers for the packagesrc
directory containing the source code for the package
So, the simplest possible package may have a file structure that looks like:
my_package/
package.xml
resource/my_package
setup.cfg
setup.py
my_package/
my_package/
package.xml
CMakeLists.txt
include/my_package/
src/
8.2.2. Packages in a workspace#
A single workspace can contain as many packages as you want, each in their own folder. You can also have packages of different build types in one workspace (CMake, Python, etc.). You cannot have nested packages.
Best practice is to have a src
folder within your workspace, and to
create your packages in there. This keeps the top level of the workspace
“clean”.
A trivial workspace might look like:
workspace_folder/
src/
cpp_package_1/
CMakeLists.txt
include/cpp_package_1/
package.xml
src/
py_package_1/
package.xml
resource/py_package_1
setup.cfg
setup.py
py_package_1/
...
cpp_package_n/
CMakeLists.txt
include/cpp_package_n/
package.xml
src/
8.2.3. Create a new package#
First, source your ROS 2 installation,
and then use the ros2_ws
workspace you created in the
previous tutorial for your new package.
Make sure you are in the src
sub directory before running the package creation command:
cd ~/ros2_ws/src
Note
Rather than creating all required package configuration files one-by-one manually,
the ros2 pkg
CLI tool we discussed earlier
provides a handy create
sub command to setup all the necessary template code.
Its command syntax is:
ros2 pkg create --build-type ament_python --license Apache-2.0 <package_name>
ros2 pkg create --build-type ament_cmake --license Apache-2.0 <package_name>
With the --build-type
argument we define the intended colcon
configuration in the generated package files.
For Python packages colcon
should use the ament_python
build helper,
and for C++ packages colcon
should use ament_cmake
to make it work with CMake.
For this tutorial, you will add the optional arguments --node-name
and
--license
:
--node-name
option will automatically create a simple Hello World type executable in the package, so for now we will not have to add any code ourselves.--license
declares the license information for the package, which tells those who wish to use or contribute to your package what they are and aren’t allowed to do (e.g.: Can they modify it? Can they publish it? Can they use it commercially? Do the need to give you attribution?). Here we use the common open sourceApache-2.0
license, but many more open source licenses are available.
So, let’s create a new package my_package
with example code for a node called my_node
.
Enter the following command in your terminal:
ros2 pkg create --build-type ament_python --license Apache-2.0 --node-name my_node my_package
ros2 pkg create --build-type ament_cmake --license Apache-2.0 --node-name my_node my_package
After running the command, your terminal will return the message:
going to create a new package
package name: my_package
destination directory: /home/user/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['<name> <email>']
licenses: ['Apache-2.0']
build type: ament_python
dependencies: []
node_name: my_node
creating folder ./my_package
creating ./my_package/package.xml
creating source folder
creating folder ./my_package/my_package
creating ./my_package/setup.py
creating ./my_package/setup.cfg
creating folder ./my_package/resource
creating ./my_package/resource/my_package
creating ./my_package/my_package/__init__.py
creating folder ./my_package/test
creating ./my_package/test/test_copyright.py
creating ./my_package/test/test_flake8.py
creating ./my_package/test/test_pep257.py
creating ./my_package/my_package/my_node.py
going to create a new package
package name: my_package
destination directory: /home/user/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['<name> <email>']
licenses: ['Apache-2.0']
build type: ament_cmake
dependencies: []
node_name: my_node
creating folder ./my_package
creating ./my_package/package.xml
creating source and include folder
creating folder ./my_package/src
creating folder ./my_package/include/my_package
creating ./my_package/CMakeLists.txt
creating ./my_package/src/my_node.cpp
You will now have a new folder within your workspace’s src
directory
called my_package
.
Tip
You can inspect the automatically generated files for the new package.
8.2.4. Build a package#
Putting packages in a workspace is especially valuable because you can
build many packages at once by running colcon build
in the workspace
root. Otherwise, you would have to build each package individually.
Return to the root of your workspace:
cd ~/ros2_ws
Now you can build your packages:
colcon build
Recall from the last tutorial that you also have the ros_tutorials
packages in your ros2_ws
. You might have noticed that running
colcon build
also built the turtlesim
package. That’s fine when you
only have a few packages in your workspace, but when there are many
packages, colcon build
can take a long time.
To build only the my_package
package next time, you can run:
colcon build --packages-select my_package
8.2.5. Source the setup file#
To use your new package and executable, first open a new terminal and source your main ROS 2 installation.
Then, from inside the ros2_ws
directory, run the following command to
source your workspace:
source install/local_setup.bash
Now that your workspace has been added to your path, you will be able to use your new package’s executables.
8.2.6. Use the package#
To run the executable you created using the --node-name
argument
during package creation, enter the command:
ros2 run my_package my_node
Which will return a message to your terminal:
Hi from my_package.
hello world my_package package
8.2.7. Examine package contents#
Inside ros2_ws/src/my_package
, you will see the files and folders that
ros2 pkg create
automatically generated:
my_package package.xml resource setup.cfg setup.py test
my_node.py
is inside the my_package
directory. This is where all
your custom Python nodes will go in the future.
CMakeLists.txt include package.xml src
my_node.cpp
is inside the src
directory. This is where all your
custom C++ nodes will go in the future.
8.2.8. Customize package.xml#
You may have noticed in the return message after creating your package
that the fields description
and license
contain TODO
notes.
That’s because the package description and license declaration are not
automatically set, but are required if you ever want to release your
package. The maintainer
field may also need to be filled in.
From ros2_ws/src/my_package
, open package.xml
using your preferred
text editor:
<?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</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="user@todo.todo">user</maintainer>
<license>TODO: License declaration</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>
<?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</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="user@todo.todo">user</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>
Input your name and email on the maintainer
line if it hasn’t been
automatically populated for you. Then, edit the description
line to
summarize the package:
<description>Beginner client libraries tutorials practice package</description>
Then, update the license
line. You can read more about open source
licenses here. Since
this package is only for practice, it’s safe to use any license. We’ll
use Apache-2.0
:
<license>Apache-2.0</license>
Don’t forget to save once you’re done editing.
Below the license tag, you will see some tag names ending with
_depend
. This is where your package.xml
would list its dependencies
on other packages, for colcon
to search for. my_package
is simple and
doesn’t have any dependencies, but you will see this space being
utilized in upcoming tutorials.
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.
Open setup.py
with your preferred text editor.
from setuptools import find_packages, setup
package_name = 'my_py_pkg'
setup(
name=package_name,
version='0.0.0',
packages=find_packages(exclude=['test']),
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='TODO',
maintainer_email='TODO',
description='TODO: Package description',
license='TODO: License declaration',
tests_require=['pytest'],
entry_points={
'console_scripts': [
'my_node = my_py_pkg.my_node:main'
],
},
)
Edit the maintainer
, maintainer_email
, and description
lines to
match package.xml
.
Don’t forget to save the file.
You’re all done for now!
8.2.9. Summary#
You’ve created a package to organize your code and make it easy to use for others.
Your package was automatically populated with the necessary files, and
then you used colcon
to build it so you can use its executables in your
local environment.