How to build a snap using ROS 2 Foxy

by Kyle Fazzari on 17 August 2020

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
build-packages: [make, gcc, g++]
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
build-packages: [make, gcc, g++]
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>
Snapped ros2-talker-listener_0.1_amd64.snap

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] [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.

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

Stepping Down Gracefully

The Snap Store has been designed to enable upstream developers and enthusiastic community contributors to publish snaps. As with most Linux packaging solutions, the wider community are often responsible for starting and maintaining software packages. This is a double-edged sword, especially for humans with limited life spans and other shi […]

The Expandables – snapcraft extensions and the secret code

If you’re a snap developer, you know that snap development is terribly easy. Or rather complex and difficult. Depending on your application code and requirements, it can take a lot of effort putting together the snapcraft.yaml file from which you will build your snap. One of our goals is to make snap development practically easier […]

Security corner: snap interface & snap connections

One of the defining features of snaps is their strong security. Snaps are designed to run isolated from the underlying system, with granular control and access to specific resources made possible through a mechanism of interfaces. Think of it as a virtual USB cable – an interface connects a plug with a slot. Security and […]