BLOGS

Dependencies in CMakeLists.txt and package.xml in Catkin ROS Package

Published Apr 24, 2020 - Author: zgxsin

Share

Create a Catkin ROS Package

Follow this tutorial to create a sample catkin ROS package. The simple command syntax to create a catkin ROS package is

catkin_create_pkg <package_name> [depend1] [depend2] [depend3]

After creating and initializing the catkin workspace, go to your_catkin_workspace/src and create a package beginner_tutorials:

guzhou@guzhou-laptop:~/catkin_ws/src$ catkin_create_pkg beginner_tutorials std_msgs rospy roscpp
Created file beginner_tutorials/package.xml
Created file beginner_tutorials/CMakeLists.txt
Created folder beginner_tutorials/include/beginner_tutorials
Created folder beginner_tutorials/src
Successfully created files in /home/guzhou/catkin_ws/src/beginner_tutorials. Please adjust the values in package.xml.

A lot of files are created. The file structure of the created ROS package is as follows:

guzhou@guzhou-laptop:~/catkin_ws/src$ tree
.
└── beginner_tutorials
    ├── CMakeLists.txt
    ├── include
    │   └── beginner_tutorials
    ├── package.xml
    └── src

4 directories, 2 files

Here two files CMakeLists.txt and package.xml are created. The contents inside these two files are full of comments and instructions which tell you how to fill in these two files. To have a clear view of the contents in these two files, I remove all the comments and paste the default generated contents here:

cmake_minimum_required(VERSION 2.8.3)
project(beginner_tutorials)

find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
)

catkin_package(

# INCLUDE_DIRS include

# LIBRARIES beginner_tutorials

# CATKIN_DEPENDS roscpp rospy std_msgs

# DEPENDS system_lib

)

include_directories(

# include

${catkin_INCLUDE_DIRS}
)
<?xml version="1.0"?>
<package format="2">
  <name>beginner_tutorials</name>
  <version>0.0.0</version>
  <description>The beginner_tutorials package</description>

<maintainer email="guzhou@todo.todo">guzhou</maintainer>

<license>TODO</license>

<buildtool_depend>catkin</buildtool_depend>
<build_depend>roscpp</build_depend>
<build_depend>rospy</build_depend>
<build_depend>std_msgs</build_depend>
<build_export_depend>roscpp</build_export_depend>
<build_export_depend>rospy</build_export_depend>
<build_export_depend>std_msgs</build_export_depend>
<exec_depend>roscpp</exec_depend>
<exec_depend>rospy</exec_depend>
<exec_depend>std_msgs</exec_depend>

<!-- The export tag contains other, unspecified, tags -->
<export>
<!-- Other tools can request additional information be placed here -->

</export>
</package>

Catkin CMakeLists.txt files mostly contain ordinary CMake commands, plus a few catkin-specific ones. 

The package dependencies are declared in package.xml. If they are missing or incorrect, you may be able to build from source and run tests in your own workspace, but your package will not work correctly when released to the ROS community. Others rely on this information to install the software they need for using your package. Your package must contain an XML file named package.xml, as specified by REP-0140. These components are all required:

<package format="2">
  <name>your_package</name>
  <version>0.0.0</version>
  <description>
    your description about this package.
  </description>
  <maintainer email="you@example.com">Your Name</maintainer>
  <license>TODO</license>
  <buildtool_depend>catkin</buildtool_depend>
</package>

Packages, Libraries and Executables

The essential concepts in Catkin ROS packages include packages, libraries and executables. The package can contain several libraries and executables as well as headers (C++ .h or .hpp file). Both libraries and executables are called targets. The main difference between an executable and a library is that an executable has a fixed starting point. A library can have multiple entry points, and there is no single point defined as the starting point. An executable could contain any code that could exist in a library. A library could contain any code that exists in an executable, except for the starting point. Separating libraries from executables allows programmers to compartmentalize functionality.

Header-only Libraries

A header-only library, as the name hints, is only made of headers. That actually means you don't have to link against binaries, because the whole code of this library is contained in headers, and this code will be compiled when you include them in your project.

This kind of libraries is sometimes the most convenient portable solution, for example when dealing with templates. Please check here and here for the reasoning behind.

C++ Catkin Library Dependencies

Catkin libraries are provided by ROS packages whether you install them from Ubuntu packages or build from source. When your package depends on a catkin C++ library, there are usually several kinds of dependencies which must be declared in your package.xml and CMakeLists.txt files.

package.xml

<depend>

It is generally sufficient to mention each ROS package dependency once, like this:

<depend>roscpp</depend> 

