Show EOL distros: 

Note: This tutorial assumes that you have completed the previous tutorials: examining the simple publisher and subscriber.
(!) Please ask about problems and questions regarding this tutorial on answers.ros.org. Don't forget to include in your question the link to this page, the versions of your OS & ROS, and also add appropriate tags.

Packaging your ROS project as a snap

Description: This tutorial covers how to package and deploy your ROS project as a snap.

Tutorial Level: INTERMEDIATE

Next Tutorial: Writing a Tutorial

What are snaps?

Snaps are containers that bundle an application and all its dependencies. They offer several features that address important concerns as one gets closer to shipping a robotic platform:

  • Container solution: Snaps bundle all your dependencies and assets in one package (including ROS) making your application installable on dozens of Linux distributions and across distro versions.

  • Strict confinement: Snaps are designed to be secure and isolated from the underlying system and other applications, with dedicated interfaces to access the host machine.

  • Managing updates: Snaps can update automatically and transactionally, making sure the device is never broken and always up-to-date.

  • Release management: Snaps' multiple release channels allow you to have role-based access controls and application versioning, making A/B testing easy and releasing fixes faster.

Creating a snap

This tutorial will demonstrate how to use Snapcraft to create a new snap, and then how to use it.

First, let us install Snapcraft.

$ sudo snap install --classic snapcraft

(Note that the snapcraft Debian package from the apt repositories is largely deprecated. One should use the snap package.)

Snapcraft has built-in support for Catkin. In order for it to know which project components to include, you must ensure that your projects have install rules.

For our example, we will use roscpp_tutorials from the ros_tutorials.

Initialize a new Snapcraft project here:

$ mkdir ~/roscpp_tutorials_snap
$ cd ~/roscpp_tutorials_snap
$ snapcraft init

This will create a file in a subdirectory snap/snapcraft.yaml

The snapcraft file

Open that snap/snapcraft.yaml file and make copy over the following:

name: ros-talker-listener
version: '0.1'
summary: ROS Talker/Listener Example
description: |
 This example launches a ROS talker and listener.
 
confinement: devmode
base: core20
 
parts:
 ros-tutorials:
   plugin: catkin
   source: https://github.com/ros/ros_tutorials.git
   source-branch: noetic-devel
   source-subdir: roscpp_tutorials
   stage-packages:
       - ros-noetic-roslaunch
 
apps:
 ros-talker-listener:
   command: opt/ros/noetic/bin/roslaunch roscpp_tutorials talker_listener.launch
   extensions: [ros1-noetic]

Don’t worry, we will break it down together.

Metadata

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

This is the basic metadata that all snaps require. These fields are fairly self-explanatory but note that the name must be globally unique across all snaps.

Base

base: core20

The base keyword defines a special kind of snap that provides a run-time environment with a minimal set of libraries that are common to most applications. Core20 is the current standard base for snap building and is based on Ubuntu 20.04 LTS. It is, therefore, the base used for Noetic.

Security model

confinement: devmode

To get started, we won’t confine this application. Unconfined applications, specified with devmode, can only be released to the edge channel of the snapcraft store.

Parts

parts:
 ros-tutorials:
   plugin: catkin
   source: https://github.com/ros/ros_tutorials.git
   source-branch: noetic-devel
   source-subdir: roscpp_tutorials
   stage-packages:
       - ros-noetic-roslaunch

Parts define how to build your app. In this case, we have one: ros-tutorials. Parts can point to local directories, remote git repositories, or tarballs. Here, we specify our source as a GitHub repository at a specific branch. We also specifically tell Catkin to build the roscpp_tutorials subdirectory. Furthermore, we tell snapcraft that packages such as make are necessary at build time while the package ros-noetic-roslaunch is necessary at build time. For more information about the plugin and it options, please refer to the online documentation.

The Catkin packages you’re building must have install rules, or the snapcraft CLI won’t know which components to place into the snap. Make sure you install binaries, libraries, header files, launch files, etc.

Apps

apps:
 ros-talker-listener:
   command: opt/ros/noetic/bin/roslaunch roscpp_tutorials talker_listener.launch
   extensions: [ros1-noetic]

Apps are the commands exposed to end users. Each key under apps is the command name that should be made available on users’ systems. The command keyword specifies the command to be run, as its name suggests. Finally, the extensions ros1-noetic essentially sets up the ROS2 apt package repository together with the necessary environment variables.

Building the snap

Now that we are all set up, let’s build the snap:

$ cd ~/roscpp_tutorials_snap
$ snapcraft --enable-experimental-extensions
*EXPERIMENTAL* extensions enabled.
Launching a VM.
Launched: snapcraft-ros-talker-listener
[...]
Snapped ros-talker-listener_0.1_amd64.snap

