ROS2 ament_cmake user documentation

Directory of series articles

Article directory

  • Table of Contents of Series Articles
  • Preface
  • 1. Basic knowledge
    • 1.1 Basic outline of the project
    • 1.2 Compiler and linker options
    • 1.3 Find dependencies
    • 1.4 Add target
      • 1.4.1 Library
      • 1.4.2 Executable files
    • 1.5 Linking to dependencies
    • 1.6 Installation
      • 1.6.1 Library
      • 1.6.2 Executable files
  • 2. Examples

Foreword

ament_cmake is a package build system for ROS 2 based on CMake (especially for most C/C++ projects). It is a set of scripts that enhance CMake and add convenience functionality for package authors. Before using ament_cmake, it is best to understand the basics of CMake. The official tutorial can be found here.

1. Basic knowledge

Use ros2 pkg create on the command line to generate a basic CMake outline. Then, the build information will be collected into two files: package.xml and CMakeLists.txt, which must be in the same directory. package.xml must contain all dependencies and some metadata so that colcon can find the correct build order for your package and install the required dependencies in CI , and provides information for publishing using bloom. CMakeLists.txt contains commands for building and packaging executables and libraries, and is the focus of this document.

1.1 Basic Project Outline

The basic outline of a CMakeLists.txt for a ROS2 package includes

cmake_minimum_required(VERSION 3.8)
project(my_project)

ament_package()

The argument to project is the package name, which must be the same as the package name in package.xml.

Project setup is done by ament_package(), which must happen only once per package. ament_package() installs package.xml, registers the package in the ament index, and installs configuration files for CMake (and possibly object files) so that other packages can find it via find_package. Since ament_package() collects a lot of information from CMakeLists.txt, it should be the last call in CMakeLists.txt.

ament_package can be given additional parameters:

  • CONFIG_EXTRAS: List of CMake files (.cmake or .cmake.in templates extended by configure_file()) that should be provided to clients of the package. See the discussion in Adding Resources for examples of when to use these parameters. For more information on how to use template files, see the official documentation.

  • CONFIG_EXTRAS_POST: Same as CONFIG_EXTRAS, but the order of adding files is different. CONFIG_EXTRAS files are added before the files generated for ament_export_* calls, and CONFIG_EXTRAS_POST files are added after.

You can also add files to the variables ${PROJECT_NAME}_CONFIG_EXTRAS and ${PROJECT_NAME}_CONFIG_EXTRAS_POST instead of adding them to ament_package, the effect same. The only difference is the order in which the files are added. The total order is as follows:

  • Files added via CONFIG_EXTRAS

  • Files added by appending to ${PROJECT_NAME}_CONFIG_EXTRAS

  • File added by appending to ${PROJECT_NAME}_CONFIG_EXTRAS_POST

  • Files added via CONFIG_EXTRAS_POST

1.2 Compiler and linker options

ROS 2 uses a compiler that is compliant with the C++17 and C99 standards. There may be updated compiler versions in the future, please refer to them here. Therefore, it is common to set the corresponding CMake flags:

if(NOT CMAKE_C_STANDARD)
  set(CMAKE_C_STANDARD 99)
endif()
if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 17)
endif()

To keep code clean, the compiler should issue warnings about problematic code and correct these warnings.

It is recommended to cover at least the following warning levels:

  • For Visual Studio: Default W1 warning

  • For GCC and Clang: -Wall -Wextra -Wpedantic is strongly recommended and -Wshadow is recommended

It is currently recommended to use add_compile_options to add these options for all targets. This avoids cluttering your code with target-based compilation options in all executables, libraries and tests:

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

1.3 Find dependencies

Most ament_cmake projects depend on other packages. In CMake, this is accomplished by calling find_package. For example, if your package depends on rclcpp, then the CMakeLists.txt file should contain

find_package(rclcpp REQUIRED)

If a library is not explicitly required, but is a dependency of another explicitly required dependent library, then there is no need to find_package. If this is the case, please file a bug against the corresponding package.

1.4 Add target

In CMake terminology, targets targets refer to the artifacts that the project will create. You can create both libraries and executables, and a project can contain zero or more targets.

1.4.1 Library

These libraries are created by calling add_library, which should contain the name of the target library and the source files compiled to create the library.

In C/C++, header files and implementation are separated, so it is usually not necessary to add header files as arguments to add_library.

The following best practices are recommended:

  • Place all header files that should be used by clients of the library (and therefore must be installed) into a subdirectory of the include folder with the same name as the package, and all other files ( .c/.cpp and header files that should not be exported) are placed in the src folder.

  • Only explicitly reference .c/.cpp files when calling add_library

Find the header file of the my_library library by

target_include_directories(my_library
  PUBLIC
    "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
    "$<INSTALL_INTERFACE:include/${PROJECT_NAME}>")

This will add all files in the ${CMAKE_CURRENT_SOURCE_DIR}/include folder to the common interface at build time and the include folder (relative to ${CMAKE_INSTALL_DIR}) is added to the public interface.

ros2 pkg create follows these rules to create a package layout.

