警告

您正在阅读的 ROS 2 文档版本已达到 EOL(生命周期结束),不再受官方支持。如果您想了解最新信息,请访问 Jazzy.

ament_cmake 用户文档

ament_cmake 是 ROS 2 中基于 CMake 的软件包的构建系统(特别是,它将用于大多数(如果不是全部的话)C/C++ 项目)。它是一组脚本,用于增强 CMake 并为软件包作者添加便利功能。了解 CMake 会很有帮助,官方教程如下 这里.

基础知识

可以使用 玫瑰2 创建 <package_name>; 命令行。然后,基本的构建信息会被收集到两个文件中,即 package.xmlCMakeLists.txt.......。 package.xml 必须包含所有依赖项和一些元数据,以便 colcon 为您的软件包找到正确的构建顺序,在 CI 中安装所需的依赖项,并为使用 盛开.......。 CMakeLists.txt 包含构建和打包可执行文件和库的命令,也是本文档的重点。

项目基本大纲

基本概要 CMakeLists.txt 包中包含的内容:

cmake_minimum_required(版本 3.5)
项目(我的项目)

ament_package()

反对 项目 将是软件包名称,且必须与 package.xml.

项目设置通过以下方式完成 ament_package() 而且每个软件包必须精确调用一次。 ament_package() 安装 package.xml在 ament 索引中注册该软件包,并为 CMake 安装配置文件(可能还有目标文件),以便其他软件包使用 查找软件包.因为 ament_package()CMakeLists.txt 中的最后一次调用。 CMakeLists.txt.虽然可以通过呼叫 ament_package() 通过调用 安装 在复制文件和目录时,只需保留 ament_package() 最后一次通话。

ament_package 可以有额外的参数:

  • CONFIG_EXTRAS:CMake 文件列表 (.cmake.cmake.in 扩展的模板 configure_file()),这些参数应该提供给软件包的客户端。有关何时使用这些参数的示例,请参阅 添加资源.有关如何使用模板文件的更多信息,请参阅 正式文件.

  • 配置临时员额同上 CONFIG_EXTRAS但添加文件的顺序有所不同。虽然 CONFIG_EXTRAS 文件包含在为 ament_export_* 中调用文件 配置临时员额 之后也包括在内。

而不是增加 ament_package还可以在变量 ${project_name}_config_extras${project_name}_config_extras_post 效果相同。唯一的区别还是添加文件的顺序,总顺序如下:

  • CONFIG_EXTRAS

  • 附加到 ${project_name}_config_extras

  • 附加到 ${project_name}_config_extras_post

  • 配置临时员额

添加文件和页眉

要构建的目标主要有两个:库和可执行文件,后者由 add_library添加可执行 分别是

在 C/C++ 中,由于头文件和实现是分离的,因此并不总是有必要将这两个文件作为参数添加到 add_library/ 添加可执行.

建议采用以下最佳做法:

  • 如果您正在构建一个库,则应将客户机可以使用的、因此必须安装的所有头文件放到 包括 文件夹,而所有其他文件 (.c/.cpp 以及不应导出的头文件)都在 来源 文件夹。

  • 的调用中只明确引用 cpp 文件。 add_library添加可执行

  • 允许通过

目标包含目录(我的目标
  公众
    $<;BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>;
    $<;INSTALL_INTERFACE:include>;)

这会添加文件夹 ${CMAKE_CURRENT_SOURCE_DIR}/include 的所有文件(相对于......)。 ${cmake_install_dir})安装时。

原则上,如果两个文件夹都被称为 包括 的最高级别 ${cmake_current_source_dir}${cmake_install_dir}但这种情况非常普遍。

添加依赖项

有两种方法可以将软件包链接到新的依赖关系。

第一种也是推荐的方法是使用 ament 宏 ament_target_dependencies.例如,假设我们要将 我的目标 针对线性代数库 Eigen3。

查找软件包(Eigen3 要求)
ament_target_dependencies(我的目标 Eigen3)

它包括必要的头文件和库及其依赖项,以便项目能正确找到它们。它还能确保在使用覆盖工作区时,所有依赖项的包含目录都能正确排序。

第二种方法是使用 目标链接库.

现代 CMake 推荐的方法是只使用目标,并根据目标进行导出和链接。CMake 目标是以名字命名的,类似于 C++。例如 Eigen3 定义了目标 Eigen3::Eigen.