That will take a few minutes. From the logs, and among other things, you will see Snapcraft using rosdep to pull the dependencies of your package but also Catkin building your application. That is because internally, Snapcraft relies on the familiar ROS tool.

Testing the snap

This snap is completely standalone: it includes ROS and your application, meaning that you don’t even need to install ROS on your system. Let’s test it out:

$ sudo snap install ros-talker-listener_0.1_amd64.snap --devmode

Note that we use --devmode here because the snap is devmode confinement.

The moment of truth, will it run?

$ ros-talker-listener

[ INFO] [1497643945.491444894]: hello world 0
[ INFO] [1497643945.495444894]: I heard: [hello world 0]
[ INFO] [1497643945.591430533]: hello world 1
[ INFO] [1497643945.595430533]: I heard: [hello world 1]
[ INFO] [1497643945.691426519]: hello world 2
[ INFO] [1497643945.695426519]: I heard: [hello world 2]
[ INFO] [1497643945.791444793]: hello world 3
[ INFO] [1497643945.795444793]: I heard: [hello world 3]

It does! We see the expected output!

You can find more information about snap on the snapcraft documentation and ROS snap page.

Creating a snap

This tutorial will demonstrate how to use Snapcraft to create a new snap, and how to use it.

First, let us install Snapcraft.

$ sudo snap install --classic snapcraft

(Note that the snapcraft Debian package from the apt repositories is largely deprecated. One should use the snap package.)

Snapcraft has built-in support for Catkin. In order for it to know which project components to include, you must ensure that your projects have install rules.

For our example, we will use roscpp_tutorials from the ros_tutorials.

Initialize a new Snapcraft project here:

$ mkdir ~/roscpp_tutorials_snap
$ cd ~/roscpp_tutorials_snap
$ snapcraft init

This will create a file in a subdirectory snap/snapcraft.yaml

The snapcraft file

Open that snap/snapcraft.yaml file and copy over the following:

name: ros-talker-listener
version: '0.1'
summary: ROS Talker/Listener Example
description: |
 This example launches a ROS talker and listener.
confinement: devmode
base: core18
 
parts:
 ros-tutorials:
   plugin: catkin
   source: https://github.com/ros/ros_tutorials.git
   source-branch: melodic-devel
   source-space: roscpp_tutorials/
 
apps:
 ros-talker-listener:
   command: roslaunch roscpp_tutorials talker_listener.launch

Don't worry, we will break it down together.

Metadata

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

This is the basic metadata that all snaps require. These fields are fairly self-explanatory but note that the name must be globally unique across all snaps.

Base

base: core18

The base keyword defines a special kind of snap that provides a run-time environment with a minimal set of libraries that are common to most applications. Core18 is the current standard base for snap building and is based on Ubuntu 18.04 LTS. It is, therefore, the base used for Melodic.

Security model

confinement: devmode

To get started, we won’t confine this application. Unconfined applications, specified with devmode, can only be released to the edge channel of the snapcraft store.

Parts

parts:
 ros-tutorials:
   plugin: catkin
   source: https://github.com/ros/ros_tutorials.git
   source-branch: melodic-devel
   source-space: roscpp_tutorials/

Parts define how to build your app. In this case, we have one: ros-tutorials. Parts can point to local directories, remote git repositories, or tarballs. Here, we specify our source as a GitHub repository at a specific branch. We also specifically tell Catkin to build the roscpp_tutorials subdirectory. For more information about the plugin and it options, please refer to the online documentation. The Catkin packages you’re building must have install rules, or the snapcraft CLI won’t know which components to place into the snap. Make sure you install binaries, libraries, header files, launch files, etc.

Apps

apps:
 ros-talker-listener:
   command: roslaunch roscpp_tutorials talker_listener.launch

Apps are the commands exposed to end users. Each key under apps is the command name that should be made available on users’ systems. The command keyword specifies the command to be run as its name suggests.

Building the snap

Now that we are all set up, let’s build the snap:

$ cd ~/roscpp_tutorials_snap
$ snapcraft
*EXPERIMENTAL* extensions enabled.
Launching a VM.
Launched: snapcraft-ros-talker-listener
[...]
Snapped ros-talker-listener_0.1_amd64.snap

That will take a few minutes. From the logs, and among other things, you will see Snapcraft using rosdep to pull the dependencies of your package but also Catkin building your application. That is because internally, Snapcraft relies on the familiar ROS tool.

Testing the snap

This snap is completely standalone: it includes ROS and your application, meaning that you don’t even need to install ROS on your system. Let's test it out:

