【ROS2原理3】:构建系统“ament_cmake”和构建工具“ament_tools”

发布于:2023-01-28 ⋅ 阅读:(1118) ⋅ 点赞:(0)

目录

一、提要

二、前言

三、关于ament

3.1 ament 命令行工具

3.2 集成任意构建系统

3.3 构建类型:ament_cmake

3.4 构建类型:cmake

3.5 构建类型:ament_python

四、环境创造

4.1 可选的符号链接安装

4.2 ament linter

4.3 ament_auto

4.4 其他资源

五、ment和catkin有什么不同

5.1 为什么不继续使用catkin

5.2 对catkin的额外改进

5.3 为什么不进化具有必要功能的catkin

5.4为什么要用新名字而不是继续叫它catkin

5.5 为什么 catkin_pkg 被 fork 到 ament_package

六、三种CMakeList.txt的区别

6.1 catkin版的

6.2 ament版的

6.3 ament_auto版的


一、提要

        最初撰写本文时,ament_tools 是 ROS 2 特定的构建工具。原因是它需要与现有的 ROS 1 软件包并排安装,这是由于不同的目标 Python 版本而导致的问题。同时,由于 Python 版本不同导致的问题已在 catkin_pkg 等共享依赖项中得到解决。从 ROS 2 开始,Bouncy ament_tools 已被 colcon 取代,如通用构建工具文章中所述。

二、前言

        ROS 是使用联合模型开发的。虽然这有很多优点,但它使构建多个相互依赖的包的过程更加复杂。

        为了构建一组相互依赖的包,用户必须以正确的拓扑顺序构建它们。用户必须查看每个包的文档以确定依赖关系,事先小心构建这些包,并确定构建该包的说明。最后,用户必须构建和安装每个包,并在继续构建下一个包之前进行任何环境配置。

        ROS 通过打包和构建系统约定以及自动化构建过程的工具来减轻这种复杂性。 ROS 尽可能使用标准工具,例如CMake 和 Python 设置工具。在这些工具缺乏对 ROS 需要做的事情的内置支持的情况下,额外的功能由 ROS 提供。

三、关于ament

        ament 是一个元构建系统,用于改进拆分为单独包的构建应用程序。它由两个主要部分组成:

  • 用于配置、构建和安装单个包的构建系统(例如 CMake、Python setuptools)
  • 一种按拓扑顺序调用单个包构建的工具

        该工具依赖于关于包的元信息来确定它们的依赖关系和它们的构建类型。此元信息在 REP 140 中指定的名为 package.xml 的清单文件中定义。

        每个包都使用自己的构建系统单独构建。为了使一个包的输出可用于其他包,每个包都可以扩展环境,使下游包可以找到并使用其工件和资源。例如,如果将生成的工件安装到 /usr 中,则可能根本不需要更改环境,因为这些文件夹通常会被各种工具搜索。

3.1 ament 命令行工具

        ament_tools 是一个 Python 包,它提供了用于构建、测试、安装和卸载包的命令行工具。它类似于 catkin_tools 并按照拓扑顺序在工作空间中构建每个包。通过扩展点集成了对不同构建系统的支持,这允许添加对其他构建类型的支持,而无需更改 amment 工具本身。 虽然它目前不并行构建包,但将来会添加该功能以加快构建过程。目标是重用 catkin_tools 中的通用功能,方法是通过两个工具都可以使用的第三个包提供它。

3.2 集成任意构建系统

        每个包都可以利用不同的构建系统来执行配置、构建和安装的步骤。构建类型是在每个包清单中使用导出部分中的 build_type 标记定义的。

        目前支持的是 CMake 和 Python,但将来可能会添加对其他工具(例如 autotools、plain Makefiles)的支持。对于每个构建系统,本机步骤由 amment 工具单独应用。

