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.
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
First of all, create a new directory and initialize it with a
~$ 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.
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
devel. If it’s
devel, the store will prevent you from releasing into one of the two stable channels (
candidate, specifically). If it’s
stable, you can release it anywhere.
confinement can be
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).
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:
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
~$ 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
$ ros2-talker-listener.run [INFO] [launch]: process[talker-1]: started with pid  [INFO] [launch]: process[listener-2]: started with pid  [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.