UP | HOME

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 where package-name is the name of the package, with underscores _ converted to dashes -.
  • 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

  1. 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 the package.xml
  2. 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 then rclpy should be an exec_depend
    • Third-party (e.g., non-ros) python packages are usually referred to as python3-pythonpackagename.
  • <build_depend> - Package needed at build time (e.g., when colcon 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 as std_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

  1. <exec_depend> are not actually checked during build or run. They are used by rosdep to help it fetch dependencies you need.
  2. <build_depend> is used to determine the order in which colcon traverses packages. However, colcon runs in parallel by default
    • Essentially, if A depends on B to build but does not declare a <build_depend> it is possible that A is built before B and fails. It is also possible that (due to parallel builds) B happens to finish before A and it succeeds.
    • To help ensure that all <build_depend> are properly set it makes sense to occasionally do a colcon build --executor sequential on a clean workspace

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.

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 behind pip.
  • 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)

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

  1. Custom interfaces are created in ament_cmake type packages
  2. 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
  3. 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)
    
  4. Edit package.xml
    • Add a <buildtool_depend> on rosidl_default_generators (contains tools needed to generate the interface code).
    • Add an <exec_depend> on rosidl_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 example geometry_msgs)
  5. All custom interface must start with a capital letter, and the names can contain only letters and numbers
  6. More information at Implementing Custom Interfaces Tutorial
  7. 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.
  8. An example of custom interfaces is available at https://github.com/m-elwin/me495_demo_interfaces

Using Interfaces

  1. You must add an <exec_depend> on the package that defines the interface
  2. 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 of msg, srv, or action, as appropriate
    • TypeName is the name of the Message, Service, or Action Type

Workspace

Structure

A ROS Workspace is a directory (called ws here) containing:

  1. The source code to ROS packages: ws/src/<repo>
    • A package is any directory under ws/ containing a package.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 under ws but that defies convention!]
    • Each repository may contain multiple packages
  2. Log files generated from building the packages go in ws/log. The latest logs are in ws/log/latest
  3. A directory where the packages are installed: ws/install
  4. ROS workspaces enable you to build collections of related packages
  5. Use a new workspace for each project, that way issues in packages you aren't currently developing won't effect you.
  6. 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 the ws/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
  • There are two important options to colcon build:
    • colcon build --symlink-install enables you to change python files without re-running colcon 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)
  • 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/) run source install/setup.bash
    • Technically you should never run colcon build from a terminal window where you have sourced the associated workspace.
  • 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
  • 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 by colcon to build packages in the right order
    • If <package A> has a build_depend on <package B> then colcon always builds <package B> before <package A>
  • 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
  • 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 files
  • colcon clean packages allows you to select individual packages to clean

colcon.pkg

  1. 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
  2. One use of this file is to install environment hooks, which are files that set environment variables when install/setup.bash is sourced
  3. To set up an environment hook
    1. 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
    2. Add the hook in colcon.pkg:

      {
          "hooks": ["share/<PACKAGE_NAME>/env-hooks/<DSV_NAME>.dsv"]
      }
      
    3. The above code adds a hook located at the specified path
    4. In setup.py be sure to install the .dsv file to the path referenced in colcon.pkg

ROS Environment

  • ROS relies on environment variables to control settings and find nodes and libraries
  • ROS environment variables are set when underlay is sourced.
    • To source the underlay run source /opt/ros/jazzy/setup.bash
    • When you installed ROS you added the above command to the ~/.bashrc so that it runs automatically whenever bash is opened
    • The underlay must be sourced to have access to the ROS command-line tools and system-installed packages
  • Other ROS workspaces can be added by sourcing an overlay, which provides access to the packages installed in that overlay
    • When you source install/setup.bash after building the workspace you are adding the overlay, providing access to the packages you just built
  • 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, or ROS
    • AMENT_PREFIX_PATH is where ROS looks for packages and files during runtime
    • ROS_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.

Overlays

  • Technically, you should not have the overlay sourced when using colcon 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 when colcon 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
  • The ROS 2 environment that exists when colcon build is run for the first time is captured by the colcon build.
    • Thus source install/setup.bash will bring in all the workspaces that were on the path when colcon build ran the first time.
  • 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 when colcon build first ran.

Python Virtual Environments

  1. 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 called myvenv, 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
  2. 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 way ament_python packages currently work, they will not use a virtual environment even if it is activated (see this issue).
  3. Whenever possible, your ROS project should depend on the version of python packages that are part of the target Ubuntu distribution
  4. In some cases (especially with machine learning), a desired package is not available via apt and is most easily available via pip

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

  1. 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
    
  2. 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 telling pip that your system's root directory is actually the path to the venv_pkg
    • The --user flag tells pip to install to the "python user directory"
    • The --break-system-packages flag overrides pips admonition to only use it in virtual environments. However, because of PYTHONUSERBASE, the packages are only getting installed into the ROS workspace not the sysem, so this should be safe.
  3. Now create a new workspace (proj_ws) for your project, and run colcon build
    • The proj_ws overlays the venv_ws and therefore will have venv_pkg on its python path
    • The pip installed packages will therefore be visible in the new workspace.
  4. 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
  5. This method should work with -r requirements.txt as well as individual pip packages

Author: Matthew Elwin.