3.3 构建类型:ament_cmake

        CMake 包 ament_cmake 提供了几个方便的功能,使编写基于 CMake 的包变得更加容易:

        它为包生成一个 CMake 配置文件。这允许将信息(例如关于包含目录和库的信息)传递给下游包。此外,它可以轻松地传递来自递归依赖项的信息(例如,关注包含目录的顺序)。

        它提供了一个简单的接口来注册测试并确保为这些生成与 JUnit 兼容的结果文件。目前它支持一些不同的测试框架,如nosetests、gtest 和gmock。

        它允许包生成环境钩子以扩展环境,例如通过扩展 PATH。

        它提供了一个 CMake API 来读取和写入资源索引条目。该索引是在构建时构建的,并提供对可用包、消息等信息的有效访问。

        为方便起见,它提供了一个卸载目标。

        大多数这些功能都在单独的包中实现。 CMake 代码使用扩展点系统来促进代码的模块化。这使其他人能够添加更多功能,而无需更改 ament_cmake 核心中的现有功能。

3.4 构建类型:cmake

        CMake 构建类型使用普通的 CMake,并为开发人员提供了所有的灵活性和责任感。

3.5 构建类型:ament_python

        Python 构建类型允许包使用仅带有 setup.py 文件的 setuptools。它使用标准 Python 工作流程来构建 Python 包。

        ament 工具会将包清单复制到安装位置。它还将扩展环境变量 PYTHONPATH 和 PATH 以包含包提供的 Python 模块和可执行文件。

四、环境创造

        通常安装空间应遵循文件系统层次标准 (FHS)。但这只是一个建议,并非由 Ament 强制执行。

        根据软件包的安装位置,可能需要设置环境以查找所有资源。例如。安装的可执行文件的位置应该在 PATH 上,安装的 Python 代码应该在 PYTHONPATH 上等。

        因此,每个包都可以提供一个 shell 脚本来设置环境以满足其需求。这些特定于软件包的脚本在文件夹 <prefix>/share/<pkg-name> 中提供。 local_setup.* 文件将更新包指定的环境变量。即使在没有使用 amment 工具的情况下构建包时,也会生成这些设置文件。

        安装空间根目录下的不同shell脚本由amment工具生成。 local_setup.* 文件仅遍历安装空间中的所有包(通过读取 ament 索引中的包列表)并获取其包特定的设置文件。 setup.* 文件还考虑此安装空间之外的工作空间(通过读取 amment 索引中的 parent_prefixp_path 列表)并在 local_setup.* 文件之前获取它们。

4.1 可选的符号链接安装

        最大限度地提高更改代码、构建和安装它然后运行它以确认更改的开发周期的效率是非常重要的。通常,安装步骤包括将一些资源从源空间复制到安装位置的最终目的地。 ament 提供了一个使用符号链接的选项(如果平台支持)。这使开发人员能够更改源空间中的资源并在许多情况下跳过安装步骤。

        对于 CMake 包,这是通过可选地覆盖 CMake install() 函数来实现的。对于 Python 包,开发模式用于安装包。符号链接安装是一项可选功能,必须由开发人员使用命令行选项 --symlink-install 显式启用。

4.2 ament linter

        ament 提供了一组 linter 来检查源代码是否符合 ROS 2 规范指南。这些 linter 的使用是可选的,但是很容易将它们集成为包的自动化测试的一部分。

        Linter 还可以检查与样式无关的标准。例如。 cppcheck 用于静态分析 C/C++ 代码并检查语义错误。

4.3 ament_auto

        ament_auto 类似于 catkin_simple。它旨在简化为 ament 包编写 CMake 代码。它通过从清单中提取依赖项来避免重复依赖项。此外,它通过依赖约定来自动化几个步骤,例如:

        来自依赖项的所有信息都用于编译和链接所有目标如果一个包有一个包含文件夹,它会将该文件夹添加到包含目录中,安装该文件夹中的所有头文件,并将包含文件夹导出到下游包如果包在 msg 和/或 srv 子文件夹中包含接口定义,它们将被自动处理所有库和可执行文件都安装到默认位置有关示例,请参见下文。