$ sudo snap install ros-talker-listener_0.1_amd64.snap --devmode

Note that we use --devmode here because the snap is devmode confinement. The moment of truth, will it run?

$ ros-talker-listener

[ INFO] [1497643945.491444894]: hello world 0
[ INFO] [1497643945.495444894]: I heard: [hello world 0]
[ INFO] [1497643945.591430533]: hello world 1
[ INFO] [1497643945.595430533]: I heard: [hello world 1]
[ INFO] [1497643945.691426519]: hello world 2
[ INFO] [1497643945.695426519]: I heard: [hello world 2]
[ INFO] [1497643945.791444793]: hello world 3
[ INFO] [1497643945.795444793]: I heard: [hello world 3]

It does! We see the expected output!

You can find more information about snap on the snapcraft documentation and ROS snap page.

Creating a snap

This tutorial will demonstrate how to use Snapcraft to create a new snap, and then the usage.

First, install Snapcraft. It’s recommended to install it from the Snap Store:

$ sudo snap install --classic snapcraft

(Note that using the apt repositories for snapcraft is not recommended and this tutorial will assume that you installed the snap.)

Snapcraft has built-in support for Catkin: you point it at a workspace, and tell it what packages to include in the snap. In order for it to know which project components to include, you must ensure that your projects have good install rules. Let's do that now. Open up your CMakeLists.txt with rosed beginner_tutorials CMakeLists.txt. If you followed the Python tutorial, add the install target at the end so that it looks like this (unused bits and comments removed):

cmake_minimum_required(VERSION 2.8.3)
project(beginner_tutorials)

## Find catkin and any catkin packages
find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs genmsg)

## Declare ROS messages and services
add_message_files(FILES Num.msg)
add_service_files(FILES AddTwoInts.srv)

## Generate added messages and services
generate_messages(DEPENDENCIES std_msgs)

## Declare a catkin package
catkin_package()

include_directories(include ${catkin_INCLUDE_DIRS})

## Install scripts
install(PROGRAMS scripts/talker.py scripts/listener.py
  DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)

If you followed the C++ tutorial, add the install target at the end so that the CMakeLists.txt looks like this:

cmake_minimum_required(VERSION 2.8.3)
project(beginner_tutorials)

## Find catkin and any catkin packages
find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs genmsg)

## Declare ROS messages and services
add_message_files(FILES Num.msg)
add_service_files(FILES AddTwoInts.srv)

## Generate added messages and services
generate_messages(DEPENDENCIES std_msgs)

## Declare a catkin package
catkin_package()

## Build talker and listener
include_directories(include ${catkin_INCLUDE_DIRS})

add_executable(talker src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
add_dependencies(talker beginner_tutorials_generate_messages_cpp)

add_executable(listener src/listener.cpp)
target_link_libraries(listener ${catkin_LIBRARIES})
add_dependencies(listener beginner_tutorials_generate_messages_cpp)

## Install talker and listener
install(TARGETS talker listener
  RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)

Change to the root of the catkin workspace you used in the "writing a simple publisher and subscriber" tutorial:

# This workspace should contain the beginner_tutorials package.
$ cd ~/catkin_ws

Initialize a new Snapcraft project here:

$ snapcraft init
Created snap/snapcraft.yaml.
Edit the file to your liking or run `snapcraft` to get started

The snapcraft.yaml

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

name: publisher-subscriber
base: core
version: '0.1'
summary: ROS Example
description: |
  The ROS publisher/subscriber example packaged as a snap.

grade: stable
confinement: strict

parts:
  workspace:
    plugin: catkin
    source: .
    catkin-packages: [beginner_tutorials]

apps:
  roscore:
    command: roscore
    plugs: [network, network-bind]

  talker:
    # Use talker.py if you followed the Python tutorial
    command: rosrun beginner_tutorials talker
    plugs: [network, network-bind]

  listener:
    # Use listener.py if you followed the Python tutorial
    command: rosrun beginner_tutorials listener
    plugs: [network, network-bind]

The snapcraft.yaml explained

Let's break that down piece by piece.

name: publisher-subscriber
base: core
version: '0.1'
summary: ROS Example
description: |
  The ROS publisher/subscriber example packaged as a snap.

This is the basic metadata that all snaps require. These fields are fairly self-explanatory, but note that the name must be globally unique among all snaps. The one that is probably not obvious is base.

A base is a special kind of snap that provides a minimal set of libraries common to most applications. A base snap mounts itself as the root filesystem within your snap so that when your application runs, the base's library paths are searched directly after the paths for your specific snap. In our case, we're using base: core which is a rootfs generated from Ubuntu 16.04 (Xenial). As a result, snapcraft understands what you're wanting to use ROS Kinetic. Another option would be to use base: core18, which is a rootfs generated from Ubuntu 18.04 (Bionic), thereby causing snapcraft to use ROS Melodic.

grade: stable
confinement: strict

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)-- think of it as a safety net to prevent accidental releases. 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 accesses that would otherwise be disallowed for your reference). 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.