至少到 水晶 克莱米斯 中不支持目标名称。 ament_target_dependencies 宏观。有时需要调用 目标链接库 CMake 函数。在 Eigen3 的例子中,调用应如下所示

查找软件包(Eigen3 要求)
目标链接库(我的目标 Eigen3::Eigen)

这也将包括必要的头文件、库及其依赖项,但与 ament_target_dependencies 在使用覆盖工作区时,可能无法正确排列依赖关系。

备注

永远没有必要 查找软件包 一个库不是明确需要的,但却是另一个明确需要的依赖库的依赖库。如果是这种情况,请针对相应的软件包提交 bug。

建立图书馆

在构建可重复使用的库时,需要导出一些信息,以便下游软件包轻松使用。

输出接口(导出我的图书馆 has_library_target)
ament_export_dependencies(某种依赖)

安装(
  目录 包括
  目的地 包括
)

安装(
  目标 我的图书馆
  出口 导出我的图书馆
  图书馆 目的地 lib
  存档 目的地 lib
  运行时间 目的地 箱柜
  包括 目的地 包括
)

在此,我们假设文件夹 包括 包含需要导出的标头。请注意,不必将所有标头都放入单独的文件夹,只需将客户端应包含的标头放入文件夹即可。

下面是上面片段中发生的情况:

  • "(《世界人权宣言》) 输出接口 宏为 CMake 导出目标。这对于允许库的客户端使用 target_link_libraries(client my_library::my_library) 句法 输出目标 可以接受一个任意的目标列表,命名为 出口 在安装调用和附加选项中 has_library_target,将潜在的库添加到环境变量中。

  • "(《世界人权宣言》) ament_export_dependencies 向下游软件包导出依赖关系。这是必要的,这样库的用户就不必调用 查找软件包 这些依赖项。

  • 第一个 安装 命令安装客户端应该可以使用的头文件。

警告

呼叫 输出目标, ament_export_dependencies或其他来自 CMake 子目录的命令将无法正常工作。这是因为 CMake 子目录无法在父作用域中设置必要的变量。 ament_package 被称为

  • 最后一条大型安装命令将安装程序库。存档和库文件将导出到 lib 文件夹,运行时二进制文件将安装到 bin 文件夹,安装头文件的路径是 包括.

备注

Windows dll 会被视为运行时工件,并安装到 运行时间 目的地 文件夹。因此,建议不要遗漏 运行时间 即使在基于 Unix 的系统上开发库时,也可以安装。

  • 关于 包括 通讯录安装命令只是向 CMake 添加信息,并不实际安装包含文件夹。这需要通过 安装(DIRECTORY <dir>; 目的地 dest>) 如上所述。

  • "(《世界人权宣言》) 出口 安装调用的符号需要额外注意:它为 我的图书馆 目标。它的命名与 输出目标 的名称。不过,这将禁止使用 ament_target_dependencies 的方式包含您的资料库。为了实现充分的灵活性,建议在导出目标前添加类似以下的内容 export_<target>;.

  • 所有安装路径都相对于 cmake_install_prefix已被 colcon/ament 正确设置。

还有两个附加功能可以使用,但对于基于目标的安装来说是多余的:

ament_export_include_directories(包括)
AEMENT_EXTORT_LIBRARY(我的图书馆)

第一个宏标记了导出包含目录的目录(通过 包括 目的地 在目标 安装 调用)。第二个宏标记了已安装库的位置(由 has_library_target 调用 输出目标).

对于非目标输出,某些宏可以使用不同类型的参数,但由于现代 Make 的推荐方式是使用目标,因此我们在此不做介绍。这些选项的文档可以在源代码中找到。

编译器和链接器选项

ROS 2 的目标编译器至少要符合 C++14 和 C99 标准,直到 水晶 克莱米斯.更新的版本可能是未来的目标,并参考了 这里.因此,通常要设置相应的 CMake 标志:

如果(不是 cmake_c_standard)
  设置(cmake_c_standard 99)
endif()
如果(不是 cmake_cxx_standard)
  设置(cmake_cxx_standard 14)
endif()

为了保持代码的整洁,编译器应该对有问题的代码发出警告,并对这些警告进行修正。

建议至少涵盖以下警告级别:

  • 对于 Visual Studio,默认 W1 保留警告

  • 用于 GCC 和 Clang: -墙 -Wextra -Wpedantic 需要和 -阴影 -错误 是可取的(后者会产生警告错误)。

