How to build a snap using ROS 2 Foxy

by Kyle Fazzari on 17 August 2020

Please note that this blog post has out technical information that may no longer be correct. For latest updated documentation about robotics in Canonical please visit https://ubuntu.com/robotics/docs.

The snapcraft CLI (the tool used to create snaps) has long had support for building snaps that use both ROS 1 and ROS 2. ROS 2 Foxy Fitzroy is the latest ROS 2 LTS, which runs on Ubuntu 20.04 (Focal Fossa). The snapcraft CLI recently gained experimental support for building Foxy snaps, so I wanted to walk you through doing exactly that with the goal of helping both of us: getting you familiar with this new feature, and at the same time getting some mileage on this so as to make it not experimental. Let’s get to it!

Prerequisites

In order to follow along, you’ll need at least v4.2 of the snapcraft CLI. At the time of writing, that is currently in the candidate channel:

$ sudo snap install --candidate --classic snapcraft

Of course, some previous experience building a snap will also be helpful.

Let’s get started

The first step toward creating any snap is to create the snapcraft.yaml, which serves as the recipe instructing the snapcraft CLI on how to piece together your snap.

Create the snapcraft.yaml

Let’s create a new directory, and then initialize it with a snapcraft.yaml:

$ mkdir ~/ros2-foxy-snap
$ cd ~/ros2-foxy-snap
$ snapcraft init
Created snap/snapcraft.yaml.
Go to https://docs.snapcraft.io/the-snapcraft-format/8337 for more information about the snapcraft.yaml format.

Open that snap/snapcraft.yaml file, and make it look like this:

name: ros2-talker-listener
version: '0.1'
summary: ROS2 Talker/Listener Example
description: |
This example launches a ROS2 talker and listener.

grade: devel
confinement: strict
base: core20

parts:
ros-demos:
plugin: colcon
source: https://github.com/ros2/demos.git
source-branch: foxy
source-subdir: demo_nodes_cpp
stage-packages: [ros-foxy-ros2launch]

apps:
ros2-talker-listener:
command: opt/ros/foxy/bin/ros2 launch demo_nodes_cpp talker_listener.launch.py
plugs: [network, network-bind]
extensions: [ros2-foxy]

Let’s break that down by section.

name: ros2-talker-listener
version: '0.1'
summary: ROS2 Talker/Listener Example
description: |
This example launches a ROS2 talker and listener.

This is the basic metadata required by all snaps. These fields are fairly self-explanatory, but note that the name must be globally unique among all snaps. You might consider appending your developer name to the end of the snap name, for example.

grade: devel
confinement: strict
base: core20

grade can be either stable or devel. If it’s devel, the store will prevent you from releasing into one of the two stable channels (stable and candidate, specifically). If it’s stable, you can release it anywhere.

confinement can be strict, devmode, or classic. strict enforces confinement, whereas devmode allows all accesses, even those that would be disallowed under strict confinement, and logs access that would be disallowed. classic is even less confined than devmode in that it doesn’t even get private namespaces anymore (among other things). There is more extensive documentation on confinement available.

As I’ve said multiple times in the past, I typically use strictconfinement unless I know for sure that the thing I’m snapping won’t run successfully under confinement, in which case I’ll use devmode. I typically avoid classic unless I never intend for the snap to run confined (e.g. you’ll notice the snapcraft CLI is a classic snap, since it needs more access to the host than confinement would allow).

Finally, the base keyword specifies a special kind of snap that provides a minimal set of libraries common to most applications (e.g. libc). It will be the root filesystem for this snap. In this case, we’re using core20 which is a minimal rootfs based upon Ubuntu 20.04 (Focal).

parts:
ros-demos:
plugin: colcon
source: https://github.com/ros2/demos.git
source-branch: foxy
source-subdir: demo_nodes_cpp
stage-packages: [ros-foxy-ros2launch]

The snapcraft CLI is responsible for taking many disparate parts and orchestrating them all into one cohesive snap. You tell it the parts that make up your snap, and it takes care of the rest. Here, we’re saying that we have a single part called ros-demos. We specify that it builds using the colcon plugin, and we point it to the ROS 2 demos GitHub repository (this could just as easily be a directory on disk). We point the colcon plugin at the subdirectory containing the C++ demo nodes as opposed to letting it build the whole thing. We also provide a list of packages that need to be installed in order to build (build-packages), and also ask that ros-foxy-ros2launch gets staged into the snap alongside the rest of the part to be used at runtime (specifically, we’ll use it in the app, below). To view all the options supported by the colcon plugin, run the command snapcraft help colcon.

apps:
ros2-talker-listener:
command: opt/ros/foxy/bin/ros2 launch demo_nodes_cpp talker_listener.launch.py
plugs: [network, network-bind]
extensions: [ros2-foxy]

This part is interesting, and even if you’ve built snaps in the past there might be something new here. When we build this snap, it will include a complete ROS 2 system: rclcpp, the demo_nodes_cpp workspace, etc. It could contain the entire system necessary for a robot in one installable blob. It’s a standalone unit: we’re in total control of how we want our users to interact with it. We exercise that control via the apps keyword, where we expose specific commands to the user. Here we specify that this snap has a single app called ros2-talker-listener (the same name as the snap itself), although it could be anything. The command that this app actually runs within the snap uses the ros-foxy-ros2launch that we staged to fire up the demo nodes’ talker/listener launch file. We use plugs to specify that this app requires network access (read more about interfaces). Finally, the new bit: we specify that this uses the ros2-foxy extension. This is where a significant chunk of the magic happens, wrapping the app in the environment necessary to run ROS 2 Foxy stuff, etc. Every app that needs ROS 2 will need to use that extension, and those that don’t need ROS 2 don’t need the extension (read more about snapcraft extensions).