4.4 其他资源

        ament 的实现分布在几个存储库中:

  • ament_package 是一个 Python 包,它提供了一个 API 来解析包含元信息的文件。
  • ament_cmake 包含一组提供基于 CMake 的功能的包。这些功能被分成几个包,以确保它们彼此干净地分开。
  • ament_lint 包含一组执行 linting 任务的包。对于一组 linter,提供了 ROS 2 特定配置,可以通过命令行以及在 CMake(通常作为 CTest)中使用。
  • ament_tools 是一个 Python 包,它提供命令行工具来构建、测试、安装和卸载包。
  • ament_index 包含一组提供 API 来访问 ament 资源索引的包。目前它只包含一个 Python 实现,但应该遵循其他语言,如 C++。

五、ment和catkin有什么不同

        从 ROS Groovy 开始,catkin 就被用于 ROS 1。它是作为 rosbuild 的替代品而开发的,rosbuild 从 ROS 开始就被使用。因此,catkin 的各种功能都被设计为类似于 rosbuild。

5.1 为什么不继续使用catkin

        catkin 比 rosbuild 有很多优点。这些包括源外构建、自动 CMake 配置文件生成、安装目标等等。

        然而,随着时间的推移,关于柔荑花序缺点的反馈已经被收集起来。还开发了其他工具(如 catkin_simple 和 catkin_tools)以改善用户/开发人员体验。

        该反馈被用于开发下一个 catkin 迭代,然后称为 ment。下面提到了其中一些案例,所有案例均由 ament 解决:

1)以 CMake 为中心
        catkin 基于 CMake,甚至仅包含 Python 代码的包也通过 CMake 进行处理。因此,catkin 包中的 setup.py 文件只能利用 setuptools 中的一小部分功能。不支持扩展点等常见功能,这使得在 Windows 上部署包变得更加困难。

2)开发空间

        catkin 具有在构建一组包后提供所谓的开发空间的功能。该文件夹提供了一个完全工作的 ROS 环境,而无需安装软件包。避免复制任何文件允许用户例如编辑 Python 代码并立即尝试运行代码。

        虽然这是一个非常方便的功能并加快了开发过程,但它是有代价的。 catkin 中的必要逻辑显着增加了其复杂性。此外,每个 ROS 包中的 CMake 代码必须确保正确处理开发空间,这给每个 ROS 开发人员带来了额外的努力。

3)CMAKE_PREFIX_PATH
        catkin 直接依赖 CMAKE_PREFIX_PATH 环境变量来存储多个工作空间的前缀。这被认为不是一个好方法,因为它会干扰变量中设置的其他值,并且是 CMake 特定的构建变量。因此,ment 使用一个单独的环境变量 (AMENT_PREFIX_PATH) 来实现该目的,该变量在运行时使用。在 CMake 包的构建时,CMake 特定变量可以从通用 Ament 变量派生。

        ament 使用符号链接安装的可选功能提供了相同的优势,而无需为每个 ROS 包增加额外的复杂性。

4)catkin_simple
        ROS 包 catkin_simple 试图使开发 ROS 包的常见案例更容易。虽然它能够降低某些 ROS 包中 CMake 代码的复杂性,但在其他情况下在概念上会失败。一些限制是由于核心 catkin 设计决策,例如调用某些 CMake 函数的顺序和位置等。

        例如。必须在任何目标之前调用函数 catkin_package() 以便为开发空间中的构建目标设置适当的位置。但是为了在代码生成步骤中自动执行多个任务,这个功能需要在所有目标都被定义之后发生。

        通过不同的 ament 设计,可以实现类似于 catkin_simple 的包,它实际上可以在 catkin_simple 失败的所有情况下可靠地工作。

