The snapcraft CLI has supported building ROS1 snaps for a while via the catkin
plugin. We supported the ROS2 betas via the ament
plugin, but that was before Open Robotics had a ROS 2 package repository setup, which meant that the ament
plugin built the ROS 2 underlay from source, and it was predictably dreadfully slow. However, the stable releases of ROS2 introduced a new build system called colcon
, and also had their own package repositories setup. Version 3.2 of the snapcraft CLI (just released today) sees the addition of a colcon
plugin to support those new releases, and I’d like to give you a quick run-through of its capabilities.
Prerequisites
In order to follow along, make sure you have at least v3.2 of the snapcraft CLI. The best way to do that is to install the snap:
$ sudo snap install --classic snapcraft
Some previous experience building a snap will also be helpful.
Let’s get started
Create the snapcraft.yaml
First of all, create a new directory and initialize it with a snapcraft.yaml
:
~$ mkdir ros2-snap
~$ cd ros2-snap/
~/ros2-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: core18
parts:
ros-demos:
plugin: colcon
source: https://github.com/ros2/demos.git
source-branch: crystal
colcon-rosdistro: crystal
colcon-source-space: demo_nodes_cpp
build-packages: [make, gcc, g++]
stage-packages: [ros-crystal-ros2launch]
apps:
run:
command: opt/ros/crystal/bin/ros2 launch demo_nodes_cpp talker_listener.launch.py
plugs: [network, network-bind]
Let’s break that down and go through it 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: core18
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 in the past, I typically use strict
confinement 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 core18
which is a minimal rootfs based upon Ubuntu Bionic (18.04).
parts:
ros-demos:
plugin: colcon
source: https://github.com/ros2/demos.git
source-branch: crystal
colcon-rosdistro: crystal
colcon-source-space: demo_nodes_cpp
build-packages: [make, gcc, g++]
stage-packages: [ros-crystal-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 also specify that we’re using the newest ROS 2 release as of this writing: Crystal. We point the colcon
plugin at the C++ demo nodes specifically. We also provide a list of packages that need to be installed in order to build (build-packages
), and also ask that ros-crystal-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:
run:
command: opt/ros/crystal/bin/ros2 launch demo_nodes_cpp talker_listener.launch.py
plugs: [network, network-bind]
This part is interesting. 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 run
. The command that this app actually runs within the snap uses the ros-crystal-ros2launch
that we staged to fire up the demo nodes' talker/listener launch file. Finally, we use plugs
to specify that this app requires network access (read more about interfaces).
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
:
~$ cd ros2-snap/
~/ros2-snap$ snapcraft
<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.
The build process will take a few minutes. You’ll see the snapcraft CLI fetch rosdep
, which is then used to determine the dependencies of the packages in the colcon
workspace. It then pulls those down and unpacks them into the snap, and finally builds the packages in the workspace and installs them into the snap as well. At the end, you’ll have your snap.
Test the snap
Let’s install the snap we just built:
~/ros2-snap$ sudo snap install --dangerous ros2-talker-listener_0.1_amd64.snap
ros2-talker-listener 0.1 installed
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.run
[INFO] [launch]: process[talker-1]: started with pid [26273]
[INFO] [launch]: process[listener-2]: started with pid [26274]
[INFO] [talker]: Publishing: 'Hello World: 1'
[INFO] [listener]: I heard: [Hello World: 1]
[INFO] [talker]: Publishing: 'Hello World: 2'
[INFO] [listener]: I heard: [Hello World: 2]
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 gives you a decent overview of the capabilities of the colcon
plugin, and I look forward to seeing what you do with it! 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.