Packages and Workspaces
Packages
A ROS Package is a collection of source code and other resources that is distributed as a single unit.
- Binary versions of ROS packages are distributed on ROS's package server and can be downloaded via
apt
(as done when you installed ROS).- The naming convention when using apt is to do
ros-jazzy-package-name
wherepackage-name
is the name of the package, with underscores_
converted to dashes-
.
- The naming convention when using apt is to do
- The source code for ROS packages can be easily downloaded and compiled.
- It is easier and less time consuming to download a package, but to create ROS code you need to create a package.
Package Structure
- All ROS packages have a base directory containing a manifest file called
package.xml
.- This file is an XML document.
- The full specification for
package.xml
is in ROS Rep 149. - The XML Schema for
package.xml
provides a machine-readable method for automatically validating thepackage.xml
- An important element of
package.xml
is the<export><build_type>
, which determines the type of package- ament python is used for pure python packages.
- ament_cmake is used for C++ packages and packages that define custom Messages, Services, or Actions (i.e., Interfaces)
- ament_cmake_python is for packages with mixed python/C++ code.
package.xml
Required Elements
<name>
The package name<version>
The package Version<description>
A description of the package<maintainer>
One or more people who are responsible for the package<license>
One or more legal ways the package may be distributed
Specifying Dependencies
There are multiple types of dependencies. The most important are:
<exec_depend>
- Packages needed at runtime. If your python package imports code from another package, or a launchfile runs a node from another package, then it should be an<exec_depend>
, thus this is the most common dependency for a python-only package.- For example, if you
import rclpy
thenrclpy
should be anexec_depend
- Third-party (e.g., non-ros) python packages are usually referred to as
python3-pythonpackagename
.
- For example, if you
<build_depend>
- Package needed at build time (e.g., whencolcon build
is run). In python, code is not compiled, so you usually do not need<build_depend>
. An exception is when you depend upon messages or services in another package.<depend>
- This is the most common dependency type for a C++ package. It should also be used by python packages when depending on packages containing custom messages such asstd_msgs
. Using<depend>
automatically creates an<exec_depend>
and a<build_depend>
(also a<build_export_depend>
). In python especially, whenever you have a<build_depend>
you almost always also have an<exec_depend>
, so you can just use<depend>
.- The tool rosdep analyzes the dependencies in
package.xml
and can automatically install missing dependencies. - The rosdep tutorial explains these dependency types.
Advanced Dependencies
While it is important to get the dependencies correct, you will notice that (especially with python), your project will still work even if package.xml
does not have all dependencies.
There are a few reasons why this can happen
<exec_depend>
are not actually checked during build or run. They are used byrosdep
to help it fetch dependencies you need.<build_depend>
is used to determine the order in whichcolcon
traverses packages. However, colcon runs in parallel by default- Essentially, if
A
depends onB
to build but does not declare a<build_depend>
it is possible thatA
is built beforeB
and fails. It is also possible that (due to parallel builds)B
happens to finish beforeA
and it succeeds. - To help ensure that all
<build_depend>
are properly set it makes sense to occasionally do acolcon build --executor sequential
on a clean workspace
- Essentially, if
Groups
<member_of_group>
In ROS 2 (but not ROS 1) packages can be placed in a group, which is a collection of packages that share something in common.- This tag sets the current package to be a member of that group
- For example, packages that define custom messages, services, etc. can be part of the
rosidl_interface_packages
group
- One package can depend on a package group rather than a named package.
- For example, some packages need to be re-built whenever any interface (e.g., message or service) file on the whole system changes.
These packages would depend on
rosidl_interface_packages
so that if any package in that group changes it would be rebuilt.
- For example, some packages need to be re-built whenever any interface (e.g., message or service) file on the whole system changes.
These packages would depend on
Ament Python Packages
- Ament Python is the preferred package type for pure python ROS 2 packages
- Boilerplate for this package can be created using
ros2 pkg create --build-type ament_python
The directory structure is:
pkg_name/ ├── package.xml # package manifest ├── pkg_name/ # python package │ ├── __init__.py # Marks this directory as a python package │ └── module.py # A python module ├── launch/ # Launchfiles go here │ ├── file.launch.xml # An xml launch file │ ├── file.launch.py # A python launch file │ └── file.launch.yaml # A yaml launch file ├── config/ # Configuration directory │ ├── parameters.yaml # File storing parameters for some nodes │ └── view.rviz # Saved rviz configuration ├── resource/ # Used for registering packages in ament_index │ └── package_name # Empty file used to register package with index ├── setup.cfg # Sets up installation directories for ROS ├── setup.py # Metadata, node entry , other files to install └── test/ # Unit tests ├── test_copyright.py # ROS test to ensure proper copyright notice ├── test_flake8.py # Uses flake8 to enforce code style └── test_pep257.py # Ensures compliance with PEP 257
setup.py
- It is based on setuptools, a commonly used python packaging system created by
pypa
, the organization behindpip
. - Contains meta-information (which must be manually synchronized with
package.xml
). - Contains
data_files
: these are non-code files that must be installed to the system. -data_files
is a list of tuples. The first element of each tuple is an install directory, and the second is a list of files to install:[('install_dir1', ['file1', 'file2', 'file3'...]), ('install_dir2', ['file1' ...])]
- entry_points: This is a dictionary of different types of entry points.
- Nodes are 'console_scripts'.
- Each ROS node is an entry in the 'console_scripts' list, of the form
'node_exec = pkg.modulename:entry_func'
,node_exec
is the name you want the executable file that runs your node to be called,pkg
is the name of the python package (usually but not necessarily the name of the ROS package)
- entry_points: This is a dictionary of different types of entry points.
setup.cfg
- Provides base install directories for
setup.py
. Usually can be left as-is.
Ament CMake Packages
- Ament Cmake packages are primarily used for C++ ROS projects
- Boilerplate for this package can be created using
ros2 pkg create --build-type ament_cmake
- Currently, custom interfaces (e.g., messages, services, or actions) need to be in their own Ament CMake package
The directory structure is:
pkg_name/ ├── package.xml # The manifest file ├── CMakeLists.txt # Build instructions, uses ament_* functions ├── msg/ # Directory for custom message types │ └── MessageType.msg # A ROS IDL file defining a message ├── srv/ # Directory for custom service types │ └── ServiceType.srv # A ROS IDL file defining a service └── action # Directory for custom action types └── ActionType.action # A ROS IDL file defining an action
Custom Interfaces
Defining Custom Interfaces
- Custom interfaces are created in
ament_cmake
type packages - To create a custom interface file, first write an interface file using the ROS IDL
- Message files end with
.msg
and go in<package_name>/msg
- Service files end with
.srv
and go in<package_name>/srv
- Action files end with
.action
and go in<package_name>/action
- See the Custom ROS Interface Tutorial
- Message files end with
Edit
CMakeLists.txt
and add# Add in the CMake code that generates ROS interfaces find_package(rosidl_default_generators REQUIRED) # If any of your interfaces depend on another interface file # (For example assume you are using a geometry_msgs/msg/Twist) # Then you must first find the dependency with find_package (e.g.) find_package(geometry_msgs REQUIRED) # Tell Cmake what files to generate interface definitions for rosidl_generate_interfaces(${PROJECT_NAME} "msg/MessageType.msg" # Whatever message file you've defined "srv/ServiceType.srv" # Whatever service file you've defined ... # and continue for all the interface files DEPENDENCIES geometry_msgs # in this example we depend on geometry_msgs ) # Export a dependency on the ros_idl_runtime ament_export_dependencies(rosidl_default_runtime)
- Edit
package.xml
- Add a
<buildtool_depend>
onrosidl_default_generators
(contains tools needed to generate the interface code). - Add an
<exec_depend>
onrosidl_default_runtime
to allow access to these types at runtime - Add the package to the
rosidl_interface_packages
group with<member_of_group>
because this package generates interfaces. - You should also
<depend>
on any packages whose types were used within your custom types (in this examplegeometry_msgs
)
- Add a
- All custom interface must start with a capital letter, and the names can contain only letters and numbers
- More information at Implementing Custom Interfaces Tutorial
- It is generally good ROS practice to make interfaces is separate packages so that they can be more easily used
- It is also generally good ROS practice to use existing interfaces when possible, to make your code as compatible as possible with the rest of the ROS ecosystem.
- An example of custom interfaces is available at https://github.com/m-elwin/me495_demo_interfaces
Using Interfaces
- You must add an
<exec_depend>
on the package that defines the interface - In python use
from <interface_package_name>.<msg|srv|action> import TypeName
<interface_package_name>
is the name of the package that defines the interfaces<msg|srv|action>
is one ofmsg
,srv
, oraction
, as appropriateTypeName
is the name of the Message, Service, or Action Type
Workspace
Structure
A ROS Workspace is a directory (called ws
here) containing:
- The source code to ROS packages:
ws/src/<repo>
- A package is any directory under
ws/
containing apackage.xml
file - By convention, packages are placed under
ws/src/<repo>
, where<repo>
is the name of the source-code repository[fn:Technically repositories can be directly underws
but that defies convention!] - Each repository may contain multiple packages
- A package is any directory under
- Log files generated from building the packages go in
ws/log
. The latest logs are inws/log/latest
- A directory where the packages are installed:
ws/install
- ROS workspaces enable you to build collections of related packages
- Use a new workspace for each project, that way issues in packages you aren't currently developing won't effect you.
- For a practical guide see the Creating a Workspace Tutorial.
Building a Workspace
- Workspaces must be built before they can be used.
- Run
colcon build
from the workspace directory (ws
) to build all the packages in thews/src
directory- Build artifacts are stored in
ws/build
- The final results are installed to
ws/install
, which is where your code (even python) actually runs from - Thus, you always need to run
colcon build
at least once before using your workspace
- Build artifacts are stored in
- There are two important options to
colcon build
:colcon build --symlink-install
enables you to change python files without re-runningcolcon build
.- This enables faster development but comes with some caveats: for example, changes to launchfiles will require a rebuild.
colcon build --merge-install
by default each package is installed into its own sub-directory tree. This can make the path very long, so merge install keeps all package files of a given type (e.g., libraries and executables) in common directories.
Warnings
- Currently, many ROS 2 packages have deprecation warnings related to their use of setuptools
- To avoid seeing these warnings, add
export PYTHONWARNINGS="ignore:easy_install command is deprecated"
- See colcon-core issue #454 and This StackExchage Post for details
- It is bad practice to leave warnings lingering rather than addressing them.
- Warnings should only be suppressed after careful consideration as to why they are not a problem (or in this case, where we have no control over them).
- Leaving some warnings to linger creates situations where all warnings are ignored, which often leads to bugs.
Using a Workspace
- After building the
ws/install
directory (called the install space) is created- This contains your python files and other files (as long as you properly installed them in
setup.py
)
- This contains your python files and other files (as long as you properly installed them in
- After building you must source it to make the files in it available to ROS
- To source the workspace run
source <path_to_install_space>/setup.bash
- For example, if the current directory is the base of the workspace (e.g.,
ws/
) runsource install/setup.bash
- Technically you should never run
colcon build
from a terminal window where you have sourced the associated workspace.
- For example, if the current directory is the base of the workspace (e.g.,
- Sourcing the workspace sets up the environment. See the Colcon Environment Documentation for more details.
Colcon
colcon
is the ROS 2 build-tool, which is used to build the workspace.- It is written in
python
and implemented as a series of extensions, which anyone can make to customize the build process - Common extensions are distributed as part of the
python3-colcon-common-extensions
package - Other extensions can usually be installed as
python3-colcon-name-of-extension
- Anyone can write a colcon extension and it need not be supported or hosted by the colcon developers. However, source code for generally useful colcon extensions are hosted on the Colcon Github Organization and maintained by them
- It is written in
- As a build tool, colcon is capable of building projects that use many build systems (See Colcon Design Rationale)
- Examples of build systems include ament_python, ament_cmake, CMake (for C++), Catkin (for ROS1), setuptools (for python)
colcon
manages dependencies between multiple ROS packages written in different computer languages with different build systems- The dependencies specified in
package.xml
are used bycolcon
to build packages in the right order - If
<package A>
has abuild_depend
on<package B>
thencolcon
always builds<package B>
before<package A>
- The dependencies specified in
- By default
colcon
builds packages in parallel.- Your code may still compile by pure luck even if dependencies are specified indirectly. For example if
<package A>
depends on<package B>
but the dependency is not specified,colcon
may try to build both at the same time. If<package B>
finishes before the<package A>
process needs it the build will succeed - To test that your dependencies will always build, build each package in series by specifying
colcon build --executor sequential
- Your code may still compile by pure luck even if dependencies are specified indirectly. For example if
- More information about the ROS 2 build system
colcon_cd
- colcon_cd allows you to quickly switch between the workspace directory and that of a package
- Install with
sudo apt install python3-colcon-cd
- Then, add
source /usr/share/colcon_cd/function/colcon_cd.sh
- From your workspace directory run
colcon_cd package
to go to that package - You can then run
colcon_cd
to return to the workspace directory
colcon_clean
- colcon-clean allows you to easily remove the build results from a workspace
- Install with
sudo apt install python3-colcon-clean
colcon clean workspace
will clean all generated filescolcon clean packages
allows you to select individual packages to clean
colcon.pkg
- The colcon.pkg file, when placed at the base of the package directory enables a package to change
colcon
settings when it is being built - One use of this file is to install environment hooks, which are files that set environment variables when
install/setup.bash
is sourced - To set up an environment hook
- Create a dsv file in the
env-hooks
subdirectory of your package- For example: prepend-non-duplicate;VAR_NAME;VAR_VALUE will prepend VAR_VALUE to VAR_NAME, as it is not duplicated
Add the hook in
colcon.pkg
:{ "hooks": ["share/<PACKAGE_NAME>/env-hooks/<DSV_NAME>.dsv"] }
- The above code adds a hook located at the specified path
- In
setup.py
be sure to install the.dsv
file to the path referenced incolcon.pkg
- Create a dsv file in the
ROS Environment
- ROS relies on environment variables to control settings and find nodes and libraries
- See Environment Varaibles for more information on environment variables generally
- ROS environment variables are set when underlay is sourced.
- To source the
underlay
runsource /opt/ros/jazzy/setup.bash
- When you installed
ROS
you added the above command to the~/.bashrc
so that it runs automatically wheneverbash
is opened - The underlay must be sourced to have access to the ROS command-line tools and system-installed packages
- To source the
- Other ROS workspaces can be added by sourcing an
overlay
, which provides access to the packages installed in thatoverlay
- When you
source install/setup.bash
after building the workspace you are adding theoverlay
, providing access to the packages you just built
- When you
- To see the ROS environment variables run
env | grep "AMENT\|CMAKE\|ROS\|COLCON"
- This command prints all the environment variables and searches for ones containing
AMENT
,CMAKE
, orROS
AMENT_PREFIX_PATH
is where ROS looks for packages and files during runtimeROS_AUTOMATIC_DISCOVERY_RANGE
provides settings for where on the network ROS nodes will automatically discover each other.- By default this setting is
SUBNET
which means all nodes running on the same subnet will see each other. However, Northwestern's network blocks the traffic required for automatic node discovery.
- By default this setting is
- This command prints all the environment variables and searches for ones containing
Overlays
- Technically, you should not have the overlay
sourced
when usingcolcon build
, which means you need a separate window for building and running ROS commands- Having the overlay sourced when building makes the packages built from the previous build available, which can sometimes mask or cause dependency problems
- In practical day-to-day usage this issue rarely matters, but it is a good habit to be in or you may eventually forget and waste a bunch of time.
- Multiple ROS workspaces can be overlayed on top of each other allowing you to use packages from multiple workspaces or even override specific packages.
- One common use case is if you are using external packages that are only available as source code (e.g., the interbotix packages).
- Another use case is to allow you to develop a package that is installed on your system, without needing to uninstall the system version.
- The
install/setup.bash
file is created whencolcon build
is run and it captures the current ROS environment and adds your workspace to that.- This means that
source install/setup.bash
will setup your environment to be what it was at the time of build plus the current workspace. install/local_setup.bash
will overlay the current workspace onto the existing ROS environment, regardless of the environment at build time
- This means that
- The ROS 2 environment that exists when
colcon build
is run for the first time is captured by thecolcon build
.- Thus
source install/setup.bash
will bring in all the workspaces that were on the path whencolcon build
ran the first time.
- Thus
- Each environment also has
local_setup.bash
files that can be sourced. These bring in only that environment, but not the other environments that were present whencolcon build
first ran.
Python Virtual Environments
- Virtual environments allow you to isolate python projects from each other and the system, enabling every project to have its own version of dependencies
- Use
python -m venv myvenv
to create a directory calledmyvenv
, containing the virtual environment (any valid directory name is valid) - Activate the virtual environment with
source myvenv/bin/activate
- Now your python will use the interpreter and files in the virtual environment rather than your system's python
- Use
- ROS workspaces do not support the use of virtual environments by default
- ROS views itself as an extension of a given Ubuntu distribution and therefore expects all dependencies to be available via
apt
- ROS expects the version of any given python package to be the version packaged by Ubuntu and distributed via
apt
. - As of
jazzy
: because of the wayament_python
packages currently work, they will not use a virtual environment even if it is activated (see this issue).
- ROS views itself as an extension of a given Ubuntu distribution and therefore expects all dependencies to be available via
- Whenever possible, your ROS project should depend on the version of python packages that are part of the target Ubuntu distribution
- In some cases (especially with machine learning), a desired package is not available via
apt
and is most easily available viapip
Using Pip Packages with ROS 2
Here is my current suggested workaround (based in part off of this issue suggestion) to use pip
packages with ROS 2 workspaces
Create a new workspace and create a dummy package in it:
mkdir venv_ws cd venv_ws ros2 pkg create --build-type ament_python venv_pkg source ven_ws/install/setup.bash
- Install the packages you want with pip into the install directory of this workspace by setting the
PYTHONUSERBASE
variable:PYTHONUSERBASE=$(ros pkg prefix venv_pkg) pip3 install --user --break-system-packages pip_package_to_install
- By setting
PYTHONUSERBASE
you are tellingpip
that your system's root directory is actually the path to thevenv_pkg
- The
--user
flag tells pip to install to the "python user directory" - The
--break-system-packages
flag overridespips
admonition to only use it in virtual environments. However, because ofPYTHONUSERBASE
, the packages are only getting installed into the ROS workspace not the sysem, so this should be safe.
- By setting
- Now create a new workspace (
proj_ws
) for your project, and runcolcon build
- The
proj_ws
overlays thevenv_ws
and therefore will havevenv_pkg
on its python path - The
pip
installed packages will therefore be visible in the new workspace.
- The
- This method mixes existing system packages with new
pip
installed packages, when the ROS workspace is sourced- From a ROS point-of-view this situation is desirable because ROS packages are designed to work with system python packages so using as many of those as possible is beneficial
- From the point-of-view of the pip package, some system packages may be incompatible: in that case you will need to install the correct version with pip
- This method should work with
-r requirements.txt
as well as individual pip packages