Sometimes, you may need or want more granularity for certain dependencies. The following sections explain how to do that. If in doubt, use the <depend> tag, it’s simpler.

<build_depend>

If you only use some particular dependency for building your package, and not at execution time, you can use the <build_depend> tag. For example, the ROS angles package only provides C++ headers and CMake configuration files:

<build_depend>angles</build_depend>

With this type of dependency, an installed binary of your package does not require the angles package to be installed. But, that could create a problem if your package exports a header that includes the <angles/angles.h> header. In that case you also need a <build_export_depend>.

<build_export_depend>

If you export a header that includes <angles/angles.h>, it will be needed by other packages that <build_depend> on yours:

<build_export_depend>angles</build_export_depend>

This mainly applies to headers and CMake configuration files. Library packages referenced by libraries you export should normally specify <depend>, because they are also needed at execution time.

<exec_depend>

This tag declares dependencies for shared (dynamic) libraries, executables, Python modules, launch scripts and other files required when running your package. For example, the ROS openni_launch package provides launch scripts, which are only needed at execution time:

<exec_depend>openni_launch</exec_depend>

CMakeLists.txt

CMake does not know about package.xml dependencies, although catkin does. For your code to compile, the CMakeLists.txt must explicitly declare how to resolve all of your header and library references.

Finding the library

First, CMake needs to find the library. For catkin dependencies (catkin ROS package), this is easy:

# roscpp is a catkin ros package.
find_package(catkin REQUIRED COMPONENTS roscpp)

This find_package() call defines CMake variables that will be needed later for the compile and link steps. List all additional catkin dependencies in the same command:

# You will have two variables catkin_INCLUDE_DIRS and catkin_LIBRARIES set. catkin_INCLUDE_DIRS is collection of all the headers while catkin_LIBRARIES is a collection of all libraries
find_package(catkin REQUIRED COMPONENTS angles roscpp std_msgs)

# Quick Test
find_package(catkin REQUIRED COMPONENTS angles roscpp std_msgs)
MESSAGE("catkin_INCLUDE_DIRS: " ${catkin_INCLUDE_DIRS})
MESSAGE("catkin_LIBRARIES: " ${catkin_LIBRARIES})

If you load the CMakeList.txt, the output will have two lines like below:

catkin_INCLUDE_DIRS: /opt/ros/melodic/include/opt/ros/melodic/share/xmlrpcpp/cmake/../../../include/xmlrpcpp/usr/include
catkin_LIBRARIES: /opt/ros/melodic/lib/libroscpp.so/usr/lib/x86_64-linux-gnu/libboost_filesystem.so/opt/ros/melodic/lib/librosconsole.so/opt/ros/melodic/lib/librosconsole_log4cxx.so/opt/ros/melodic/lib/librosconsole_backend_interface.so/usr/lib/x86_64-linux-gnu/liblog4cxx.so/usr/lib/x86_64-linux-gnu/libboost_regex.so/opt/ros/melodic/lib/libxmlrpcpp.so/opt/ros/melodic/lib/libroscpp_serialization.so/opt/ros/melodic/lib/librostime.so/opt/ros/melodic/lib/libcpp_common.so/usr/lib/x86_64-linux-gnu/libboost_system.so/usr/lib/x86_64-linux-gnu/libboost_thread.so/usr/lib/x86_64-linux-gnu/libboost_chrono.so/usr/lib/x86_64-linux-gnu/libboost_date_time.so/usr/lib/x86_64-linux-gnu/libboost_atomic.so/usr/lib/x86_64-linux-gnu/libpthread.so/usr/lib/x86_64-linux-gnu/libconsole_bridge.so.0.4

Make sure all these packages are also mentioned in your package.xml using a <depend> or <build_depend> tag.

Include directories

Note: target_include_directories() is preferred.

Before compiling, collect all the header paths you found earlier:

include_directories(include ${catkin_INCLUDE_DIRS}) 

The include parameter is needed only if that sub-directory of your package contains headers used to compile your programs. In our example beginner_tutorials, there is an include folder which is created by default.

Exporting interfaces

You must declare the library and header packages needed by all the interfaces you export to other ROS packages:

# This only list the catkin ros packages dependencies which are needed when your current catkin package is exported.
catkin_package(CATKIN_DEPENDS angles roscpp std_msgs)

Make sure all these packages are also mentioned in your package.xml using a <depend> or <build_export_depend> tag.

The catkin_package() command is only called once. It may need additional parameters, depending on what else your package exports.

C++ System Library Dependencies

System libraries are part of your operating system distribution.

