Skip to content

C++ 软件包迁移参考

本页面介绍了如何将 C++ 软件包的各部分从 ROS 1 迁移到 ROS 2。 如果是首次迁移 C++ 软件包,建议先阅读 C++ 迁移示例,再以本页面作为参考来迁移自己的软件包。

ROS 2 不再使用 catkin_make、catkin_make_isolated 或 catkin build,而是使用命令行工具 colcon 来构建和安装一组软件包。 请参阅入门教程了解 colcon 的基本用法。

更新 CMakeLists.txt 以使用 ament_cmake

Section titled “更新 CMakeLists.txt 以使用 ament_cmake”

ROS 2 C++ 软件包使用 CMake 以及 ament_cmake 提供的便利函数。 以下是改用 ament_cmake 替代 catkin 所需的更改。

ROS 2 依赖于比 ROS 1 更高版本的 CMake。 在 REP 2000 中查找目标 ROS 发行版的最低 CMake 版本要求,并在 CMakeLists.txt 顶部指定该版本。 例如,3.14.4 是 ROS Humble 的最低推荐支持版本。

cmake_minimum_required(VERSION 3.20)

从 package.xml 中删除对 catkin 的任何依赖:

# Remove this!
<buildtool_depend>catkin</buildtool_depend>

添加对 ament_cmake_ros 的新依赖(示例):

<buildtool_depend>ament_cmake_ros</buildtool_depend>

如果 package.xml 中还没有 <export> 部分,则添加一个。 将 <build_type> 设置为 ament_cmake(示例)

<export>
<build_type>ament_cmake</build_type>
</export>

在 CMakeLists.txt 底部插入 ament_package() 调用(示例)

# Add this to the bottom of your CMakeLists.txt
ament_package()

将 find_package(catkin COMPONENTS ...) 调用替换为单独的 find_package() 调用(示例)。

例如,将以下代码:

find_package(catkin REQUIRED COMPONENTS foo bar std_msgs)
find_package(baz REQUIRED)

改为:

find_package(ament_cmake_ros REQUIRED)
find_package(foo REQUIRED)
find_package(bar REQUIRED)
find_package(std_msgs REQUIRED)
find_package(baz REQUIRED)

建议优先使用 per-target 的 CMake 函数,以便软件包可以导出现代 CMake targets。

如果 CMakeLists.txt 中使用了 include_directories(),请删除这些调用。

# Delete calls to include_directories like this one!
include_directories(include ${catkin_INCLUDE_DIRS})

为软件包中的每个库添加 target_include_directories() 调用(示例)。

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

将所有 target_link_libraries() 调用改为使用现代 CMake targets。 例如,如果 ROS 1 软件包使用了旧式 CMake 变量,如下所示:

target_link_libraries(my_library ${catkin_LIBRARIES} ${baz_LIBRARIES})

则改为使用具体的现代 CMake targets:

target_link_libraries(my_library PUBLIC foo::foo bar::bar std_msgs::std_msgs baz::baz)

根据依赖项在库中的使用方式,选择 PUBLIC 或 PRIVATE(示例)。

  • 如果下游用户也需要该依赖项(例如库的公共 API 使用了它),则选择 PUBLIC。
  • 如果依赖项仅在库内部使用,则选择 PRIVATE。

用各种 ament_cmake 调用替换 catkin_package()

Section titled “用各种 ament_cmake 调用替换 catkin_package()”

假设 CMakeLists.txt 中有如下 catkin_package 调用:

catkin_package(
INCLUDE_DIRS include
LIBRARIES my_library
CATKIN_DEPENDS foo bar std_msgs
DEPENDS baz
)
install(TARGETS my_library
ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION}
)

如果已经使用了现代 CMake targets 和 target_include_directories(),则无需再做其他处理。 下游用户通过依赖现代 CMake targets 即可获取 include 目录。

使用 ament_export_targets() 和 install(TARGETS ... EXPORT ...) 来替换 LIBRARIES 参数。

安装 my_library target 时使用 EXPORT 关键字(示例)。

install(TARGETS my_library EXPORT export_my_package
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
)