5)在单个 CMake 上下文中构建
        catkin 允许用户在单个 CMake 上下文中构建多个包(使用 catkin_make)。虽然这显着加快了该过程,但它有几个缺点。由于工作空间中的所有包共享相同的 CMake 上下文,所有目标都在相同的命名空间中,因此在所有包中必须是唯一的。这同样适用于全局变量、函数、宏、测试等。此外,一个包可能需要声明与其他包中的目标的目标级别依赖关系,以避免 CMake 目标在它们应该是顺序的时被并行化(但仅在构建于相同的 CMake 上下文)。这需要内部了解其他包的构建结构,破坏了解耦包的目标。由于这些缺点,ment 不提供该功能。

5.2 对catkin的额外改进

        catkin 是一个单一的整体包,提供各种特性和功能。例如。它集成了对 gtest 的支持,这使得选择支持 gmock 变得非常困难,因为一次只能选择一个工具。

        另一方面,ment 的设计非常模块化。几乎每个功能都由单独的包提供,并且可以选择使用。 Ament 的 CMake 部分还具有一个扩展点系统,可以使用更多功能对其进行扩展(见上文)。

5.3 为什么不进化具有必要功能的catkin

        ROS 2 的一个重要目标是不以任何方式破坏 ROS 1。对 catkin 的任何更改都可能将回归引入 ROS 1,这是不希望的。

        此外,一些必要的更改需要在每个 ROS 包中使用不同的 catkin。在未来的发行版中将这样的更改发布到 ROS 1 将意味着每个开发人员都需要付出巨大的努力,这也应该避免。更不用说更新文档和教程以及让用户意识到细微变化的努力了。

5.4为什么要用新名字而不是继续叫它catkin

        对于在 ROS 1 和 ROS 2 之间建立桥梁的用例,必须可以在单个项目中访问这两个代码库。由于两个构建系统具有相同的名称,它们在 CMake 级别上将无法区分(这两个包中的哪一个是通过 find_package(catkin) 找到的?)。因此,两个构建系统的包名必须不同。

        新开发的构建系统具有类似的 CMake 函数,但它们具有部分不同的参数和/或在不同的位置被调用。由于这两个系统被称为相同(甚至非常相似),因此预计会出现严重的用户混淆。考虑将新的构建系统命名为 catkin2,但相似性仍然会导致用户混淆。因此选择了不同的名称来阐明不同的上下文和行为。

5.5 为什么 catkin_pkg 被 fork 到 ament_package

        和 ROS 2 一样,通常只使用 Python 3。 catkin_pkg 以及许多其他 ROS 1 Python 包不能同时为 Python 2 和 Python 3 并排安装。因此,对于 ROS 2 需要 python3-catkin-pkg 会与 ROS 1 的 python-catkin-pkg 发生冲突,并且会使两个 ROS 版本无法同时安装。

        ament_package 仅实现规范 REP 140。它不支持旧格式的 package.xml 文件。

        此外,它确实为所有环境挂钩提供了模板,以便每个包都可以生成自己的环境,而不依赖于外部工具来设置环境。除此之外,该软件包不提供 catkin_pkg 的任何附加功能(无爬行、无拓扑排序等)。

六、三种CMakeList.txt的区别

6.1 catkin版的

cmake_minimum_required(VERSION 2.8.3)

project(foo)


# find dependencies
find_package(catkin REQUIRED COMPONENTS message_generation roscpp std_msgs)
find_package(Boost REQUIRED)


# message generation
add_message_files(
  DIRECTORY msg
  FILES MyMessage.msg
)
generate_messages(
  DEPENDENCIES std_msgs
)

# export information to downstream packages
# must be called *before* the targets to generate them in the desired location in the devel space
catkin_package(
  CATKIN_DEPENDS message_runtime std_msgs
  CONFIG_EXTRAS "foo-extras.cmake"
  DEPENDS Boost
)


# setup targets
include_directories(
  include
  ${catkin_INCLUDE_DIRS}
  ${Boost_INCLUDE_DIRS}
)

