ament_cmake 用户文档
ament_cmake
是 ROS 2 中基于 CMake 的软件包的构建系统(尤其是大多数 C/C++ 项目)。它是一组脚本,用于增强 CMake 并为软件包作者添加便利功能。在使用 ament_cmake
了解以下基础知识非常有帮助 CMake.官方教程如下 这里.
基础知识
可以使用 玫瑰2 包 创建 <package_name>;
命令行。然后,构建信息会被收集到两个文件中: package.xml
和 CMakeLists.txt
必须在同一目录下。文件名 package.xml
必须包含所有依赖项和一些元数据,以便 colcon 为您的软件包找到正确的构建顺序,在 CI 中安装所需的依赖项,并为使用 盛开
.......。 CMakeLists.txt
包含构建和打包可执行文件和库的命令,也是本文档的重点。
项目基本大纲
基本概要 CMakeLists.txt
包中包含的内容:
cmake_minimum_required(版本 3.8)
项目(我的项目)
ament_package()
反对 项目
将是软件包名称,且必须与 package.xml
.
项目设置通过以下方式完成 ament_package()
而且每个软件包必须精确调用一次。
ament_package()
安装 package.xml
在 ament 索引中注册该软件包,并为 CMake 安装配置文件(可能还有目标文件),以便其他软件包使用 查找软件包
.因为 ament_package()
从 CMakeLists.txt
中的最后一次调用。 CMakeLists.txt
.
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
由
配置临时员额
编译器和链接器选项
ROS 2 采用符合 C++17 和 C99 标准的编译器。未来可能会有更新的版本,请参考 这里.因此,通常要设置相应的 CMake 标志:
如果(不是 cmake_c_standard)
设置(cmake_c_standard 99)
endif()
如果(不是 cmake_cxx_standard)
设置(cmake_cxx_standard 17)
endif()
为了保持代码的整洁,编译器应该对有问题的代码发出警告,并对这些警告进行修正。
建议至少涵盖以下警告级别:
对于 Visual Studio:默认
W1
警告用于 GCC 和 Clang:
-墙 -Wextra -Wpedantic
强烈推荐-阴影
可取
目前建议使用 添加编译选项
为所有目标添加这些选项。这就避免了所有可执行文件、库和测试中基于目标的编译选项所造成的代码混乱:
如果(cmake_compiler_is_gnucxx 或 cmake_cxx_compiler_id 比赛 "Clang";)
添加编译选项(-墙 -Wextra -Wpedantic)
endif()
查找依赖关系
大多数 ament_cmake
项目会依赖其他软件包。在 CMake 中,可以通过调用 查找软件包
.例如,如果您的软件包依赖于 rclcpp
则 CMakeLists.txt
文件应包含
查找软件包(rclcpp 要求)
备注
永远没有必要 查找软件包
一个库不是明确需要的,但却是另一个明确需要的依赖库的依赖库。如果是这种情况,请针对相应的软件包提交 bug。
添加目标
在 CMake 术语中 目标
是该项目将创建的工件。既可以创建程序库,也可以创建可执行文件,一个项目可以包含零个或多个可执行文件。
通过调用 add_library
,其中应包含目标文件和为创建库而应编译的源文件的名称。
在 C/C++ 中,由于头文件和实现是分离的,因此通常无需将头文件作为参数添加到 add_library
.
建议采用以下最佳做法:
将本程序库客户端应该使用的所有头文件(因此必须安装)放入
包括
文件夹,而所有其他文件 (.c/.cpp
以及不应导出的头文件)都在来源
小册子只有
.c/.cpp
在调用add_library
为您的资料库查找页眉
我的图书馆
经由
目标包含目录(我的图书馆
公众
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>";
"$<INSTALL_INTERFACE:include/${PROJECT_NAME}>";)
这会添加文件夹 ${CMAKE_CURRENT_SOURCE_DIR}/include
的所有文件(相对于......)。 ${cmake_install_dir}
)安装时。
玫瑰2 包 创建
创建一个遵循这些规则的软件包布局。
备注
由于 Windows 是官方支持的平台之一,因此任何软件包都应构建在 Windows 平台上,这样才能发挥最大作用。Windows 库格式强制执行符号可见性;也就是说,每个应从客户端使用的符号都必须由库显式导出(符号需要隐式导入)。
由于 GCC 和 Clang 编译通常不会这样做,因此建议使用 GCC 维基百科.将其用于名为 我的图书馆
:
将链接中的逻辑复制到一个名为
visibility_control.hpp
.更换
DLL
由我的资料库
(的可见性控制为例 rviz_rendering).对所有需要导出的符号(如类或函数)使用宏 "MY_LIBRARY_PUBLIC"。
在该项目中
CMakeLists.txt
使用:目标编译定义(我的图书馆 私人 "MY_LIBRARY_BUILDING_LIBRARY";)
更多详情,请参阅 Windows 提示和技巧文档中的 Windows 符号可见性.
应调用 添加可执行
,其中应包含目标文件和为创建可执行文件而编译的源文件的名称。可执行文件还必须与该软件包中创建的任何库链接,链接时使用 目标链接库
.
由于客户端一般不会将可执行文件作为库使用,因此无需将头文件放入 包括
目录。
如果软件包既有程序库又有可执行文件,请务必将上述 "程序库 "和 "可执行文件 "中的建议结合起来。
链接到依赖项
有两种方法可以将目标与依赖关系联系起来。
第一种也是推荐的方法是使用 ament 宏 ament_target_dependencies
.例如,假设我们要将 我的图书馆
针对线性代数库 Eigen3。
查找软件包(Eigen3 要求)
ament_target_dependencies(我的图书馆 公众 Eigen3)
它包括必要的头文件和库及其依赖项,以便项目能正确找到它们。
第二种方法是使用 目标链接库
.
现代 CMake 更倾向于只使用目标,并根据目标进行导出和链接。CMake 目标可能是命名的,类似于 C++。如果有命名间隔的目标,请优先使用它们。例如 Eigen3
定义了目标 Eigen3::Eigen
.
以 Eigen3 为例,调用过程如下
目标链接库(我的图书馆 公众 Eigen3::Eigen)
这还将包括必要的头文件、库及其依赖项。请注意,该依赖必须是之前通过调用 查找软件包
.
安装
在构建可重复使用的库时,需要导出一些信息,以便下游软件包轻松使用。
首先,安装客户端应该可以使用的头文件。include 目录是自定义的,以支持在 胶管
见 https://colcon.readthedocs.io/en/released/user/overriding-packages.html#install-headers-to-a-unique-include-directory 了解更多信息。
安装(
目录 包括
目的地 包括${项目名称}
)
接下来,安装目标并创建导出目标 (export_${PROJECT_NAME} 出口
),其他代码将使用它来查找此软件包。请注意,您可以使用单个 安装
调用来安装项目中的所有库。
安装(
目标 我的图书馆
出口 出口_${项目名称}
图书馆 目的地 lib
存档 目的地 lib
运行时间 目的地 箱柜
)
输出目标(出口_${项目名称} has_library_target)
ament_export_dependencies(某种依赖)
下面是上面片段中发生的情况:
"(《世界人权宣言》)
输出目标
宏为 CMake 导出目标。这对于允许库的客户端使用target_link_libraries(client 私人 my_library::my_library)
语法如果导出集包含一个库,则添加选项has_library_target
至输出目标
,将潜在的库添加到环境变量中。"(《世界人权宣言》)
ament_export_dependencies
向下游软件包导出依赖关系。这是必要的,这样库的用户就不必调用查找软件包
这些依赖项。
警告
呼叫 输出目标
, ament_export_dependencies
或其他来自 CMake 子目录的命令将无法正常工作。这是因为 CMake 子目录无法在父作用域中设置必要的变量。 ament_package
被称为
备注
Windows 动态链接库被视为运行时工件,并安装到 运行时间 目的地
文件夹。因此,建议将 运行时间
即使在基于 Unix 的系统上开发库时,也可以安装。
"(《世界人权宣言》)
出口
安装调用的符号需要额外注意:它为我的图书馆
目标。它的名称必须与输出目标
.为了确保可以通过ament_target_dependencies
,它的名称不应与库名完全相同,而应有一个前缀,如出口_
(如上图所示)。所有安装路径都相对于
cmake_install_prefix
已由 colcon/ament 正确设置。
还有两个附加功能可用,但对于基于目标的安装来说是多余的:
ament_export_include_directories("include/${PROJECT_NAME}";)
AEMENT_EXTORT_LIBRARY(我的图书馆)
第一个宏标记了导出的 include 目录。第二个宏标记了已安装库的位置(由 has_library_target
调用 输出目标
).只有当下游项目不能或不想使用基于 CMake 目标的依赖关系时,才应使用这些依赖关系。
对于非目标输出,某些宏可以使用不同类型的参数,但由于现代 Make 的推荐方式是使用目标,因此我们在此不做介绍。这些选项的文档可以在源代码中找到。
在安装可执行文件时,请使用以下字符串 必须完全遵循 让 ROS 的其他工具找到它:
安装(目标 my_exe
目的地 lib/${项目名称})
如果软件包既有程序库又有可执行文件,请务必将上述 "程序库 "和 "可执行文件 "中的建议结合起来。
着色和测试
为了将测试与使用 colcon 构建库分开,请将所有对衬垫和测试的调用封装在一个条件中:
如果(构建测试)
查找软件包(ament_cmake_gtest 要求)
添加测试(测试)
endif()
林亭
建议使用来自 自动:
查找软件包(自动 要求)
ament_lint_auto_find_test_dependencies()
将运行 package.xml
.建议使用由软件包 ament_lint_common
.其中包含的单个衬纸及其功能可参见 ament_lint_common docs.
也可以单独添加由 ament 提供的流化床,而不是运行 自动
.其中一个例子是 ament_cmake_lint_cmake 文档.
测试
Ament 包含 CMake 宏,可简化 GTests 的设置。调用:
查找软件包(ament_cmake_gtest)
添加测试(some_test <test_sources>;)
添加一个 GTest。这样,它就成为一个常规目标,可以与其他库(如项目库)进行链接。宏有附加参数:
APPEND_ENV
:附加环境变量。例如,你可以通过调用
查找软件包(ament_cmake_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_current_binary_dir
在 CMake 文档.
同样,有一个 CMake 宏可以设置 GTest(包括 GMock):
查找软件包(ament_cmake_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.cmake
和register_rviz_ogre_media_exports_hook-extra.cmake
登记为CONFIG_EXTRA
与ament_package()
.