以上是库 targets 的推荐默认设置。 如果软件包使用了不同的 CATKIN_*_DESTINATION 变量,请按以下方式转换:

catkinament_cmake
CATKIN_GLOBAL_BIN_DESTINATIONbin
CATKIN_GLOBAL_INCLUDE_DESTINATIONinclude
CATKIN_GLOBAL_LIB_DESTINATIONlib
CATKIN_GLOBAL_LIBEXEC_DESTINATIONlib
CATKIN_GLOBAL_SHARE_DESTINATIONshare
CATKIN_PACKAGE_BIN_DESTINATIONlib/${PROJECT_NAME}
CATKIN_PACKAGE_INCLUDE_DESTINATIONinclude/${PROJECT_NAME}
CATKIN_PACKAGE_LIB_DESTINATIONlib
CATKIN_PACKAGE_SHARE_DESTINATIONshare/${PROJECT_NAME}

添加 ament_export_targets() 调用,名称与 EXPORT 关键字指定的名称相同(示例)。

ament_export_targets(export_my_package)

替换 catkin_package(CATKIN_DEPENDS .. DEPENDS ..)

Section titled “替换 catkin_package(CATKIN_DEPENDS .. DEPENDS ..)”

下游软件包的用户必须能够 find_package() 软件包公共 API 所使用的依赖项。 在 ROS 1 中,这是通过 CATKIN_DEPENDS 和 DEPENDS 参数为下游用户自动完成的。 在 ROS 2 中,请使用 ament_export_dependencies 来实现。

ament_export_dependencies(
foo
bar
std_msgs
baz
)

如果软件包同时包含 C++ 代码和 ROS 消息、服务或动作定义,建议将其拆分为两个软件包:

  • 一个仅包含 ROS 消息、服务和/或动作定义的软件包
  • 一个包含 C++ 代码的软件包

在包含 ROS 消息的软件包的 package.xml 中添加以下依赖项:

  1. 添加对 rosidl_default_generators 的 <buildtool_depend>(示例)

    <buildtool_depend>rosidl_default_generators</buildtool_depend>
  2. 添加对 rosidl_default_runtime 的 <exec_depend>(示例)

    <exec_depend>rosidl_default_runtime</exec_depend>
  3. 添加一个 <member_of_group> 标签,组名为 rosidl_interface_packages(示例)

    <member_of_group>rosidl_interface_packages</member_of_group>

在你的 CMakeLists.txt 中,用 rosidl_generate_interfaces 替换 add_message_files、add_service_files 和 generate_messages 的调用。 由于此 bug,第一个参数必须是 ${PROJECT_NAME}。

例如,如果你的 ROS 1 软件包如下所示:

add_message_files(DIRECTORY msg FILES FooBar.msg Baz.msg)
add_service_files(DIRECTORY srv FILES Ping.srv)
add_action_files(DIRECTORY action FILES DoPong.action)
generate_messages(
DEPENDENCIES actionlib_msgs std_msgs geometry_msgs
)

则将其更改为以下内容(示例)

rosidl_generate_interfaces(${PROJECT_NAME}
"msg/FooBar.msg"
"msg/Baz.msg"
"srv/Ping.srv"
"action/DoPong.action"
DEPENDENCIES actionlib_msgs std_msgs geometry_msgs
)

删除对 devel 空间 的任何引用,例如 CATKIN_DEVEL_PREFIX。 ROS 2 中没有 devel 空间 的等效物。

如果你的软件包使用 gtest,则:

  • 将 CATKIN_ENABLE_TESTING 替换为 BUILD_TESTING。
  • 将 catkin_add_gtest 替换为 ament_add_gtest。
  • 为 ament_cmake_gtest 而非 GTest 添加 find_package()

例如,如果你的 ROS 1 软件包如下添加测试:

if (CATKIN_ENABLE_TESTING)
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})
catkin_add_gtest(my_test src/test/some_test.cpp)
target_link_libraries(my_test
# ...
${GTEST_LIBRARIES})
endif()

则将其更改为:

if (BUILD_TESTING)
find_package(ament_cmake_gtest REQUIRED)
ament_add_gtest(my_test src/test/test_something.cpp)
target_link_libraries(my_test
#...
)
endif()

