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 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()
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()
This pattern appears in various places in ROS 2:
if(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE)
message("${PROJECT_NAME}: Defaulting to Release build")
set(CMAKE_BUILD_TYPE Release)
endif()
install(
PROGRAMS
scripts/script1.py
scripts/script2.py
DESTINATION lib/${PROJECT_NAME}
)
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")
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")
Add eigen to your package.xml as a dependency, and then:
find_package(Eigen3 REQUIRED)
include_directories(${EIGEN3_INCLUDE_DIRS})
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}"
)