虽然现代 CMake 建议在目标基础上添加编译器标志,即调用

目标编译选项(我的目标 私人 -墙)

目前建议使用目录级功能 add_compile_options(-Wall) 以避免在所有可执行文件和测试中使用基于目标的编译选项造成代码混乱。

在 Windows 上构建程序库

由于 Linux、Mac 和 Windows 都是官方支持的平台,因此任何软件包都应在 Windows 上构建,以产生最大影响。Windows 库格式强制执行符号可见性:客户端使用的每个符号都必须由库明确导出(数据符号则需要隐式导入)。

为了与 Clang 和 GCC 编译兼容,建议使用 GCC 维基百科.将其用于名为 我的图书馆:

  • 将链接中的逻辑复制到一个名为 visibility_control.hpp.

  • 更换 DLL我的资料库 (的可见性控制为例 rviz_rendering).

  • 对所有需要导出的符号(如类或函数)使用宏 "MY_LIBRARY_PUBLIC"。

  • 在该项目中 CMakeLists.txt 使用:

目标编译定义(我的图书馆 私人 "MY_LIBRARY_BUILDING_LIBRARY";)

测试和制绒

为了将测试与使用 colcon 构建库分开,请将所有对衬垫和测试的调用封装在一个条件中:

如果(构建测试)
  查找软件包(ament_gtest)
  添加测试(测试)
endif()

林亭

建议使用来自 自动:

查找软件包(自动 要求)
ament_lint_auto_find_test_dependencies()

将运行 package.xml.建议使用由软件包 ament_lint_common.

也可以单独添加由 ament 提供的流化床,而不是运行 自动.其中一个例子是 ament_cmake_lint_cmake 文档.

测试

Ament 包含 CMake 宏,可简化 GTests 的设置。调用:

查找软件包(ament_gtest)
添加测试(some_test <test_sources>;)

添加一个 GTest。这样,它就成为一个常规目标,可以与其他库(如项目库)进行链接。宏有附加参数:

  • APPEND_ENV:附加环境变量。例如,你可以通过调用

查找软件包(ament_gtest 要求)
添加测试(some_test <test_sources>;
  APPEND_ENV PATH=some/addtional/path/for/testing/resources)
  • 添加库文件在运行时,链接器可以找到这些库。这可以通过设置环境变量来实现,如 路径 在 Windows 和 ld_library_path 但这使得调用具有平台特性。

  • 环境:设置环境变量(语法与 APPEND_ENV).

  • 超时:设置测试超时(秒)。GTests 的默认值为 60 秒。例如

添加测试(some_test <test_sources>; 超时 120)
  • SKIP_TEST:跳过此测试(在控制台输出中将显示为 "通过")。

  • 跳过链接主库:不要链接 GTest。

  • 工作目录:设置测试的工作目录。

否则,默认工作目录是 cmake_source_dir的目录。 CMakeLists.txt.

同样,有一个 CMake 宏可以设置 GTest(包括 GMock):

查找软件包(ament_gmock 要求)
ament_add_gmock(some_test <test_sources>;)

它的附加参数与 添加测试.

扩展活动

可以使用 ament_cmake 并以多种方式加以扩展。

在内容中添加函数/宏

扩展 ament 通常意味着您希望将某些功能提供给其他软件包。向客户端软件包提供宏的最佳方法是向 ament 注册。

可以通过添加 ${project_name}_config_extras 变量,该变量被 ament_package() 经由

清单(附录 ${项目名称}_CONFIG_EXTRAS
  path/to/file.cmake";
  other/pathto/file.cmake";
)

或者,您也可以直接将文件添加到 ament_package() 打电话:

ament_package(CONFIG_EXTRAS
  path/to/file.cmake
  other/pathto/file.cmake
)

添加到扩展点

除了包含可用于其他软件包的函数的简单文件外,您还可以为 ament 添加扩展。这些扩展是与定义扩展点的函数一起执行的脚本。ament 扩展最常见的用途可能是注册 rosidl 消息生成器:在编写生成器时,通常希望在不修改消息/服务定义包代码的情况下,也能用生成器生成所有消息和服务。这可以通过将生成器注册为 生成接口.

例如

