Skip to content

CMake

While you don't need to know everything about CMake to use ROS 2, knowing a bit will really be helpful. You might be interested in the CMake tutorial which explains the basics of CMake.

Ament

Ament is a set of CMake modules specifically designed for ROS 2 with the intent of making CMake easier to use. See the Ament CMake documentation.

The basic structure of an ament package:

cmake_minimum_required(VERSION 3.8)
project(my_package_name)

# Default to C++17
if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 17)
endif()

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

# Find packages
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)

# Include our own headers
include_directories(include)

# Create a node
add_executable(my_node src/my_node.cpp)
ament_target_dependencies(my_node
  rclcpp
  # Other ament dependencies
  # This sets up include and linker paths
)

add_library(my_library src/my_library.cpp)
ament_target_dependencies(my_library
  rclcpp
)

# Install our headers
install(
  DIRECTORY include/
  DESTINATION include
)

# Install our node and library
install(
  TARGETS my_node my_library
  ARCHIVE DESTINATION lib
  LIBRARY DESTINATION lib
  RUNTIME DESTINATION lib/${PACKAGE_NAME}
)

# Install some Python scripts
install(
  PROGRAMS
    scripts/my_script.py
  DESTINATION
    lib/${PROJECT_NAME}
)

# Tell downstream packages where to find our headers
ament_export_include_directories(include)
# Tell downstream packages our libraries to link against
ament_export_libraries(my_library)
# Help downstream packages to find transitive dependencies
ament_export_dependencies(
  rclcpp
)
ament_package()

Linting Configuration

I prefer a more ROS 1-style code style. To allow braces to be on their own lines:

if(BUILD_TESTING)
  find_package(ament_cmake_cpplint)
  ament_cpplint(FILTERS "-whitespace/braces" "-whitespace/newline")
endif()

Installing Python Scripts

install(
  PROGRAMS
    scripts/script1.py
    scripts/script2.py
  DESTINATION lib/${PROJECT_NAME}
)

Depending on Messages in Same Package

It is generally best practice to put messages in separate packages, but sometimes, especially for drivers, you want the messages in the same package.

The following example worked in earlier versions of ROS 2 - but the syntax has changed See the Implementing custom interfaces tutorial for newer ROS 2 distributions.

# Note: this WILL NOT work in ROS 2 Humble or later
find_package(rosidl_default_generators REQUIRED)

# Generate some messages
rosidl_generate_interfaces(${PROJECT_NAME}
  "msg/MyMessage.msg"
)

# Add a node which uses the messages
add_executable(my_node my_node.cpp)
rosidl_target_interfaces(my_node ${PROJECT_NAME} "rosidl_typesupport_cpp")

Removing Boost from Pluginlib

Pluginlib supports both boost::shared_ptrs and std::shared_ptrs by default, if you want to avoid depending on Boost in your shiny new ROS 2 library, you need to specifically tell pluginlib not to include the Boost versions:

target_compile_definitions(your_library PUBLIC "PLUGINLIB__DISABLE_BOOST_FUNCTIONS")

Using Eigen3

Add eigen to your package.xml as a dependency, and then:

find_package(Eigen3 REQUIRED)
include_directories(${EIGEN3_INCLUDE_DIRS})

Building Python Extensions in C++

The example below is based on the etherbotix package.

find_package(PythonLibs REQUIRED)
find_package(Boost REQUIRED python)
find_package(ament_cmake_python REQUIRED)
find_package(python_cmake_module REQUIRED)

ament_python_install_package(${PROJECT_NAME})

add_library(
  my_python SHARED
  ${SOURCE_FILES}
)
set_target_properties(
  my_python PROPERTIES
  LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}
  PREFIX ""
)
target_link_libraries(my_python
  ${Boost_LIBRARIES}
  ${PYTHON_LIBRARIES}
)

install(
  TARGETS my_python
  DESTINATION "${PYTHON_INSTALL_DIR}/${PROJECT_NAME}"
)