When you develop ROS software on your desktop, installing a new package can be as easy as typing apt install ros-jazzy-my-package, provided that ros-jazzy-my-package is contained in your ROS distro. If it’s not, you can usually git clone the package’s repository into your colcon workspace and build it via colcon build. Deploying custom ROS packages to an embedded Linux image requires a different approach:
Embedded Linux images built with Yocto Project are cross-compiled from scratch. Every package that ends up in the final image must be described by a BitBake recipe. For packages that are part of the official ROS distribution, a tool called superflore auto-generates these recipes. For your own custom package you have to write the recipe yourself.
The Pika Spark micro robot control system contains a pika-spark-ros-jazzy-image target. It leverages meta-ros for integrating ROS 2 Jazzy into the Pika Spark image. This article goes over every line of a BitBake recipe for l3xz_joy, a simple PS3 joystick driver node. Every BitBake recipe entry is mapped back to the package’s package.xml file. By the end you will know exactly how to write an equivalent recipe for your own ROS 2 package.
Note: Pika Spark’s product documentation contains detailed information on how to build and flash images for Pika Spark.
Here is the complete recipe l3xz-joy_1.0.0.bb in full. Note the naming convention difference: in package.xml and CMake the name uses underscores (l3xz_joy), while the BitBake recipe filename uses hyphens (l3xz-joy_1.0.0.bb).
| |
Let’s go over this recipe with a fine-toothed comb, starting with the top-level inherit section:
| |
inherit pulls in a BitBake class (a .bbclass file) and adds its logic to the current recipe. The ros_distro_jazzy class (provided by meta-ros) sets distribution-wide variables such as ROS prefix (/opt/ros/jazzy), Python version, CMake build options, etc..
Next, let’s look at the package metadata:
| |
Copied verbatim from <description> in package.xml. BitBake uses DESCRIPTION for display purposes (e.g. in package managers on the target, or in build logs).
| |
AUTHOR is a standard BitBake variable; ROS_AUTHOR is meta-ros-specific. Both carry the same information that <maintainer> holds in package.xml.
| |
HOMEPAGE points to the upstream source repository.
| |
A BitBake packaging hint that groups this recipe with other development tools. It does not affect compilation but influences how package managers on the target may categorise the software.
| |
LICENSE mirrors <license>MIT</license> in package.xml. BitBake takes license compliance seriously: LIC_FILES_CHKSUM provides a checksum of the actual LICENSE file inside the source tree. This makes the build fail if the upstream repository’s license text ever changes without the recipe being updated to reflect it. The md5= hash must match the MD5 sum of ${S}/LICENSE at the pinned commit (obtain checksum via md5sum l3xz_joy/LICENSE).
Next, let’s look at the ROS package naming variables:
| |
ROS_CN (ROS Component Name) identifies the package within the ROS ecosystem. It matches <name>l3xz_joy</name> in package.xml. ROS_BPN represents the base name of the ROS package, often derived by replacing hyphens with underscores (- to _) from the original ROS package name to fit Yocto’s naming convention.
The dependency section is the heart of the recipe. ROS package manifests distinguish several dependency types (see REP-149). meta-ros preserves these distinctions even though BitBake itself knows only DEPENDS (build-time) and RDEPENDS (run-time).
| |
ROS_BUILD_DEPENDS are libraries that must be present on the build host in order to compile the package. Again note the naming convention: sensor_msgs (underscores in ROS) becomes sensor-msgs (hyphens in BitBake), and ros2_heartbeat becomes ros2-heartbeat.
| |
ROS_BUILDTOOL_DEPENDS are programs that run on the build host during compilation. ament-cmake-native is ament_cmake compiled for the host machine. It provides the CMake modules (find_package(ament_cmake REQUIRED) and ament_package()) that orchestrate a ROS 2 CMake build. The -native suffix is Yocto convention for a host-side version of a package. It corresponds to <buildtool_depend>ament_cmake</buildtool_depend> in package.xml.
| |
ROS_EXPORT_DEPENDS are dependencies which a downstream package would need on its include path or link line. In classical CMake terms these would be the libraries that appear in the package’s exported CMake targets. Here they are the same as the build dependencies because ament_target_dependencies() in CMakeLists.txt links against all three libraries and exports them. BitBake has no native concept of “export” dependencies. The ros_superflore_generated class therefore folds them into DEPENDS (see the DEPENDS line below) to ensure they are staged before any recipe that lists l3xz-joy in its own DEPENDS.
| |
Build-tool export dependencies would be host-side tools required by downstream packages to use this package’s CMake exports. For l3xz_joy there are none.
| |
ROS_EXEC_DEPENDS are shared libraries that must be present on the target at runtime for the installed binary to execute. They correspond to <depend> tags in package.xml. At runtime the node binary links dynamically against librclcpp, libsensor_msgs, and libros2_heartbeat, so all three must be present in the final image.
| |
Test dependencies are only needed when running the package’s own test suite. They correspond to <test_depend>ament_lint_auto</test_depend> and <test_depend>ament_lint_common</test_depend> in package.xml.
| |
In BitBake, DEPENDS refers to other recipes whose output must be staged in the sysroot before this recipe compiles. RDEPENDS:${PN} refers to packages that must be installed on the target when this package is installed.
The first DEPENDS assignment brings in build and build-tool dependencies. The second DEPENDS += line adds export dependencies on top — even though this package does not need them at compile time, downstream packages that list l3xz-joy in their own DEPENDS will find all transitive headers and libraries already staged. ${PN} expands to the package name (l3xz-joy), and RDEPENDS:${PN} declares the runtime dependencies that the target-side package manager will pull in automatically.
| |
SRC_URI tells BitBake’s fetcher where to retrieve the source code. SRCREV pins the fetch to a specific commit hash. S tells BitBake where the checked-out source tree lives within the build work directory.
| |
ROS_BUILD_TYPE is read from the <export><build_type> element in package.xml. The second line expands to inherit ros_ament_cmake. The ros_ament_cmake class (provided by meta-ros) implements the actual compile and install logic: it invokes CMake with the correct cross-compilation toolchain, the ROS 2 sysroot, and ament-specific options; runs make; and then calls make install to populate the staging area and eventually the target image.
Once the recipe is written, place it in the appropriate recipes-ros/<package-name>/ directory (e.g. meta-pika-spark/recipes-ros/l3xz-joy/l3xz-joy_1.0.0.bb) and verify that it builds:
| |
Add recipe l3xz-joy to packagegroup-pika-ros-examples.bb in order to build it into the Pika Spark ROS image (pika-spark-ros-jazzy-image).
| |
Once added to the image build, Yocto will fetch your package at the pinned commit, cross-compile it against the ROS 2 Jazzy sysroot, and install the resulting binary and launch files into the correct locations in the target rootfs.
| |
