概述
Open Robotics为多个平台提供了预构建的ROS 2包,但许多开发人员仍需要依赖交叉编译1,原因如下:
- 开发机与目标系统不一致。
- 为特定的核心架构调优构建(例如,在为Raspberry Pi3构建时设置-mcpu=cortex-a53 -mfpu=neon-fp-armv8)。
- 希望运行于不同于Open Robotics发布的预构建镜像所支持的文件系统上的应用。
它是如何工作的?
交叉编译简单软件(例如,不依赖外部库的软件),只需要使用交叉编译器工具链替代本地工具链即可。
交叉编译的复杂性往往由以下因素带来:
- 构建的软件系统是基于目标架构的。在构建过程中,必须根据目标体系结构正确地隔离和启用依赖特定体系结构的代码。 典型的例子是汇编代码。
- 所有依赖项(通常是各种库)必须存在,通常以以下两种方式予以支持:
- 作为预构建包
- 使依赖项的库在目标软件之前被交叉编译
- 当使用构建工具(如colcon)构建software stacks(相对于独立软件的概念)时,构建工具应该提供一种机制,以保证开发者在stacks中的每个软件构建时使用的相同的底层构建系统进行交叉编译。
交叉编译ROS 2
ROS 2交叉编译工具由Open Robotics和ROS Tooling Working Group所共有。
它是一个Python脚本,使用docker容器中的模拟器为受支持的目标架构编译ROS 2源文件。
该工具的详细设计可在ROS 2 design2中找到。 使用该工具的说明在cross_compile包中。
如果您使用的是旧版本,请遵循交叉编译指南。
ROS 2包的交叉编译工具设计
接下来我们看一下设置和管理用于交叉编译的sysroot环境的ROS 2工具的方案。
本方案的目标是简单和可扩展。
我们可以使用colcon mixins扩展支持新的交叉编译配置。
背景
ROS的交叉编译指南和esteve/ros2_raspbian_tools库中说明了如何使用CMake和Docker镜像的工具链将ROS 2交叉编译未受官方支持的架构,如ARM64和ARM-HF。
esteve/ros2_objc和esteve/ros2_java也提供对Android和iOS等其他平台的支持。
理想情况下,我们应该能够通过单个命令来交叉编译用于ROS 2的ROS包。该命令还应该具备良好的可扩展性以便于支持新平台。该设计方案是对ARM-HF和ARM64架构的一级支持的推动。
设计思路
我们将继续使用交叉编译教程的一般方法。这涉及到为目标平台构建一个sysroot,使用QEMU和Docker,然后在colcon mixins中使用C和C++交叉编译器进行编译。
我们将在交叉编译教程中添加一组命令来进行二次封装。这些命令将为目标平台使用一个新的workspace目录,并基于以下关键字确定:
- 目标平台
- 操作系统
- RMW实现
- ROS发行版
我们称此工作区目录称为cc-root。平台意味着体系结构,但是像generic_armhf
这样的通用平台也属于此语境范畴。平台标识符的例子如:generic_armhf-ubuntu_bionic-fastrtps-crystal
或turtlebot_armhf-ubuntu_bionic-fastrtps-dashing
。我们将添加以下命令:
- 一个新的
create-cc-sysroot
命令,将使用Docker为目标平台生成sysroot,并将其存储在cc-root的sysroot子目录中。这个命令将接受参数来指定目标平台,例如:
create-cc-sysroot --arch generic_armhf --os ubuntu_bionic \
--rmw fastrtps --rosdistro crystal
- 每个已知平台的一个新的colcon mixin,它向colcon构建任务中添加了使用由
create-cc-sysroot
生成的sysroot的选项,使用相同的路径约定。
例如,在开发者工作站上的ROS 2覆盖workspace中执行以下命令,可以将交叉编译工作区中的包编译至performance_test
包。这将在cc-rootgeneric_armhf-ubuntu_bionic-fastrtps-crystal
的build
、install
和log
子目录下交叉编译生成相应的二进制文件。
colcon build --mixin cc-generic_armhf-ubuntu_bionic-fastrtps-crystal \
--packages-up-to performance_test # any other other `colcon build` arguments
- 此外:
- 维护一个用于交叉编译的docker镜像,它同时安装了交叉编译工具链和前面提到的colcon mixins。
- 实现一个新的
cc-build
命令,确保使用create-cc-sysroot
创建了sysroot,然后在Docker容器中启动交叉编译相应的Docker镜像。
create-cc-sysroot
命令将暗自执行以下操作:
- 下载目标平台的基本ROS 2 Docker镜像。
- 通过使用一个从基础镜像开始的Dockerfile来构建一个依赖于工作区的sysroot镜像,并且:
- 运行
COPY
以将工作区的内容获取到容器中。 - 使用
rosdep
确保workspace的系统依赖项没有丢失。注意,由于使用了QEMU,我们可以在Docker中为ARM-HF架构运行rosdep
。
- 运行
- 启动该镜像的容器,并将其文件系统导出到cc-root的
sysroot
子目录中。 - 在构建过程中,colcon mixin使用
CMAKE_TOOLCHAIN_FILE
参数指向工具链文件(例如generic_linux.cmake),从而为交叉编译设置适当的CMake参数。
ROS 2基本镜像是使用Docker官方镜像的ros2/cross_compile中的sysroot/Dockerfile_ubuntu_arm的变体。
OSRF将发布带有预构建ROS 2的基本ROS 2镜像,这用于交叉编译用户包。
OSRF还将发布其他基本ROS 2镜像,并预装用于构建ROS 2的系统设置和基本工具,这些工具应该用于从源构建ROS 2。
create-cc-sysroot
命令也支持以下可选参数:
--sysroot-base-image
:指定一个Docker镜像作为依赖于工作空间的sysroot镜像的基础镜像。所指定的参数可作为docker image pull
的有效参数,并在FROM
Dockerfile 语句中使用。如果没有指定,该命令将从目标平台推断--sysroot-base-image
的值,并从Docker的官方ROS镜像、arm64v8 Dockerhub repo或arm32v7 Dockerhub repo上发布的镜像中进行拉取。--sysroot-image
:指定工作空间的sysroot的Docker镜像。构建将从该镜像中导出sysroot,而不是从工作区创建sysroot镜像。开发者需自行保证workspace的包所需的所有依赖项都被包含在镜像中。--use-base-image-sysroot
: Boolean标志,当为true时,将使用base ROS 2 Docker镜像构建sysroot,此时工作区包中声明的依赖项将被忽略。在没有额外依赖的情况下,该选项可以加速包的构建。
通过创建相应的ROS 2 Docker镜像以及工具链文件的变体,我们可以添加对其他平台的支持。我们在Ubuntu Bionic的ROS 2 crystal和fastrtps版本上,首次添加了对ARM-HF和ARM64的通用版本的支持。
常见问题
- 我们如何运行生成的二进制文件?
打包生成的二进制文件不在本文档的描述范围内。
满足以下条件后,交叉编译的二进制文件将在cc-root的工作区中可用(如generic_armhf-ubuntu_bionic-fastrtps-crystal
):
- 将包含
src
在内的整个工作区复制到远程设备 - 在目标平台上运行
rosdep
以在运行二进制文件前安装ROS相关依赖项。
维护和测试
为了支持新平台的交叉编译,我们需要:
- 为新平台准备并发布一个基本的ROS 2 Docker镜像,使用
rosdep install
获取ROS 2源代码所需的依赖项。如果该平台存在预构建的二进制文件,我们也可以从二进制文件中获取依赖项。 - 为新平台调整工具链文件。ruslo/polly库中提供了大量的交叉编译可用的CMake工具链文件和脚本。
- 向
cross-compile
库添加新平台的colcon mixin,并根据工具链文件进行相应的修改。
我们可以通过如下方法测试新平台的交叉编译支持情况:
- 构建基本ROS 2 Docker镜像。
- 使用
create-cc-sysroot
为一些参考包(例如ApexAI/performance_test)构建一个自定义的sysroot映像,并将其文件系统导出至sysroot中。 - 使用该sysroot交叉编译该包。
- 为sysroot镜像启动一个Docker容器,并使用交叉编译的二进制文件运行
colcon test
。该容器将与工作区的bind mount一起启动,这样它们就可以访问已编译的二进制文件。
为了简化针对新架构-操作系统组合的基础ROS 2 Docker镜像的开发,在未来的迭代中,我们可能会添加一个新的命令build-sysroot-base-image
,功能如下:
- 将所需的QEMU文件和ROS 2源文件复制到临时位置。
- 从指定的Dockerfile构建一个Docker镜像,并访问上述资源。
- 【可选】将镜像发布到Docker注册表中,并命名为与平台标识符相匹配的名称。
与该命令兼容的Dockerfiles将保留在ros2/cross_compile
中。如果需要,我们可以在CI/CD管道中使用它们。
该方案的优点
本方案带来了简便的开发体验,其中:
- 交叉编译由两个简单命令触发
- 在二次迭代编译中,仅需由一个
cc-build
命令触发
此外,本方案具有可扩展性,可以轻松支持新的平台。
对于带有附加依赖项的简单包,开发者仅需使用现成的基础Docker镜像,而无需使用Docker在本地构建sysroot。对于具有自定义依赖项的包,可以在通过binfmt_misc
方式使用QEMU的Docker映像上执行rosdep install
的方式来扩展基本的sysroot应用。
局限性和开放性问题
这个方案主要解决的是基于Linux平台的交叉编译。由于我们无法使用Apt安装Visual Studio编译器,因此我们也无法在Windows上进行交叉编译。
所以,在Windows或Mac开发环境上,我们只能通过在容器的方式运行本工具。当然,受限于虚拟化层,性能多少会有所下降。
我们应该为每个平台确定一个构建和发布基本ROS 2 Docker映像的过程。 理想情况下,为master发布从源代码构建的镜像应该是无感的,但根据可用资源的不同,这可能很难实现的。
其他方法
可以使用colcon bundle插件将ROS工作区及其所有依赖打包成一个tarball。通过启动目标平台的Docker容器,构建并打包工作空间,并导出bundle,可以将其部署到目标硬件,从而交叉编译ROS 2的工作空间。 这种方法的主要缺点是在Docker映像中使用默认的ARM编译器会显著降低编译速度,这可能会给日常开发带来负担。通过安装qemu-user-static
,我们安装了QEMU二进制文件来模拟ARM32/64处理器,而且还在本地机器中注册了binfmt_misc
的钩子。这意味着每次程序启动时,如果是它希望运行在QEMU支持的平台上,则二进制文件将自动通过QEMU运行。实际上,它允许您在X86实例上直接运行ARM二进制文件,这个工作是在构建基本镜像时通过Docker隐式完成的。由于QEMU模拟器比正常的执行慢10倍以上,通过此方案我们将QEMU的使用限制在构建基本镜像阶段,在其余时间在本机上使用用于目标平台的交叉编译器。
colcon bundle方法需要在colcon bundle中添加对ROS 2的支持,而后者目前不支持Ament。 这将提供一种分发交叉编译的系统依赖项的方法,由于rosdep install
在资源受限的硬件上会花费很多时间,并且需要Internet连接,因此本方法提供了极大的便利性。 目前的方案和colcon bundle是互补方案,若colcon bundle最终支持ROS 2,我们就可以使用cc-build
轻松地完成交叉编译,最小化Docker的使用,然后使用colcon bundle
来生成可部署的工件。
对于依赖于工作空间的sysroot映像,我们可以通过不构建工作空间的sysroot映像来避免该工作空间的Docker COPY
,我们只需为基础ROS 2镜像启动一个容器,并bind mount该工作空间。 容器的入口点将在挂载工作空间的目录上启动rosdep
,然后我们就可以通过docker container export
导出容器的文件系统。这避免了COPY
,但缺点是我们的工作空间就不会有sysroot映像,而且每次启动容器时都必须在该镜像的入口点上运行rosdep
。该镜像对于运行测试非常有用,而最终的cc-test
命令可以进一步简化使用该镜像的操作。
最后,与交叉编译教程中的原始方案相比,本方案基本上在cross_compile/entry_point.sh中对上述教程所使用的命令进行了重新组织,这样就可以用简单的一行命令对工作区进行交叉编译工作区。 因此,本方案是上述教程的演进。