在你的 package.xml 中添加 <test_depend>ament_cmake_gtest</test_depend>(示例)。

<test_depend>ament_cmake_gtest</test_depend>

ROS 2 代码的风格指南与 ROS 1 不同。

如果你选择遵循 ROS 2 风格指南,则通过在 if(BUILD_TESTING) 块中添加以下行来启用自动 linter 测试:

if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
ament_lint_auto_find_test_dependencies()
# ...
endif()

在你的 package.xml 中添加以下依赖项:

<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>

ROS 2 消息、服务和动作的命名空间在软件包名称之后使用子命名空间(分别为 msg、srv 或 action)。 因此 include 看起来像:#include <my_interfaces/msg/my_message.hpp>。 C++ 类型随后命名为:my_interfaces::msg::MyMessage。

共享指针类型在消息结构体中作为 typedef 提供:my_interfaces::msg::MyMessage::SharedPtr 以及 my_interfaces::msg::MyMessage::ConstSharedPtr。

有关更多详细信息,请参阅关于生成的 C++ 接口的文章。

迁移需要 include 做以下更改:

  • 在软件包名称和消息数据类型之间插入子文件夹 msg
  • 将包含的文件名从 CamelCase 更改为下划线分隔
  • 从 *.h 更改为 *.hpp
// ROS 1 style is in comments, ROS 2 follows, uncommented.
// # include <geometry_msgs/PointStamped.h>
#include <geometry_msgs/msg/point_stamped.hpp>
// geometry_msgs::PointStamped point_stamped;
geometry_msgs::msg::PointStamped point_stamped;

迁移需要代码在所有实例中插入 msg 命名空间。

ROS 2 中的服务回调没有布尔返回值。 不是在失败时返回 false,而是建议抛出异常。

// ROS 1 style is in comments, ROS 2 follows, uncommented.
// #include "nav_msgs/GetMap.h"
#include "nav_msgs/srv/get_map.hpp"
// bool service_callback(
// nav_msgs::GetMap::Request & request,
// nav_msgs::GetMap::Response & response)
void service_callback(
const std::shared_ptr<nav_msgs::srv::GetMap::Request> request,
std::shared_ptr<nav_msgs::srv::GetMap::Response> response)
{
// ...
// return true; // or false for failure
}

对于 ros::Time 的使用:

  • 将所有 ros::Time 实例替换为 rclcpp::Time

  • 如果你的消息或代码使用了 std_msgs::Time:

    • 将所有 std_msgs::Time 实例转换为 builtin_interfaces::msg::Time

    • 将所有 #include "std_msgs/time.h" 转换为 #include "builtin_interfaces/msg/time.hpp"

    • 将所有使用 std_msgs::Time 字段 nsec 的实例转换为 builtin_interfaces::msg::Time 字段 nanosec

有一个等效类型 rclcpp::Rate 对象,它基本上是 ros::Rate 的直接替代品。

Boost 以前提供的许多功能已被集成到 C++ 标准库中。 因此,我们希望利用新的核心功能,并尽可能避免对 boost 的依赖。

要将共享指针从 boost 切换到标准 C++,请替换以下实例:

  • #include <boost/shared_ptr.hpp> 替换为 #include <memory>
  • boost::shared_ptr 替换为 std::shared_ptr

还可能有 weak_ptr 等变体也需要转换。

此外,推荐使用 using 而不是 typedef。 using 在模板化逻辑中能更好地工作。 详情请参见这里

ROS 代码库中使用的 boost 的另一个常见部分是 boost::thread 中的互斥锁。

  • 将 boost::mutex::scoped_lock 替换为 std::unique_lock<std::mutex>
  • 将 boost::mutex 替换为 std::mutex
  • 将 #include <boost/thread/mutex.hpp> 替换为 #include <mutex>

替换:

  • #include <boost/unordered_map.hpp> 替换为 #include <unordered_map>
  • boost::unordered_map 替换为 std::unordered_map

替换:

  • #include <boost/function.hpp> 替换为 #include <functional>
  • boost::function 替换为 std::function