parts:
  workspace:
    plugin: catkin
    source: .
    catkin-packages: [beginner_tutorials]

Snapcraft 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 tell Snapcraft that we have a single part called workspace. We specify that it builds with Catkin, and we specify the packages in this workspace that we want included in the snap. In our case, we only have one: the beginner_tutorials package we've been working on through the tutorials. Note that this is not required: if you leave catkin-packages off entirely, Snapcraft will build all packages in the workspace.

apps:
  roscore:
    command: roscore
    plugs: [network, network-bind]

  talker:
    # Use talker.py if you followed the Python tutorial
    command: rosrun beginner_tutorials talker
    plugs: [network, network-bind]

  listener:
    # Use listener.py if you followed the Python tutorial
    command: rosrun beginner_tutorials listener
    plugs: [network, network-bind]

Now things get a little interesting. When this snap is built, it will include a complete ROS system: roscpp, rospy, roscore, your ROS workspace, etc. It's a standalone unit, and you're in complete control over how the user interacts with it. You exercise that control via the apps keyword, where you expose specific commands to the user.

Here we simply expose the three components of the publisher/subscriber tutorial: roscore, the talker, and the listener. We could just as easily written a launch file to bring up the entire system in a single app.

We use plugs to specify that each app requires network access (read more about interfaces). Without this, each app would be confined such that they couldn't communicate.

Actually create the snap

That's it: time to build the snap.

$ cd ~/catkin_ws
$ snapcraft

That will take a few minutes. You may be prompted to install multipass, a tool used by snapcraft to ensure build isolation (so it doesn't dirty up your host when building). You'll see Snapcraft fetch rosdep, which is then used to determine the dependencies of the ROS packages in the workspace. It then pulls those down and puts them into the snap along with roscore. Finally, it builds the requested packages in the workspace, and installs them into the snap as well. At the end, you'll have your snap.

Testing the snap

As we discussed previously, this snap is completely standalone: you could email it to someone and they'd be able to install it and run your ROS system, even if they didn't have ROS installed themselves. Test it out yourself:

# We use --dangerous here because the snap doesn't come from an
# authenticated source
$ sudo snap install --dangerous publisher-subscriber_0.1_amd64.snap

Now you can take it for a spin just like you did when examining the simple publisher and subscriber. First, run roscore:

$ publisher-subscriber.roscore

Now run the publisher:

$ publisher-subscriber.talker

And you'll see the familiar output:

[ INFO] [1497643945.491444894]: hello world 5
[ INFO] [1497643945.591430533]: hello world 6
[ INFO] [1497643945.691426519]: hello world 7
[ INFO] [1497643945.791444793]: hello world 8
[ INFO] [1497643945.891433313]: hello world 9

Now let's run the listener:

$ publisher-subscriber.listener

And you'll see the this project works exactly the same as before:

[ INFO] [1497643969.443662208]: I heard: [hello world 41]
[ INFO] [1497643969.543668530]: I heard: [hello world 42]
[ INFO] [1497643969.643621679]: I heard: [hello world 43]
[ INFO] [1497643969.743650720]: I heard: [hello world 44]
[ INFO] [1497643969.843650108]: I heard: [hello world 45]

When you're done, as usual, press Ctrl-C to terminate roscore, as well as the talker and listener.

Sharing the snap with the world

Emailing snaps to people doesn't scale, but more importantly, it gives the snap users no upgrade path. If you make your snap available in the snap store, whenever you release a new version, your robots (or users) will automatically update. In order to do this, you'll need to create a (free) store account at snapcraft.io. Then, using Snapcraft, login with that account:

$ snapcraft login

Remember our previous discussion about how snap names need to be unique. Before you can release your snap in the store, you need to register the snap name to your store account:

$ snapcraft register <my snap name>

Finally, push the snap up to the store and release it into the stable channel:

$ snapcraft push path/to/my.snap --release=stable

Once the upload and automated reviews finish successfully, anyone in the world can install your snap as simply as:

$ sudo snap install <my snap name>

And they can rest secure knowing that when you release an update, they'll get it, no further work required.

Wiki: ROS/Tutorials/Packaging your ROS project as a snap (last edited 2022-03-25 11:15:21 by GuillaumeBeuzeboc)