.. redirect-from::
Guides/Ament-CMake-Documentation
Tutorials/Ament-CMake-Documentation
ament_cmake user documentation
==============================
``ament_cmake`` is the build system for CMake based packages in ROS 2 (in particular, it will be used for most C/C++ projects).
It is a set of scripts enhancing CMake and adding convenience functionality for package authors.
Before using ``ament_cmake``, it is very helpful to know the basics of `CMake `__.
An official tutorial can be found `here `__.
.. contents:: Table of Contents
:depth: 2
:local:
Basics
------
A basic CMake outline can be produced using ``ros2 pkg create `` on the command line.
The build information is then gathered in two files: the ``package.xml`` and the ``CMakeLists.txt``, which must be in the same directory.
The ``package.xml`` must contain all dependencies and a bit of metadata to allow colcon to find the correct build order for your packages, to install the required dependencies in CI, and to provide the information for a release with ``bloom``.
The ``CMakeLists.txt`` contains the commands to build and package executables and libraries and will be the main focus of this document.
Basic project outline
^^^^^^^^^^^^^^^^^^^^^
The basic outline of the ``CMakeLists.txt`` of an ament package contains:
.. code-block:: cmake
cmake_minimum_required(VERSION 3.8)
project(my_project)
ament_package()
The argument to ``project`` will be the package name and must be identical to the package name in the ``package.xml``.
The project setup is done by ``ament_package()`` and this call must occur exactly once per package.
``ament_package()`` installs the ``package.xml``, registers the package with the ament index, and installs configuration (and possibly target) files for CMake so that it can be found by other packages using ``find_package``.
Since ``ament_package()`` gathers a lot of information from the ``CMakeLists.txt`` it should be the last call in your ``CMakeLists.txt``.
``ament_package`` can be given additional arguments:
- ``CONFIG_EXTRAS``: a list of CMake files (``.cmake`` or ``.cmake.in`` templates expanded by ``configure_file()``) which should be available to clients of the package.
For an example of when to use these arguments, see the discussion in `Adding resources`_.
For more information on how to use template files, see `the official documentation `__.
- ``CONFIG_EXTRAS_POST``: same as ``CONFIG_EXTRAS``, but the order in which the files are added differs.
While ``CONFIG_EXTRAS`` files are included before the files generated for the ``ament_export_*`` calls the files from ``CONFIG_EXTRAS_POST`` are included afterwards.
Instead of adding to ``ament_package``, you can also add to the variable ``${PROJECT_NAME}_CONFIG_EXTRAS`` and ``${PROJECT_NAME}_CONFIG_EXTRAS_POST`` with the same effect.
The only difference is again the order in which the files are added with the following total order:
- files added by ``CONFIG_EXTRAS``
- files added by appending to ``${PROJECT_NAME}_CONFIG_EXTRAS``
- files added by appending to ``${PROJECT_NAME}_CONFIG_EXTRAS_POST``
- files added by ``CONFIG_EXTRAS_POST``
Compiler and linker options
^^^^^^^^^^^^^^^^^^^^^^^^^^^
ROS 2 targets compilers which comply with the C++17 and C99 standard.
Newer versions might be targeted in the future and are referenced `here `__.
Therefore it is customary to set the corresponding CMake flags:
.. code-block:: cmake
if(NOT CMAKE_C_STANDARD)
set(CMAKE_C_STANDARD 99)
endif()
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 17)
endif()
To keep the code clean, compilers should throw warnings for questionable code and these warnings should be fixed.
It is recommended to at least cover the following warning levels:
- For Visual Studio: the default ``W1`` warnings
- For GCC and Clang: ``-Wall -Wextra -Wpedantic`` are highly recommended and ``-Wshadow`` is advisable
It is currently recommended to use ``add_compile_options`` to add these options for all targets.
This avoids cluttering the code with target-based compile options for all executables, libraries, and tests:
.. code-block:: cmake
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
Finding dependencies
^^^^^^^^^^^^^^^^^^^^
Most ``ament_cmake`` projects will have dependencies on other packages.
In CMake, this is accomplished by calling ``find_package``.
For instance, if your package depends on ``rclcpp``, then the ``CMakeLists.txt`` file should contain:
.. code-block:: cmake
find_package(rclcpp REQUIRED)
.. note::
It should never be necessary to ``find_package`` a library that is not explicitly needed but is a dependency of another dependency that is explicitly needed.
If that is the case, file a bug against the corresponding package.
Adding targets
^^^^^^^^^^^^^^
In CMake nomenclature, ``targets`` are the artifacts that this project will create.
Either libraries or executables can be created, and a single project can contain zero or many of each of them.
.. tabs::
.. group-tab:: Libraries
These are created with a call to ``add_library``, which should contain both the name of the target and the source files that should be compiled to create the library.
With the separation of header files and implementation in C/C++, it is not usually necessary to add header files as arguments to ``add_library``.
The following best practice is proposed:
- Put all headers which should be usable by clients of this library (and therefore must be installed) into a subdirectory of the ``include`` folder named like the package, while all other files (``.c/.cpp`` and header files which should not be exported) are inside the ``src`` folder
- Only ``.c/.cpp`` files are explicitly referenced in the call to ``add_library``
- Find headers to your library ``my_library`` via
.. code-block:: cmake
target_include_directories(my_library
PUBLIC
"$"
"$")
This adds all files in the folder ``${CMAKE_CURRENT_SOURCE_DIR}/include`` to the public interface during build time and all files in the include folder (relative to ``${CMAKE_INSTALL_DIR}``) when being installed.
``ros2 pkg create`` creates a package layout that follows these rules.
.. note::
Since Windows is one of the officially supported platforms, to have maximum impact, any package should also build on Windows.
The Windows library format enforces symbol visibility; that is, every symbol which should be used from a client has to be explicitly exported by the library (and symbols need to be implicitly imported).
Since GCC and Clang builds do not generally do this, it is advised to use the logic in `the GCC wiki `__.
To use it for a package called ``my_library``:
- Copy the logic in the link into a header file called ``visibility_control.hpp``.
- Replace ``DLL`` by ``MY_LIBRARY`` (for an example, see visibility control of `rviz_rendering `__).
- Use the macros "MY_LIBRARY_PUBLIC" for all symbols you need to export (i.e. classes or functions).
- In the project ``CMakeLists.txt`` use:
.. code-block:: cmake
target_compile_definitions(my_library PRIVATE "MY_LIBRARY_BUILDING_LIBRARY")
For more details, see :ref:`Windows Symbol Visibility in the Windows Tips and Tricks document `.
.. group-tab:: Executables
These should be created with a call to ``add_executable``, which should contain both the name of the target and the source files that should be compiled to create the executable.
The executable may also have to be linked with any libraries created in this package by using ``target_link_libraries``.
Since executables aren't generally used by clients as a library, no header files need to be put in the ``include`` directory.
In the case that a package has both libraries and executables, make sure to combine the advice from both "Libraries" and "Executables" above.
Linking to dependencies
^^^^^^^^^^^^^^^^^^^^^^^
There are two ways to link your targets against a dependency.
The first and recommended way is to use the ament macro ``ament_target_dependencies``.
As an example, suppose we want to link ``my_library`` against the linear algebra library Eigen3.
.. code-block:: cmake
find_package(Eigen3 REQUIRED)
ament_target_dependencies(my_library PUBLIC Eigen3)
It includes the necessary headers and libraries and their dependencies to be correctly found by the project.
The second way is to use ``target_link_libraries``.
Modern CMake prefers to use only targets, exporting and linking against them.
CMake targets may be namespaced, similar to C++.
Prefer to use the namespaced targets if they are available.
For instance, ``Eigen3`` defines the target ``Eigen3::Eigen``.
In the example of Eigen3, the call should then look like
.. code-block:: cmake
target_link_libraries(my_library PUBLIC Eigen3::Eigen)
This will also include necessary headers, libraries and their dependencies.
Note that this dependency must have been previously discovered via a call to ``find_package``.
Installing
^^^^^^^^^^
.. tabs::
.. group-tab:: Libraries
When building a reusable library, some information needs to be exported for downstream packages to easily use it.
First, install the headers files which should be available to clients.
The include directory is custom to support overlays in ``colcon``; see https://colcon.readthedocs.io/en/released/user/overriding-packages.html#install-headers-to-a-unique-include-directory for more information.
.. code-block:: cmake
install(
DIRECTORY include/
DESTINATION include/${PROJECT_NAME}
)
Next, install the targets and create the export target (``export_${PROJECT_NAME}``) that other code will use to find this package.
Note that you can use a single ``install`` call to install all of the libraries in the project.
.. code-block:: cmake
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 is what's happening in the snippet above:
- The ``ament_export_targets`` macro exports the targets for CMake.
This is necessary to allow your library's clients to use the ``target_link_libraries(client PRIVATE my_library::my_library)`` syntax.
If the export set includes a library, add the option ``HAS_LIBRARY_TARGET`` to ``ament_export_targets``, which adds potential libraries to environment variables.
- The ``ament_export_dependencies`` exports dependencies to downstream packages.
This is necessary so that the user of the library does not have to call ``find_package`` for those dependencies, too.
.. warning::
Calling ``ament_export_targets``, ``ament_export_dependencies``, or other ament commands from a CMake subdirectory will not work as expected.
This is because the CMake subdirectory has no way of setting necessary variables in the parent scope where ``ament_package`` is called.
.. note::
Windows DLLs are treated as runtime artifacts and installed into the ``RUNTIME DESTINATION`` folder.
It is therefore advised to keep the ``RUNTIME`` install even when developing libraries on Unix based systems.
- The ``EXPORT`` notation of the install call requires additional attention:
It installs the CMake files for the ``my_library`` target.
It must be named exactly the same as the argument in ``ament_export_targets``.
To ensure that it can be used via ``ament_target_dependencies``, it should not be named exactly the same as the library name, but instead should have a prefix like ``export_`` (as shown above).
- All install paths are relative to ``CMAKE_INSTALL_PREFIX``, which is already set correctly by colcon/ament.
There are two additional functions which are available, but are superfluous for target based installs:
.. code-block:: cmake
ament_export_include_directories("include/${PROJECT_NAME}")
ament_export_libraries(my_library)
The first macro marks the directory of the exported include directories.
The second macro marks the location of the installed library (this is done by the ``HAS_LIBRARY_TARGET`` argument in the call to ``ament_export_targets``).
These should only be used if the downstream projects can't or don't want to use CMake target based dependencies.
Some of the macros can take different types of arguments for non-target exports, but since the recommended way for modern Make is to use targets, we will not cover them here.
Documentation of these options can be found in the source code itself.
.. group-tab:: Executables
When installing an executable, the following stanza *must be followed exactly* for the rest of the ROS tooling to find it:
.. code-block:: cmake
install(TARGETS my_exe
DESTINATION lib/${PROJECT_NAME})
In the case that a package has both libraries and executables, make sure to combine the advice from both "Libraries" and "Executables" above.
Linting and Testing
-------------------
In order to separate testing from building the library with colcon, wrap all calls to linters and tests in a conditional:
.. code-block:: cmake
if(BUILD_TESTING)
find_package(ament_cmake_gtest REQUIRED)
ament_add_gtest()
endif()
Linting
^^^^^^^
It's advised to use the combined call from `ament_lint_auto `_:
.. code-block:: cmake
find_package(ament_lint_auto REQUIRED)
ament_lint_auto_find_test_dependencies()
This will run linters as defined in the ``package.xml``.
It is recommended to use the set of linters defined by the package ``ament_lint_common``.
The individual linters included there, as well as their functions, can be seen in the `ament_lint_common docs `_.
Linters provided by ament can also be added separately, instead of running ``ament_lint_auto``.
One example of how to do so can be found in the `ament_cmake_lint_cmake documentation `_.
Testing
^^^^^^^
Ament contains CMake macros to simplify setting up GTests. Call:
.. code-block:: cmake
find_package(ament_cmake_gtest)
ament_add_gtest(some_test )
to add a GTest.
This is then a regular target which can be linked against other libraries (such as the project library).
The macros have additional parameters:
- ``APPEND_ENV``: append environment variables.
For instance you can add to the ament prefix path by calling:
.. code-block:: cmake
find_package(ament_cmake_gtest REQUIRED)
ament_add_gtest(some_test
APPEND_ENV PATH=some/addtional/path/for/testing/resources)
- ``APPEND_LIBRARY_DIRS``: append libraries so that they can be found by the linker at runtime.
This can be achieved by setting environment variables like ``PATH`` on Windows and ``LD_LIBRARY_PATH`` on Linux, but this makes the call platform specific.
- ``ENV``: set environment variables (same syntax as ``APPEND_ENV``).
- ``TIMEOUT``: set a test timeout in second. The default for GTests is 60 seconds. For example:
.. code-block:: cmake
ament_add_gtest(some_test TIMEOUT 120)
- ``SKIP_TEST``: skip this test (will be shown as "passed" in the console output).
- ``SKIP_LINKING_MAIN_LIBRARIES``: Don't link against GTest.
- ``WORKING_DIRECTORY``: set the working directory for the test.
The default working directory otherwise is the ``CMAKE_CURRENT_BINARY_DIR``, which is described in the `CMake documentation `_.
Similarly, there is a CMake macro to set up GTest including GMock:
.. code-block:: cmake
find_package(ament_cmake_gmock REQUIRED)
ament_add_gmock(some_test )
It has the same additional parameters as ``ament_add_gtest``.
Extending ament
---------------
It is possible to register additional macros/functions with ``ament_cmake`` and extend it in several ways.
Adding a function/macro to ament
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Extending ament will often times mean that you want to have some functions available to other packages.
The best way to provide the macro to client packages is to register it with ament.
This can be done by appending the ``${PROJECT_NAME}_CONFIG_EXTRAS`` variable, which is used by ``ament_package()`` via
.. code-block:: cmake
list(APPEND ${PROJECT_NAME}_CONFIG_EXTRAS
path/to/file.cmake"
other/pathto/file.cmake"
)
Alternatively, you can directly add the files to the ``ament_package()`` call:
.. code-block:: cmake
ament_package(CONFIG_EXTRAS
path/to/file.cmake
other/pathto/file.cmake
)
Adding to extension points
^^^^^^^^^^^^^^^^^^^^^^^^^^
In addition to simple files with functions that can be used in other packages, you can also add extensions to ament.
Those extensions are scripts which are executed with the function which defines the extension point.
The most common use-case for ament extensions is probably registering rosidl message generators:
When writing a generator, you normally want to generate all messages and services with your generator also without modifying the code for the message/service definition packages.
This is possible by registering the generator as an extension to ``rosidl_generate_interfaces``.
As an example, see
.. code-block:: cmake
ament_register_extension(
"rosidl_generate_interfaces"
"rosidl_generator_cpp"
"rosidl_generator_cpp_generate_interfaces.cmake")
which registers the macro ``rosidl_generator_cpp_generate_interfaces.cmake`` for the package ``rosidl_generator_cpp`` to the extension point ``rosidl_generate_interfaces``.
When the extension point gets executed, this will trigger the execution of the script ``rosidl_generator_cpp_generate_interfaces.cmake`` here.
In particular, this will call the generator whenever the function ``rosidl_generate_interfaces`` gets executed.
The most important extension point for generators, aside from ``rosidl_generate_interfaces``, is ``ament_package``, which will simply execute scripts with the ``ament_package()`` call.
This extension point is useful when registering resources (see below).
``ament_register_extension`` is a function which takes exactly three arguments:
- ``extension_point``: The name of the extension point (most of the time this will be one of ``ament_package`` or ``rosidl_generate_interfaces``)
- ``package_name``: The name of the package containing the CMake file (i.e. the project name of the project where the file is written to)
- ``cmake_filename``: The CMake file executed when the extension point is run
.. note::
It is possible to define custom extension points in a similar manner to ``ament_package`` and ``rosidl_generate_interfaces``, but this should hardly be necessary.
Adding extension points
^^^^^^^^^^^^^^^^^^^^^^^
Very rarely, it might be interesting to define a new extension point to ament.
Extension points can be registered within a macro so that all extensions will be executed when the corresponding macro is called.
To do so:
- Define and document a name for your extension (e.g. ``my_extension_point``), which is the name passed to the ``ament_register_extension`` macro when using the extension point.
- In the macro/function which should execute the extensions call:
.. code-block:: cmake
ament_execute_extensions(my_extension_point)
Ament extensions work by defining a variable containing the name of the extension point and filling it with the macros to be executed.
Upon calling ``ament_execute_extensions``, the scripts defined in the variable are then executed one after another.
Adding resources
----------------
Especially when developing plugins or packages which allow plugins it is often essential to add resources to one ROS package from another (e.g. a plugin).
Examples can be plugins for tools using the pluginlib.
This can be achieved using the ament index (also called "resource index").
The ament index explained
^^^^^^^^^^^^^^^^^^^^^^^^^
For details on the design and intentions, see `here `__
In principle, the ament index is contained in a folder within the install/share folder of your package.
It contains shallow subfolders named after different types of resources.
Within the subfolder, each package providing said resource is referenced by name with a "marker file".
The file may contain whatever content necessary to obtain the resources, e.g. relative paths to the installation directories of the resource, it may also be simply empty.
To give an example, consider providing display plugins for RViz:
When providing RViz plugins in a project named ``my_rviz_displays`` which will be read by the pluginlib, you will provide a ``plugin_description.xml`` file, which will be installed and used by the pluginlib to load the plugins.
To achieve this, the plugin_description.xml is registered as a resource in the resource_index via
.. code-block:: cmake
pluginlib_export_plugin_description_file(rviz_common plugins_description.xml)
When running ``colcon build``, this installs a file ``my_rviz_displays`` into a subfolder ``rviz_common__pluginlib__plugin`` into the resource_index.
Pluginlib factories within rviz_common will know to gather information from all folders named ``rviz_common__pluginlib__plugin`` for packages that export plugins.
The marker file for pluginlib factories contains an install-folder relative path to the ``plugins_description.xml`` file (and the name of the library as marker file name).
With this information, the pluginlib can load the library and know which plugins to load from the ``plugin_description.xml`` file.
As a second example, consider the possibility to let your own RViz plugins use your own custom meshes.
Meshes get loaded at startup time so that the plugin owner does not have to deal with it, but this implies RViz has to know about the meshes.
To achieve this, RViz provides a function:
.. code-block:: cmake
register_rviz_ogre_media_exports(DIRECTORIES )
This registers the directories as an ogre_media resource in the ament index.
In short, it installs a file named after the project which calls the function into a subfolder called ``rviz_ogre_media_exports``.
The file contains the install folder relative paths to the directories listed in the macros.
On startup time, RViz can now search for all folders called ``rviz_ogre_media_exports`` and load resources in all folders provided.
These searches are done using ``ament_index_cpp`` (or ``ament_index_py`` for Python packages).
In the following sections we will explore how to add your own resources to the ament index and provide best practices for doing so.
Querying the ament index
^^^^^^^^^^^^^^^^^^^^^^^^
If necessary, it is possible to query the ament index for resources via CMake.
To do so, there are three functions:
``ament_index_has_resource``: Obtain a prefix path to the resource if it exists with the following parameters:
- ``var``: the output parameter: fill this variable with FALSE if the resource does not exist or the prefix path to the resource otherwise
- ``resource_type``: The type of the resource (e.g. ``rviz_common__pluginlib__plugin``)
- ``resource_name``: The name of the resource which usually amounts to the name of the package having added the resource of type resource_type (e.g. ``rviz_default_plugins``)
``ament_index_get_resource``: Obtain the content of a specific resource, i.e. the contents of the marker file in the ament index.
- ``var``: the output parameter: filled with the content of the resource marker file if it exists.
- ``resource_type``: The type of the resource (e.g. ``rviz_common__pluginlib__plugin``)
- ``resource_name``: The name of the resource which usually amounts to the name of the package having added the resource of type resource_type (e.g. ``rviz_default_plugins``)
- ``PREFIX_PATH``: The prefix path to search for (usually, the default ``ament_index_get_prefix_path()`` will be enough).
Note that ``ament_index_get_resource`` will throw an error if the resource does not exist, so it might be necessary to check using ``ament_index_has_resource``.
``ament_index_get_resources``: Get all packages which registered resources of a specific type from the index
- ``var``: Output parameter: filled with a list of names of all packages which registered a resource of resource_type
- ``resource_type``: The type of the resource (e.g. ``rviz_common__pluginlib__plugin``)
- ``PREFIX_PATH``: The prefix path to search for (usually, the default ``ament_index_get_prefix_path()`` will be enough).
Adding to the ament index
^^^^^^^^^^^^^^^^^^^^^^^^^
Defining a resource requires two bits of information:
- a name for the resource which must be unique,
- a layout of the marker file, which can be anything and could also be empty (this is true for instance for the "package" resource marking a ROS 2 package)
For the RViz mesh resource, the corresponding choices were:
- ``rviz_ogre_media_exports`` as name of the resource,
- install path relative paths to all folders containing resources. This will already enable you to write the logic for using the corresponding resource in your package.
To allow users to easily register resources for your package, you should furthermore provide macros or functions such as the pluginlib function or ``rviz_ogre_media_exports`` function.
To register a resource, use the ament function ``ament_index_register_resource``.
This will create and install the marker files in the resource_index.
As an example, the corresponding call for ``rviz_ogre_media_exports`` is the following:
.. code-block:: cmake
ament_index_register_resource(rviz_ogre_media_exports CONTENT ${OGRE_MEDIA_RESOURCE_FILE})
This installs a file named like ``${PROJECT_NAME}`` into a folder ``rviz_ogre_media_exports`` into the resource_index with content given by variable ``${OGRE_MEDIA_RESOURCE_FILE}``.
The macro has a number of parameters that can be useful:
- the first (unnamed) parameter is the name of the resource, which amounts to the name of the folder in the resource_index
- ``CONTENT``: The content of the marker file as string. This could be a list of relative paths, etc. ``CONTENT`` cannot be used together with ``CONTENT_FILE``.
- ``CONTENT_FILE``: The path to a file which will be use to create the marker file. The file can be a plain file or a template file expanded with ``configure_file()``.
``CONTENT_FILE`` cannot be used together with ``CONTENT``.
- ``PACKAGE_NAME``: The name of the package/library exporting the resource, which amounts to the name of the marker file. Defaults to ``${PROJECT_NAME}``.
- ``AMENT_INDEX_BINARY_DIR``: The base path of the generated ament index. Unless really necessary, always use the default ``${CMAKE_BINARY_DIR}/ament_cmake_index``.
- ``SKIP_INSTALL``: Skip installing the marker file.
Since only one marker file exists per package, it is usually a problem if the CMake function/macro gets called twice by the same project.
However, for large projects it might be best to split up calls registering resources.
Therefore, it is best practice to let a macro registering a resource such as ``register_rviz_ogre_media_exports.cmake`` only fill some variables.
The real call to ``ament_index_register_resource`` can then be added within an ament extension to ``ament_package``.
Since there must only ever be one call to ``ament_package`` per project, there will always only be one place where the resource gets registered.
In the case of ``rviz_ogre_media_exports`` this amounts to the following strategy:
- The macro ``register_rviz_ogre_media_exports`` takes a list of folders and appends them to a variable called ``OGRE_MEDIA_RESOURCE_FILE``.
- Another macro called ``register_rviz_ogre_media_exports_hook`` calls ``ament_index_register_resource`` if ``${OGRE_MEDIA_RESOURCE_FILE}`` is non-empty.
- The ``register_rviz_ogre_media_exports_hook.cmake`` file is registered as an ament extension in a third file ``register_rviz_ogre_media_exports_hook-extras.cmake`` via calling
.. code-block:: cmake
ament_register_extension("ament_package" "rviz_rendering"
"register_rviz_ogre_media_exports_hook.cmake")
- The files ``register_rviz_ogre_media_exports.cmake`` and ``register_rviz_ogre_media_exports_hook-extra.cmake`` are registered as ``CONFIG_EXTRA`` with ``ament_package()``.