How to build ROS 2 Eloquent Snaps

The end of 2019 brings about the latest ROS 2 release – Eloquent Elusor. Despite an ever growing set of features and some changes throughout the ecosystem, packaging with snaps is as easy as always. Let’s go through a quick example!

Prerequisites

You’ll need two tools: “snapcraft,” the program that builds snaps, and “multipass,” the VM management tool that is used by snapcraft to conduct snap builds without cluttering your development machine. You can get both of these from the snap store.

At the time of this writing, the latest stable release of snapcraft is version 3.8. Until the next release comes out, in order to build Eloquent snaps, you’ll need to grab it from edge.

$ snap install snapcraft --edge --classic

If you already have snapcraft stable installed, you can change to edge using:

$ snap refresh snapcraft --edge

Multipass is grabbed the same way, requiring the --beta flag since the version at the time of writing is 0.9.0

$ snap install multipass --classic --beta

Getting Started

Create the snapcraft.yaml

First of all, create a new workspace directory and add a snapcraft.yaml.

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

Open snap/snapcraft.yaml, and make the following changes:

name: eloquent-talker-listener-arnatious
version: "0.1"
summary: Eloquent ROS2 Example
description: |
 A ROS2 workspace containing a talker and a listener.
 
grade: devel
confinement: strict
base: core18
 
parts:
 colcon-part:
   plugin: colcon
   source: https://github.com/ros2/demos.git
   source-branch: eloquent
   colcon-rosdistro: eloquent
   colcon-source-space: demo_nodes_cpp
   build-packages: [make, gcc, g++]
   stage-packages: [ros-eloquent-ros2launch]
 
apps:
 eloquent-talker-listener-arnatious:
   command: opt/ros/eloquent/bin/ros2 launch demo_nodes_cpp talker_listener.launch.py
   plugs: [network, network-bind]

Let’s take an in depth look at this manifest.

name: eloquent-talker-listener
version: "0.1"
summary: Eloquent ROS2 Example
description: |
 A ROS2 workspace containing a talker and a listener.

This is the metadata any snap requires. The only caveat to this section is that the name must be unique on the snap store. Appending your developer name to the snap is an option to get around this (like I did).

grade: devel
confinement: strict
base: core18

grade should be either stable or devel, with devel locking out the ability to publish to the “stable” and “candidate” channels.

confinement can be strict, devmode, or classic. These are documented in detail on snapcraft.io, but in short: strict enforces tight confinement, restricting access to the system for security; devmode allows full access without any restriction but logs all accesses that strict would prevent; classic is unconfined, without its own private namespace or other security features.

Generally, you should always target “strict” confinement unless there’s specific features that confinement will prevent. “Classic” snaps, like snapcraft itself or text editors like Visual Studio Code, have no containment and run without protection. Classic snaps need manual approval.

The base keyword specifies the special snap providing things like a minimal set of core libraries for our snap’s runtime environment. We’re just using the “core18” snap, which bundles a minimal rootfs based on Ubuntu Bionic (18.04).

parts:
 colcon-part:
   plugin: colcon
   source: https://github.com/ros2/demos.git
   source-branch: eloquent
   colcon-rosdistro: eloquent
   colcon-source-space: demo_nodes_cpp
   build-packages: [make, gcc, g++]
   stage-packages: [ros-eloquent-ros2launch]

Snapcraft builds packages piecemeal, in small parts that are combined together. These are reflected by the part entries in the snapcraft.yaml. colcon-part is specified here as pulling the source code from a remote Github repository, though it can just as easily pull source code from a local directory or any source control system.

The colcon plugin is used here to build the package, with the colcon-rosdistro argument specified as eloquent, the colcon-source-space specified as the demo_nodes_cpp directory containing just the talker/listener package we care about. We specify the packages that provide the commands we need to build the ROS nodes under build-packages. stage-packages contains ros2launch, as we’ll need the launch verb it provides as part of the entry point to our snap.

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

We need to express how users will interact with this package. Since we’re packaging ~our~ project, we want the snap to expose our project’s verbs. We define these user interactions as “apps” in the apps” section.

In general, these verbs are invoked using <snap-name>.<app>. An entry in apps with the same name as the snap, like “eloquent-talker-listener-arnatious” above, can be invoked with just the snap name. Calling this app invokes the script in the command field, in this case roslaunch-ing the talker/listener system out of the demo_nodes_cpp package.

Lastly, we specify the interfaces the snap needs access to outside of confinement – in this case network and network-bind. This allows the snap to access the network and be accessed from the network, respectively. You can read more about interfaces on the snapcraft site.
Build the Snap
Now that we’ve defined the snapcraft.yaml, we can build the snap itself. Make sure you’re in the correct directory (above the “snap/” directory containing the “snapcraft.yaml”) and invoke “snapcraft.”

~$ cd eloquent-snap
~/eloquent-snap$ snapcraft
<omitted for brevity>
Snapped colcon-talker-listener-arnatious_0.1_amd64.snap

If you neglected to install multipass earlier, snapcraft may prompt you to do so.

The build process may take several minutes. During that time, the colcon plugin will install rosdep and use it to determine the dependencies specified in the package.xml of the packages, fetching and unpacking them into your snap. At the end, the result is squashed into a .snap file.

Test the Snap

To install the snap we just built before publishing it, we run

~/eloquent-snap$ sudo snap install --dangerous eloquent-talker-listener-arnatious_0.1_amd64.snap
eloquent-talker-listener-arnatious 0.1 installed

The --dangerous flag is required to install snaps from locations other than the store. The snapd daemon responsible for running snaps requires any snap to be cryptographically verified from the snap store, unless the --dangerous flag is used.

Finally, we invoke the snap:

$ eloquent-talker-listener-arnatious
[INFO] [launch]: All log files can be found below /home/arnatious/snap/eloquent-talker-listener-arnatious/x1/.ros/log/2019-12-11-07-56-04-973199-arnatious-ubuntu-1910
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [talker-1]: process started with pid [28330]
[INFO] [listener-2]: process started with pid [28331]
[talker-1] [INFO] [talker]: Publishing: 'Hello World: 1'
[listener-2] [INFO] [listener]: I heard: [Hello World: 1]
[talker-1] [INFO] [talker]: Publishing: 'Hello World: 2'
[listener-2] [INFO] [listener]: I heard: [Hello World: 2]
[talker-1] [INFO] [talker]: Publishing: 'Hello World: 3'
[listener-2] [INFO] [listener]: I heard: [Hello World: 3]
…

The snap runs without issue on the machine, regardless of linux distro or whether ROS is on the machine. You can now push to the the snap store – where anyone running snapd can install and run it.

I hope this gives you a good look at how to build ROS 2 Eloquent snaps! Feel free to ask questions on the Snapcraft forums or on the ROS Forums.

About: Blog