When your package depends on a system C++ library, there are usually several kinds of dependencies which must be declared in your package.xml and CMakeLists.txt files.

package.xml

<build_depend>

This tag declares packages needed for building your programs, including development files like headers, libraries and configuration files. For each build dependency, specify the corresponding rosdep key. On many Linux distributions these are called “development” packages, and their names generally end in -dev or -devel, like this:

<build_depend>libgstreamer0.10-dev</build_depend>

Some C++ packages, like Eigen, have no run-time library, and everything is defined in the header files:

<build_depend>eigen</build_depend>

<build_export_depend>

If your package exports a header that includes an Eigen header like <Eigen/Geometry>, then other packages that <build_depend> on yours will need Eigen, too. To make that work correctly, declare it like this:

<build_export_depend>eigen</build_export_depend>

This type of dependency mainly applies to headers and CMake configuration files, and it typically names the “development” package:

<build_export_depend>libgstreamer0.10-dev</build_export_depend>

<exec_depend>

The <exec_depend> is for shared (dynamic) libraries, executables, Python modules, launch scripts and other files required for running your package. Specify the run-time rosdep key, if possible, like this

libgstreamermm-1.0-1/bionic 1.10.0+dfsg-1 amd64
  C++ wrapper library for GStreamer (shared libraries)

libgstreamermm-1.0-dev/bionic 1.10.0+dfsg-1 amd64
  C++ wrapper library for GStreamer (development files)
<exec_depend>libgstreamer0.10-0</exec_depend>

<depend>

This tag combines all the previous types of dependencies into one. It is not recommended for system dependencies, because it forces your package’s binary installation to depend on the “development” package, which is not generally necessary or desirable:

<depend>curl</depend>

Development Packages

The *-devel packages (usually called *-dev in Debian-based distributions) usually contain all the files necessary to compile code against a given library.

For running an application using the library libfoo, only the actually shared library file (*.so.*, for example libfoo.so.1.0) are needed (plus possibly some data files and some version-specific symlinks).

When you actually want to compile a C++ application that uses that library, you'll need the header files (*.h, for example foo.h) that describe the interface of that application as well as a version-less symlink to the shared library (*.so, for example libfoo.so -> libfoo.so.1.0). Those are usually bundled in the *-devel packages.

Sometimes the *-devel packages also include statically compiled versions of the libraries (*.a, for example libfoo.a) in case you want to build a complete stand-alone application that doesn't depend on dynamic libraries at all.

Below is an example of different packages for the same application.

# libavformat57: FFmpeg library with (de)muxers for multimedia containers - runtime files

guzhou@guzhou-laptop:~$ apt-file list libavformat57
libavformat57: /usr/lib/x86_64-linux-gnu/libavformat.so.57
libavformat57: /usr/lib/x86_64-linux-gnu/libavformat.so.57.83.100
libavformat57: /usr/share/doc/libavformat57/changelog.Debian.gz
libavformat57: /usr/share/doc/libavformat57/copyright
libavformat57: /usr/share/lintian/overrides/libavformat57

guzhou@guzhou-laptop:~$  ls -l /usr/lib/x86_64-linux-gnu/libavformat.so.57.83.100
-rw-r--r-- 1 root root 2459288 Apr 24  2019 /usr/lib/x86_64-linux-gnu/libavformat.so.57.83.100
guzhou@guzhou-laptop:~$  ls -l /usr/lib/x86_64-linux-gnu/libavformat.so.57
lrwxrwxrwx 1 root root 24 Apr 24  2019 /usr/lib/x86_64-linux-gnu/libavformat.so.57 -> libavformat.so.57.83.100


# libavformat-dev: FFmpeg library with (de)muxers for multimedia containers - development files

guzhou@guzhou-laptop:~$ apt-file list libavformat-dev
libavformat-dev: /usr/include/x86_64-linux-gnu/libavformat/avformat.h
libavformat-dev: /usr/include/x86_64-linux-gnu/libavformat/avio.h
libavformat-dev: /usr/include/x86_64-linux-gnu/libavformat/version.h
libavformat-dev: /usr/lib/x86_64-linux-gnu/libavformat.a
libavformat-dev: /usr/lib/x86_64-linux-gnu/libavformat.so
libavformat-dev: /usr/lib/x86_64-linux-gnu/pkgconfig/libavformat.pc
libavformat-dev: /usr/share/doc/libavformat-dev/changelog.Debian.gz
libavformat-dev: /usr/share/doc/libavformat-dev/copyright

guzhou@guzhou-laptop:~$  ls -l /usr/lib/x86_64-linux-gnu/libavformat.so
lrwxrwxrwx 1 root root 24 Apr 24  2019 /usr/lib/x86_64-linux-gnu/libavformat.so -> libavformat.so.57.83.100