Since Windows is one of the officially supported platforms, any software package should be built on the Windows platform to be most useful. The Windows library format enforces symbol visibility; that is, every symbol that should be used from the client must be explicitly exported by the library (symbols need to be imported implicitly).
Since GCC and Clang compilation usually don’t do this, it is recommended to use the logic from the GCC wiki. Use this with a package named my_library:
Copy the logic from the link into a header file called visibility_control.hpp.
Replace the DLL with MY_LIBRARY (see rviz_rendering’s visibility control for an example).
Use the macro “MY_LIBRARY_PUBLIC” for all symbols that need to be exported (such as classes or functions).
Use in CMakeLists.txt project: target_compile_definitions(my_library PRIVATE “MY_LIBRARY_BUILDING_LIBRARY”)

1.4.2 Executable files

These executables should be created by calling add_executable, which should contain the name of the object file and the source file compiled to create the executable. The executable may also need to link with any libraries created in this package via target_link_libraries.

Since customers generally do not use executable files as libraries, there is no need to place header files in the include directory.

If a package has both libraries and executables, be sure to combine the suggestions in “Libraries” and “Executables” above.

1.5 Linking to dependencies

There are two ways to link targets with dependencies.

The first and recommended way is to use the ament macro ament_target_dependencies. For example, suppose we want to connect my_library to the linear algebra library Eigen3.

find_package(Eigen3 REQUIRED)
ament_target_dependencies(my_library PUBLIC Eigen3)

It includes necessary header files and libraries and their dependencies so that the project can find them correctly.

The second method is to use target_link_libraries.

Modern CMake prefers to just use target and export and link against them. CMake targets may be named, similar to C++. If there are named interval targets available, use them in preference. For example, Eigen3 defines the target Eigen3::Eigen.

In the case of Eigen3, the call would look like this

target_link_libraries(my_library PUBLIC Eigen3::Eigen)

This will also include necessary header files, libraries and their dependencies. Note that the dependency must have been previously discovered by calling find_package.

1.6 Installation

1.6.1 Library

When building a reusable library, you need to export some information so that it can be easily used by downstream packages.

First, install the header files that the client should use. The include directory is customized to support overriding in colcon; see https://colcon.readthedocs.io/en/released/user/overriding- for more information packages.html#install-headers-to-a-unique-include-directory.

install(
  DIRECTORY include/
  DESTINATION include/${<!-- -->PROJECT_NAME}
)

Next, install the target and create an export target (export_${PROJECT_NAME}) that will be used by other code to find this package. Note that you can install all libraries in a project with one install call.

install(
  TARGETS my_library
  EXPORT export_${<!-- -->PROJECT_NAME}
  LIBRARY DESTINATION lib
  ARCHIVE DESTINATION lib
  RUNTIME DESTINATION bin
)

ament_export_targets(export_${<!-- -->PROJECT_NAME} HAS_LIBRARY_TARGET)
ament_export_dependencies(some_dependency)

Here’s what’s happening in the code snippet above:

The ament_export_targets macro exports targets for CMake. This is required to allow clients of the library to use the target_link_libraries(client PRIVATE my_library::my_library) syntax. If the export set contains a library, add the HAS_LIBRARY_TARGET option to ament_export_targets which will add the potential library to the environment variables.

ament_export_dependencies will export dependencies to downstream packages. This is necessary so that users of the library do not have to also call find_package for these dependent packages.

Calling ament_export_targets, ament_export_dependencies, or other ament commands from a CMake subdirectory will not work correctly. This is because the CMake subdirectory cannot set the necessary variables in the parent scope where ament_package is called.

Windows DLLs are considered runtime artifacts and installed into the RUNTIME DESTINATION folder. Therefore, it is recommended to keep RUNTIME installed even when developing libraries on Unix-based systems.

Installing the EXPORT symbol called requires additional attention: it installs the CMake files for the my_library target. Its name must be exactly the same as the parameter in ament_export_targets. To ensure that it is available through ament_target_dependencies, its name should not be exactly the same as the library name, but should be prefixed with export_ (as shown in the image above).

All installation paths are relative to CMAKE_INSTALL_PREFIX, which is correctly set by colcon/ament.

There are two additional features available but are redundant for target-based installations:

ament_export_include_directories("include/${PROJECT_NAME}")
ament_export_libraries(my_library)

The first macro marks the exported include directory. The second macro marks the location of the installed library (this is done via the HAS_LIBRARY_TARGET parameter when calling ament_export_targets). These macros should only be used if the downstream project cannot or does not want to use dependencies based on the CMake target.

For non-target exports, some macros can take different types of arguments, but since the modern Make recommended way is to use targets, we won’t cover that here. Documentation for these options can be found in the source code.

1.6.2 Executable file

When installing the executable, you must strictly follow these instructions to find the other parts of the ROS tools:

install(TARGETS my_exe
    DESTINATION lib/${<!-- -->PROJECT_NAME})

If a package has both libraries and executables, be sure to combine the suggestions in “Libraries” and “Executables” above.

2. Example