ament_register_extension(
  "rosidl_generate_interfaces";
  "rosidl_generator_cpp";
  "rosidl_generator_cpp_generate_interfaces.cmake";)

注册宏 rosidl_generator_cpp_generate_interfaces.cmake 为软件包 rosidl_generator_cpp 到延伸点 生成接口.当扩展点被执行时,将触发脚本的执行 rosidl_generator_cpp_generate_interfaces.cmake 这里。特别是,每当函数 生成接口 被执行。

最重要的扩展点除了 生成接口 发电机为 ament_package的脚本,它将简单地执行带有 ament_package() 调用。该扩展点在注册资源时非常有用(见下文)。

ament_register_extension 是一个包含三个参数的函数:

  • 扩展点:扩展点的名称(大多数情况下,它将是 ament_package生成接口)

  • 包名:包含 CMake 文件的软件包名称(即文件被写入的项目名称)

  • cmake_filename:运行扩展点时执行的 CMake 文件

备注

可以用类似于 ament_package生成接口但这几乎没有必要。

添加扩展点

在极少数情况下,为 ament 定义一个新的扩展点可能会很有趣。

可以在宏中注册扩展点,以便在调用相应宏时执行所有扩展。要做到这一点

  • 定义并记录扩展名(例如 我的扩展点),它是传递给 ament_register_extension 宏。

  • 在执行扩展调用的宏/函数中:

ament_execute_extensions(我的扩展点)

Ament 扩展的工作方式是定义一个包含扩展点名称的变量,并在其中填入要执行的宏。调用 ament_execute_extensions然后,变量中定义的脚本将被逐个执行。

添加资源

特别是在开发插件或允许使用插件的软件包时,经常需要将资源从另一个 ROS 软件包(如插件)添加到另一个软件包中。使用插件库的工具插件就是一个例子。

这可以通过调整指数(也称为 "资源指数")来实现。

解释了补偿指数

有关设计和意图的详细信息,请参见 这里

原则上,ment 索引包含在软件包 install/share 文件夹内的一个文件夹中。它包含以不同类型资源命名的浅子文件夹。在子文件夹中,每个提供上述资源的软件包都有一个 "标记文件"。该文件可包含获取资源所需的任何内容,例如资源安装目录的相对路径,也可以是空文件。

举个例子,考虑为 RViz 提供显示插件:在一个名为 my_rviz_displays 将被插件库读取。 plugin_description.xml 文件,pluginlib 将安装并使用该文件加载插件。为此,plugin_description.xml 将作为资源注册到 resource_index 中。

pluginlib_export_plugin_description_file(rviz_common plugins_description.xml)

运行时 胶管 构建,这会安装一个文件 my_rviz_displays 子文件夹 rviz_common__pluginlib__plugin 到 resource_index 中。rviz_common 中的 Pluginlib 工厂将知道从所有名为 rviz_common__pluginlib__plugin 用于导出插件的软件包。pluginlib 工厂的标记文件包含一个安装文件夹的相对路径,该路径指向 plugins_description.xml 文件(以及作为标记文件名的程序库名称)。有了这些信息,pluginlib 就能加载程序库,并知道要从 plugin_description.xml 锉刀

作为第二个例子,请考虑让您自己的 RViz 插件使用自定义网格的可能性。网格会在启动时加载,因此插件所有者无需处理,但这意味着 RViz 必须知道网格。为此,RViz 提供了一个函数:

register_rviz_ogre_media_exports(目录 <my_dirs>;)

这会将目录注册为 ament 索引中的 ogre_media 资源。简而言之,它会将一个以调用该函数的项目命名的文件安装到一个名为 rviz_ogre_media_exports.该文件包含宏中所列目录的安装文件夹相对路径。启动时,RViz 现在可以搜索所有名为 rviz_ogre_media_exports 并加载所提供的所有文件夹中的资源。这些搜索使用 ament_index_cpp (或 ament_index_py 用于 Python 软件包)。

在下面的章节中,我们将探讨如何将自己的资源添加到 ament 索引中,并提供相关的最佳实践。

查询ment索引

如有必要,可通过 CMake 查询资源的 ament 索引。为此,有三种功能:

ament_index_has_resource获取资源的前缀路径(如果存在),参数如下:

  • 变异:输出参数:如果资源不存在,则在该变量中填入 FALSE,否则填入资源的前缀路径

  • 资源类型:资源类型(例如 rviz_common__pluginlib__plugin)

  • 资源名称:资源的名称,通常相当于添加了 resource_type 类型资源的软件包的名称(例如:"......")。 rviz_default_plugins)