Other languages (such as Java, Python, ...) use a different way of noting the API of a library (effectively including all the necessary information in the actual library) and thus usually need no separate *-devel packages (except maybe for documentation and additional tools).

CMakeLists.txt

Finding the library

First, CMake needs to find the library. If you are lucky, someone has already provided a CMake module as was done for boost. Most boost C++ components are fully implemented in the header files. They do not require a separate shared library at run-time:

find_package(Boost REQUIRED)

But, the boost thread implementation does require a library, so specify “COMPONENTS thread” if you need it:

find_package(Boost REQUIRED COMPONENTS thread)

While the CMake community recommends standard names for those variables, some packages may not follow their recommendations.

Sometimes, no CMake module is available, but the library’s development package provides a pkg-config file. To use that, first load the CMake PkgConfig module, then access the build flags provided by the library. You might check the usage of pkg_check_modules here.

# pkg_check_modules
# Checks for all the given modules, setting a variety of result 
# variables in the calling scope.

pkg_check_modules(<prefix>
                  [REQUIRED] [QUIET]
                  [NO_CMAKE_PATH]
                  [NO_CMAKE_ENVIRONMENT_PATH]
                  [IMPORTED_TARGET [GLOBAL]]
                  <moduleSpec> [<moduleSpec>...])
find_package(PkgConfig REQUIRED)
pkg_check_modules (GLIB2 REQUIRED glib-2.0)

The first pkg_check_modules() parameter declares a prefix for CMake variables like GLIB2_INCLUDE_DIRS and GLIB2_LIBRARIES, later used for the compile and link. The REQUIRED argument causes configuration to fail unless the following “module” is the base name of a pkg-config file provided by the library. The corresponding CMake result for the above command is

-- Checking for module 'glib-2.0'
--   Found glib-2.0, version 2.56.4

Include directories

Before compiling, collect all the header paths you found earlier using find_package() or pkg_check_modules():

include_directories(include ${Boost_INCLUDE_DIRS} ${GLIB2_INCLUDE_DIRS})

The include parameter is needed only if that sub-directory of your package contains headers used to compile your programs. If your package also depends on other catkin ROS packages, add ${catkin_INCLUDE_DIRS} to the list. Some packages provide variable names that do not comply with these recommendations. In that case, you must pass the absolute paths explicitly as INCLUDE_DIRS and LIBRARIES.

Exporting interfaces

The catkin_package() command is only called once. In addition to any other parameters, it must declare the non-catkin system library and header packages needed by the interfaces you export to other ROS packages:

catkin_package(DEPENDS Boost GLIB2)

Make sure all these packages are also mentioned in your package.xml using a <build_export_depend> or <depend> tag.

C++ Message or Service Dependencies

When your C++ programs depend on ROS messages or services, they must be defined by catkin packages like std_msgs and sensor_msgs, which are used in the examples below.

package.xml

For each C++ message dependency, package.xml should provide a <depend> tag with the ROS package name:

<depend>std_msgs</depend>
<depend>sensor_msgs</depend>

CMakeLists.txt

For C++ access to ROS messages, CMake needs to find the message or service headers:

find_package(catkin REQUIRED COMPONENTS std_msgs sensor_msgs)
include_directories(include ${catkin_INCLUDE_DIRS})

The include parameter is needed only if that sub-directory of your package contains headers also needed to compile your programs.

Since you presumably have build targets using the message or service headers, add this to ensure all their headers get built before any targets that need them:

add_dependencies(your_program ${catkin_EXPORTED_TARGETS})
add_dependencies(your_library ${catkin_EXPORTED_TARGETS})

Python Module Dependencies

When your Python package imports other python modulespackage.xml should provide a <exec_depend> with the appropriate package name.

<exec_depend>python-numpy</exec_depend>
<exec_depend>python-yaml</exec_depend>

These names are usually already defined in the rosdistro repository. If you need a module not yet defined there, please fork that repository and add them.

<exec_depend>python-rosdep</exec_depend>
<exec_depend>python-rospkg</exec_depend>

<exec_depend>roslaunch</exec_depend>
<exec_depend>rospy</exec_depend>
<depend>std_msgs</depend>
<depend>sensor_msgs</depend>

Your CMakeLists.txt need not specify Python-only dependencies. They are resolved automatically via sys.path.

Sources

  1. https://stackoverflow.com/questions/2358801/what-are-devel-packages
  2. https://docs.ros.org/melodic/api/catkin/html/howto/format2/index.html