add_library(foo_lib src/lib.cpp)
target_link_libraries(foo_lib ${catkin_LIBRARIES})

add_executable(foo_bin src/bin.cpp)
add_dependencies(foo_bin foo_lib)


# install artifacts
install(
  DIRECTORY cmake
  DESTINATION share/${PROJECT_NAME})
install(
  DIRECTORY include/
  DESTINATION include
)
install(
  TARGETS foo_lib foo_bin
  ARCHIVE DESTINATION lib
  LIBRARY DESTINATION lib
  RUNTIME DESTINATION bin
)

# tests
if(CATKIN_ENABLE_TESTING)
  catkin_add_gtest(${PROJECT_NAME}_gtest test/my_test.cpp)
  if(TARGET ${PROJECT_NAME}_gtest)
    target_link_libraries(
      ${PROJECT_NAME}_gtest
      ${catkin_LIBRARIES}
    )
  endif()
endif()

6.2 ament版的

cmake_minimum_required(VERSION 2.8.3)

project(foo)

if(NOT WIN32)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall")
endif()


# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rosidl_default_generators REQUIRED)
find_package(rmw_connext_cpp REQUIRED)
find_package(std_interfaces REQUIRED)


# message generation
rosidl_generate_interfaces(
  ${PROJECT_NAME}_msg-gen
  msg/MyMessage.msg
)


# setup targets
include_directories(
  include
  ${rclcpp_INCLUDE_DIRS}
  ${rmw_connext_cpp_INCLUDE_DIRS}
  ${std_interfaces_INCLUDE_DIRS}
)

add_library(foo_lib src/lib.cpp)
target_link_libraries(foo_lib ${rclcpp_LIBRARIES} ${rmw_connext_cpp_LIBRARIES} ${std_interfaces})

add_executable(foo_bin src/bin.cpp)
add_dependencies(foo_bin foo_lib)



# export information to downstream packages
ament_export_dependencies(
  rclcpp
  std_interfaces
)
ament_export_include_directories(include)
ament_export_libraries(foo_lib)

# must be called *after* the targets to check exported libraries etc.
ament_package(
  CONFIG_EXTRAS "foo-extras.cmake"
)


# install artifacts
install(
  DIRECTORY cmake
  DESTINATION share/${PROJECT_NAME}
)
install(
  DIRECTORY include/
  DESTINATION include
)
install(
  TARGETS foo_lib foo_bin
  ARCHIVE DESTINATION lib
  LIBRARY DESTINATION lib
  RUNTIME DESTINATION bin
)


# tests
if(AMENT_ENABLE_TESTING)
  find_package(ament_cmake_gtest REQUIRED)
  ament_add_gtest(foo_gtest test/my_test.cpp)
  target_link_libraries(foo_gtest ${rclcpp_LIBRARIES} ${rmw_connext_cpp_LIBRARIES} ${std_interfaces})
endif()

6.3 ament_auto版的

cmake_minimum_required(VERSION 2.8.3)

project(foo)

if(NOT WIN32)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall")
endif()


# find dependencies
find_package(ament_cmake_auto REQUIRED)
ament_auto_find_build_dependencies()


# message generation
ament_auto_generate_code()


# setup targets
ament_auto_add_library(foo_lib src/lib.cpp)
ament_auto_add_executable(foo_bin src/bin.cpp)


# export information to downstream packages
ament_auto_package(
  CONFIG_EXTRAS "foo-extras.cmake"
)


# tests
if(AMENT_ENABLE_TESTING)
  find_package(ament_cmake_gtest REQUIRED)
  ament_add_gtest(foo_gtest test/my_test.cpp)
  target_link_libraries(foo_gtest ${rclcpp_LIBRARIES} ${rmw_connext_cpp_LIBRARIES} ${std_interfaces})
endif()

本文含有隐藏内容,请 开通VIP 后查看