ament_index_get_resource:获取特定资源的内容,即索引中标记文件的内容。

  • 变异输出参数:如果存在资源标记文件,则用该文件的内容填充。

  • 资源类型:资源类型(例如 rviz_common__pluginlib__plugin)

  • 资源名称:资源的名称,通常相当于添加了 resource_type 类型资源的软件包的名称(例如:"......")。 rviz_default_plugins)

  • PREFIX_PATH:要搜索的前缀路径(通常默认为 ament_index_get_prefix_path() 就足够了)。

请注意 ament_index_get_resource 如果资源不存在,就会出错,因此可能需要使用 ament_index_has_resource.

ament_index_get_resources:从索引中获取注册了特定类型资源的所有软件包

  • 变异:输出参数:填写所有注册了 resource_type 资源的软件包名称列表

  • 资源类型:资源类型(例如 rviz_common__pluginlib__plugin)

  • PREFIX_PATH:要搜索的前缀路径(通常默认为 ament_index_get_prefix_path() 就足够了)。

增加娱乐指数

定义一个资源需要两个信息位:

  • 资源的名称,必须是唯一的、

  • 标记文件的布局,可以是任何内容,也可以是空(例如,标记 ROS 2 软件包的 "软件包 "资源就是如此)。

对于 RViz 网格资源,相应的选择有

  • rviz_ogre_media_exports 作为资源名称、

  • install path 包含资源的所有文件夹的相对路径。这样,您就可以在软件包中编写使用相应资源的逻辑。

为方便用户为软件包注册资源,您应进一步提供宏或函数,如 pluginlib 函数或 rviz_ogre_media_exports 功能。

要注册资源,请使用 ament 功能 ament_index_register_resource.这将创建并安装 resource_index 中的标记文件。例如,调用 rviz_ogre_media_exports 具体如下

ament_index_register_resource(rviz_ogre_media_exports 内容 ${媒体资源文件})

这会安装一个名为 ${project_name} 项目名称 文件夹中 rviz_ogre_media_exports 到 resource_index 中,其内容由变量 ${ogre_media_resource_file}(媒体资源文件.该宏有许多有用的参数:

  • 第一个(未命名)参数是资源名称,相当于 resource_index 中的文件夹名称

  • 内容:标记文件的内容字符串。可以是相对路径列表等。 内容 不能与 内容文件.

  • 内容文件:用于创建标记文件的文件路径。该文件可以是普通文件,也可以是用 configure_file(). 内容文件 不能与 内容.

  • 软件包名称:导出资源的软件包/库的名称,相当于标记文件的名称。默认为 ${project_name} 项目名称.

  • ament_index_binary_dir:生成索引的基本路径。除非确有必要,否则始终使用默认的 ${CMAKE_BINARY_DIR}/ament_cmake_index.

  • SKIP_INSTALL:跳过安装标记文件。

由于每个软件包只存在一个标记文件,如果 CMake 函数/宏被同一项目调用两次,通常不会有问题。不过,对于大型项目来说,最好还是将注册资源的调用拆分开来。

因此,最佳做法是让宏注册一个资源,如 register_rviz_ogre_media_exports.cmake 只填充一些变量。真正调用 ament_index_register_resource 然后可以在扩展名中添加 ament_package.由于只能调用一次 ament_package 在每个项目中,只有一个地方可以注册资源。在 rviz_ogre_media_exports 这相当于以下战略:

  • 宏观 register_rviz_ogre_media_exports 将文件夹列表追加到一个名为 媒体资源文件.

  • 另一个宏名为 register_rviz_ogre_media_exports_hook 电话 ament_index_register_resource 如果 ${ogre_media_resource_file}(媒体资源文件 是非空的。

  • "(《世界人权宣言》) register_rviz_ogre_media_exports_hook.cmake 文件在第三个文件中注册为扩展名 register_rviz_ogre_media_exports_hook-extras.cmake 通过呼叫

ament_register_extension("ament_package"; "rviz_rendering";
  "register_rviz_ogre_media_exports_hook.cmake";)
  • 文件 register_rviz_ogre_media_exports.cmakeregister_rviz_ogre_media_exports_hook-extra.cmake 登记为 CONFIG_EXTRAament_package().