Build the snap

Now that we’ve defined the snapcraft.yaml, it’s time to build the snap. Make sure you’re in the directory we created earlier (the one that contained the snap/ directory), and run snapcraft with the --enable-experimental-extensions flag (I told you this was experimental):

$ cd ~/ros2-snap
$ snapcraft --enable-experimental-extensions
*EXPERIMENTAL* extensions enabled.
*EXPERIMENTAL* extension 'ros2-foxy' enabled.
<snip>
This part is missing libraries that cannot be satisfied with any available stage-packages known to snapcraft:
libnddsc.so
libnddscore.so
libnddscpp.so
librosidl_typesupport_connext_c.so
librosidl_typesupport_connext_cpp.so
librticonnextmsgcpp.so
Snapped ros2-talker-listener_0.1_amd64.snap

The warnings regarding missing libraries are false positive here. These libraries are build time dependencies only.

Note that depending on your host and whether or not you’ve built snaps in the past, the snapcraft CLI may prompt you to install Multipass, a tool used by the snapcraft CLI to manage VMs for building snaps (rather than building straight on your host).

The build process will take a few minutes. You’ll see the snapcraft CLI install the build dependencies of the demo nodes, build the packages in the workspace and install them into the snap, and fetch their runtime dependencies and unpack them into the snap as well. This is a bit different than previous ROS snap build processes that used rosdep: this uses catkin_pkg directly to avoid rosdep‘s issue of unpacking even build dependencies into the snap, which means ROS snaps using extensions will be significantly smaller than in the past (and take less time to rebuild). Anyway, at the end, you’ll have your snap.

Test the snap

Let’s install the snap we just built:

$ cd ~/ros2-snap
$ sudo snap install ros2-talker-listener_0.1_amd64.snap --dangerous

Note the use of the --dangerous flag. That’s required because we’re installing a snap from disk instead of using the store, and snapd (the daemon with which we’re communicating using the snap command) only trusts snaps that it can cryptographically verify as being from the store unless we tell it otherwise with this flag.

Finally, let’s run the app we defined in the snapcraft.yaml:

$ ros2-talker-listener 
<snip>
[INFO] [talker-1]: process started with pid [21071]
[INFO] [listener-2]: process started with pid [21073]
[talker-1][RTPS_TRANSPORT_SHM Error] Failed to create segment 86bb3c83d0835208: Permission denied -> Function compute_per_allocation_extra_size
[talker-1][RTPS_MSG_OUT Error] Permission denied -> Function init
[listener-2][RTPS_TRANSPORT_SHM Error] Failed to create segment 02affbbfef90b6bb: Permission denied -> Function compute_per_allocation_extra_size
[listener-2][RTPS_MSG_OUT Error] Permission denied -> Function init
[talker-1] [INFO] [1597444182.799512859] [talker]: Publishing: 'Hello World: 1'
[listener-2] [INFO] [1597444182.800671590] [listener]: I heard: [Hello World: 1]
[talker-1] [INFO] [1597444182.799512859] [talker]: Publishing: 'Hello World: 1'
[listener-2] [INFO] [1597444182.800671590] [listener]: I heard: [Hello World: 1]
[talker-1] [INFO] [1597444183.799313385] [talker]: Publishing: 'Hello World: 2'
[listener-2] [INFO] [1597444183.799922986] [listener]: I heard: [Hello World: 2]

CTRL+C to stop that. As you can see, it works great! You could hand this snap to anyone with a snap-capable system, even if they don’t have ROS installed, and it would work exactly the same way for them.

You surely noticed the error regarding shared memory. This doesn’t prevent the example from working. ROS 2 falls back to the default mode since it cannot access shared memory. We are currently working on making it work within snaps!

I hope this is helpful in your snap journey, and gives you a decent guide to building ROS 2 Foxy snaps using the new ros2-foxy extension. Please feel free to ask any questions on the Snapcraft forums, or on the ROS forums. I’d love to hear any feedback you have.

This article originally appeared on Kyle Fazzari’s blog.

Newsletter Signup

Related posts

Snapcraft 8.0 and the respectable end of core18

‘E’s not pinin’! ‘E’s passed on! This base is no more! He has ceased to be! ‘E’s expired and gone to meet ‘is maker! ‘E’s a stiff! Bereft of life, ‘e rests in peace! If you hadn’t nailed ‘im to the perch ‘e’d be pushing up the daisies! ‘Is software processes are now ‘istory! ‘E’s […]

Craft team welcomes you to another episode of its adventures

Welcome to the second article in the Craft team saga. Previously, on Craft Team, we gave you a brief introduction into the team’s function, we announced our desire to share the ins and outs of our day-to-day work with the community, and gave you an overview of roughly two weeks of coding and fun. Today, […]

Creating Snaps on Ubuntu Touch

This article was written in collaboration with Alfred E. Neumayer of the UBports Project. Tablets, phones and current technology’s capabilities are phenomenal. Who would have thought a thin, light, barely 10 inch device would provide all the power necessary to run Virtual Machines, wherever one desires while powered on battery? That a sma […]