Guidelines and HOWTOs/Snap

Want to run your application binaries on any Linux distribution? Snap makes that possible.

For general purpose information about snap, snapcraft and how to use them please have a look at their documentation as it excellently teaches you most generic information about snaps https://docs.snapcraft.io
Even so this page will teach you a lot of the basics of snaps.

How it Works

Snaps work very much like DMG files on OS X. A snap is a compressed squashfs file containing the entire file tree of the program. A system-level helper called snapd manages the snap file and mounts it into the file system. Snapd also manages host-level integration (e.g. creation of .desktop files). Running an application from the snap needs to go through snapd (e.g. snap run foo) which needs to be installed on the target system. Snap, the format, could be used by various daemon implementations, but as a matter of fact the only viable implementation is snapd and maintained by Canonical. Snapd frowns upon side-loaded snaps. You may still install a snap downloaded manually, but the preferred way to use snaps is through snapd directly which in turn will go to the associated store as a remote source. The store too is run by Canonical. Most aspects of snaps may be inspected/changed through the CLI for snapd. It's simply called "snap" snap help for help.

Binary Sources

Building a snap we'll lovingly call "snapping". At the time of writing you can snapcraft on two core systems: one is Ubuntu 16.04, the other is Ubuntu 18.04. Both of them are the LTS version of Ubuntu and therefore supported for what seems like forever. Snapcraft has native support to pull binary packages (i.e. debs) into the snap so that you might build against them without having to build the binaries yourselves. For example you could use Qt from Ubuntu directly without the need to build it in your snap. However, since the base systems are LTS the software is usually very dated. Seeing as you may need newer dependencies there's a bunch of ways you can get them besides pre-built Ubuntu debs:

As Snapcraft Parts

You can add any number of additional parts that build dependencies. For example you might build your own qtbase as part of snap. How much work this is can be vastly different between software. Building all of Qt can get quickly slow, at the same time relying on an ancient Qt may not be practical either. To gain access to newer foundations software you may instead want to get them ...

From KDE neon

We are in luck and KDE neon already builds packages for Ubuntu LTS, so you may choose to simply use neon debs on top of Ubuntu debs. This gives access to the latest Qt, KDE frameworks and other related libraries. This can be a huge time saver. Unfortunately Snapcraft's support for adding additional repositories is non-existent and so if you choose to go this route you may need to somewhat manually manage the build environment yourself e.g. using an especially prepared LXD container.

From Content Snaps

Content snaps are other snaps you can "include" in your snap to get access to pre-existing binary (or data) assets so you don't need to ship them. Notable advantage is that one content snap may be shared across multiple consumers and thus reduce the disk and network footprint. For example there is a read to use KF5 content snap which you can use to get access to all
of KF5 and Qt5. Content snaps may be combined with the other means of sourcing binaries.

From PPAs

Much like KDE neon, this too will require you to manage the build environment manually. Also, when using PPAs beware that they may not be compatible with neon (so, ideally you should use either-or) and that many of them are not particularly trustworthy or well maintained (important vis-a-vis security).

Snapcraft

The tool to build snaps is called snapcraft. The definition for how to craft a snap is written down in snapcraft.yaml. Generally speaking a snapcraft.yaml will contain global metadata of the snap, a list of applications provided by the snap, and lastly a list of parts that when put together result in the snap. Access to the host system is controlled through a plug-and-slot system which is also used in the snapcraft.yaml. Each application may have one or multiple plugs it uses to get access to resources of the host system or other snaps. For example the 'desktop' plug gives access to host fonts and icons. Also see https://docs.snapcraft.io/interface-management/6154

Types of Snap

With a broad overview of abilities and shortcomings let's dive right in and look at types of snaps we might build.

Standalone

A standalone snap is a snap which solely relies on a "core" but no other snaps. This is generally speaking the most reliable type of snap as everything the snap needs is inside the snap (except libc and friends which are in the core).

It is also the best supported way of building a snap since it's been around since the very beginning.

Advantages:

Very reliable

Easy to build and test

You are always on your own and unrelated changes rarely if ever can impair your snap

Disadvantages:

Huge in size (each standalone snap needs to ship their own Qt/l10n and necessary kf5 and other dependencies)

You need to take care of setting up your execution environment yourself.

You are always on your own and unrelated changes rarely if ever can improve your snap

Example

1 name:qtnetsample 2 version:'0'# the version of the snap. has no semantic meaning 3 summary:This is my-snap's summary# 79 char long summary 4 description:This is my-snap's description# a longer description for the snap 5 confinement:strict# use "strict" to enforce system access only via declared interfaces 6 grade:devel# use "stable" to assert the snap quality 7 base:core18# the core this snap depends on 8 9 apps:10 qtnetsample:11 command:launcher qtnetsample# the launcher will setup the environment for qtnetsample to find libraries/plugins/data etc12 plugs:[x11,network,network-bind]# this snap will be able to act as xclient and talk over the network13 14 parts:15 qtnetsample:16 build-packages:[qt5-default]17 plugin:cmake18 stage-packages:[libqt5network5,libqt5core5a]19 source:.

Shared Snap

A snap may also choose to use one or more Content Snaps (see glossary) to share part of the binaries or UI assets with other snaps. As shared content will generally be in the content snap, the ultimate size of the snap can be fairly small. Think of this as an approach more akin to how traditional binary package dependencies work. Albeit with many of the same complexities surrounding it.

For example KDE neon builds the kde-frameworks-5 content snap. It contains all of Qt and all (not-deprecated) KDE frameworks along with Plasma integration rigging.

Advantages:

Application snap is super small

You don't need to care of setting up the execution environment

Integration and international improvements are all in one place (shared environment setup etc)

Generally speaking when using the KF5 content snap SDK you can get access to KDE neon's Qt and Frameworks without having to actually add the deb sources.

Disadvantages:

Up-front "cost" of a single application may be higher. e.g. if the application only uses QtCore, the content snap will still bring in all of Qt and all of KF5 through the content snap. It's like a shared library, the more it is used the smaller the cost per-user.

Somewhat harder to build and test because of added complexity. Also managing deb build dependencies in addition to content snap SDKs is problematic TBD link to forum post

Unrelated changes in the content snaps may impair your snap

Since this type was introduced a while after snap initially came into being you still can feel rough edges when working with content snaps.

Execution Environment and Launchers

When binaries inside snaps get executed they only get a super minimal environment set up by snapd. The snap itself needs to take care of most of the higher level spin up of the environment.

Inside a confined snap the / will be the core snap, while the actual snap will be in SNAP=/snap/name/rev/.... As a result for example icons, which usually would be expected in $XDG_DATA_DIRS/icons meaning /usr/share/icons, will need to actually be looked for in $SNAP/usr/share/icons. The same applies to pretty much all XDG_* variables, LD_LIBRARY_PATH, various QT_* variables and so on and so forth.

Simply put: a snap's tree is not "merged" with the core's tree, rather it is "mounted" inside the core tree under $SNAP and so each snap needs to set up an environment which redirects or adds $SNAP to all lookup locations you can possibly imagine. As general assumptions about where things are on a Linux system no longer hold true. One the one hand that technically allows you to create a snap which entirely does away with the FHS, on the other it means someone needs to actually mangle the environment so files may be located properly.

That's why most, if not all, desktop application snaps will need a launch helper. The launcher will set up all the general purpose path variables so they point to $SNAP. A standard desktop launcher implementation is available here https://github.com/ubuntu/snapcraft-desktop-helpers. Obviously you can also write your own, but since there are lots of things to consider, even for simple applications, it's probably not a good idea to do so.

You can have a look at the standard environment by running

snap install hello-world
snap run --shell hello-world
env

This will drop you on a minimal shell inside the confined snap, where you can have a look around to see what the snap sees.

A Snap from Scratch

Standalone

We'll create a snap bundle from scratch using LXD, the KDE neon repositories, and will also look at how to make use of the KDE Frameworks 5 content snap. Using LXD and managing the environment manually allows us to use the KDE neon repositories, it does however also mean that we need to take care of more things ourselves. It also means that snapcraft will need to be run with --destructive-mode to instruct it that it may install dependencies and the like into the actual system.

To follow along you'll need a working LXD setup. A KDE neon VM would do as well. Docker however is pretty unsuitable as we need a working systemd, which is hard to get with docker. To get started with LXD, you need to run lxd init on most distributions.

Now we can start writing our snapcraft.yaml. You can either install a command line editor and write it inside the container or write it on your system and "upload" it to the container with the command sudo lxc file push --recursive snapcraft.yaml mycontainer/workspace/. Let's start with the absolutely bare minimum.

We've defined (not very good) metadata for the snap and a single part to build. Attempting to build this using snapcraft --destructive-mode will however result in an error similar to

CMake Error at CMakeLists.txt:1 (project):
No CMAKE_CXX_COMPILER could be found.

We haven't installed any of our build dependencies. An easy fix. We'll simply add build-packages to our part. In this case the compiler is missing and it's usually best pulled in via the package "build-essential". You'd continue building your build-packages list until the software starts building. Fortunately I already know all the stuff that is needed so we can move ahead.

Tip

For software which is packaged through KDE neon (which is just about everything KDE) you can get a good list to start with by looking at the debian/control file of the Neon/release branch of the packaging repository at https://packaging.neon.kde.org

The part needs editing with build-packges specified before we can do another snapcraft run:

We can build the software fine, but snapcraft is concerned that we haven't "snapped" all the necessary dependencies. The list it prints is by no means exhaustive, it's only things snapcraft can easily detect, such as missing shared libraries. Getting the necessary dependencies on board is a bit tricky. The easiest way would be to simply take the list of build-packages and use the exact same list as stage-packages. Stage packages (and their dependencies) get put into the final snap. So, by using the build-packages also as stage-packages we'd put a whole bunch of buildtime-only stuff into our final snap unless we explicitly exclude files from getting primed. Since that is somewhat unreliable and probably not particularly advisable unless you have a firm grasp of all concepts involved, we'll opt to do it the other way around and only stage packages we know are need. So from the list of build-packages packages we'll simply look at their dependencies and try to infer which of their dependencies we need (if any).

build-essential we'll leave out entirely, as the name suggests it only contains buildtime stuff such as make and gcc

extra-cmake-modules similarly is only useful at build time as it contains cmake extensions

libkf5widgetsaddons-dev is a dev package of a library and thus actually needed at runtime.

We can ignore qtbase5, its another dev package. libkf5widgetsaddons-doc is library documentation which we also do not need. libkf5widgetsaddons5 is the actual library and we'll definitely want it staged.
We'll continue this review for all build-packages:

libqt5svg5-dev: libray is libqt5svg5

libkf5parts-dev: library is libkf5parts5

libkf5doctools-dev: buildtime only, builds documentation

gettext: buildtime only, builds localization

docbook-xml, docbook-xsl: buildtime only, build documentation

libkf5crash-dev: library is libkf5crash5

We now have a list of packages that need staging and can update our part accordingly:

Snapcrafting our refined data should now create a snap without any additional warnings. This should be good enough to now. You can pull the snap out of the container with sudo lxc file pull mycontainer/workspace/kmplot_0_amd64.snap . and install it into with snap install --force-dangerous kmplot_0_amd64.snap and try to run it snap run kmplot. Unsuccessfully...

error: cannot find app "kmplot" in "kmplot"

Before snapd can run an application the snap first needs to declare one. Let's add one:

Plugs are a bit out of scope, for more information on them refer to the upstream snapcraft documentation. Snapcrafting, installing and running the new snap will unfortunately still result in problems:

kmplot(4481)/(qt.qpa.plugin) unknown: Could not find the Qt platform plugin "xcb" in ""
kmplot(4481)/(default) unknown: This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this proble

This is where the environment helpers come in. Qt attempts to find the xcb plugin in /usr/... but actually needs to look in $SNAP/usr/... Easiest solution is using the kf5-launch helper. We need to define a new part for it. It doesn't have any build system so we simply use the 'dump' plugin which copies the source tree verbatim into the snap. One caveat is that we need to skip the kf5 directory, otherwise the launcher thinks we want to use it with the KF5 content snap. To skip directories or files in one of the stage or prime lists simply prefix it with a minus character:

Many of our applications will expose themselves on the dbus session bus. Doing so does require extra permissions though. The snap needs a dbus slot. A dbus slot allows us to claim a well-known name on the session bus, meaning snapd will allow us to register a service on it. In our case the name is org.kde.kmplot and snapd will be smart enough to figure out that this name also entails our per-process variants org.kde.kmplot-$PID. To define a slot we'll need a completely new section in our snapcraft.yaml:

1 slots:2 session-dbus-interface:# semi-arbitrary name3 interface:dbus# type of the interface; this allows snapd to restrict/allow what is necessary4 name:org.kde.kmplot5 bus:session

With this the application should now be actually starting. Alas, it looks terrible! We haven't yet added any styles or icons! Let's solve this. We can either use a new part of extend our kmplot. For easier reading we'll use a new part, in practice that is not really necessary though.

1 parts:2 [...]3 plasma-integration:4 plugin:nil5 stage-packages:[plasma-integration]# this also pulls in all of breeze, so it forms a complete theme set

Snapcraft and install and the application should look like a piece of art! In fact, it's almost ready for production now. We are still missing a bunch of metadata.

Desktop file (so snapd can shove us into the host's XDG menu)

Icon file for snap store

Appstream association, so discover can find the snap

Since we can derive all of this from appstream data we'll start by extending our kmplot part to parse the appstream file by setting the 'parse-info' attribute:

And lastly we can drop our summary and description fields in favor of adopting the information from the parsed appstream data of our kmplot part. The entire refined snapcraft.yaml should now look something like this:

Before you run snapcraft this time, make sure to clean up everything rm -r prime stage parts. Appstream extraction strings across multiple stages, so you'll need a fairly clean build to actually pick up the data. After installation you should find kmplot listed in your menu and having an icon.

This snap is now in a very good shape and could be uploaded to the snap store for either public testing or actual release.

Using a Content Snap

You should read through the standalone example first. Content snaps build on how standalone snaps are built and much of the same concepts are involved, so much so that understanding standalone snap building is almost a prerequisite.

Now that we have a lovely kmplot snap we can try to optimize it. While the standalone snap is fully functional and perfect as it is, it's also very large. Size may vary over time but at the time of writing it's about 115 MiB. That is fairly excessive, considering how tiny the application really is.

As mentioned earlier a good way to bring down the footprint of an application is to use the KF5 content snap. It allows sharing the entire KF5/Qt/Mesa stack across multiple snaps. Ideally this comes at next to no extra cost.

Let's start converting the snapcraft.yaml. We start with what we had for our standalone snap. The first change is to move away from our manual build-packages and stage-packages list. Content snaps may provide their own SDKs called build-snaps. They will generally include just about everything needed to build a snap on top of the content snap. What exactly is included is entirely up to the build-snap authors. In KF5's case Make and GCC are not included, so we'll want to still pull them in via
build-packages. But since we only have KF5 and Qt5 dependencies in our stage-packages list we can probably entirely get rid of it. Same goes for the plasma-integration part since the content snap comes with its own runtime theming. Lastly, as previously mentioned, the KF5 launcher we use needs to have the kf5/ place holder directory available, so we'll also update the
env part accordingly. To make the changes easier to spot they have a change comment after them:

This will pass snapcraft, but once again not be functional. The building itself largely works out of the box because of additional magic in snapcraft itself. When setting a build-snap it will automatically inject the build-snap's paths into CMAKE_FIND_ROOT_PATH which allows all cmake find_* commands to locate assets in the snap's in addition to the system paths. This ideally means you can mix additional dependencies in via the build-packages. In practice this may be more complicated because you then may have two sources provide the same library or cmake package. This can become a problem, so watch your cmake output carefully when using build-snaps.

$ snap run kmplot
You need to connect this snap to the kde-frameworks-5-core18 snap

The snap is again broken because we haven't actually added anything to indicate what kind of content snap we want to use. We only use the build-snap thus far. Unfortunately using the content snap is a bit more lines of yaml. Similarly to how we earlier defined a slot for dbus we'll now need to define a plug for the KF5 content snap. Conceptually the way this works is that the content snap (e.g. the KF5 one) defines a 'content' slot, while we define a 'content' plug that uses the specific slot defined by the content snap, and snapd will then auto connect a matching combination of the slot and plug. The plug definition is fairly boilerplate and doesn't really need changing, ever. On top of this we'll also need to mark our kmplot app as wanting to use the plug, we'll simply add it to the already existing plugs list there:

snapcraft & install and we should now have a working ... Or maybe we don't :)

You'll encounter the same error when running, except this time the commands it tells you to run will actually work. The reason for this is that automatic connection of some interfaces (among them the content type) is not by default enabled. Content snaps auto connection works when the application snap and the content snap are from the same publisher though.

Simply put: when your snap is offered by the KDE account on the snap store it will auto connect, when sideloading/installing manual builds it won't auto-connect.

So, run snap connect kmplot:kde-frameworks-5-plug kde-frameworks-5-core18:kde-frameworks-5-core18-slot and the application should start working.

At this point you may also note that while the old snap was more than 100 MiB in size, the new one isn't even 1!

Glossary

'app: In the context of snapcraft/snapd this is the (portable) description of an 'executable' exposed to the outside (i.e. something snapd knows how to run).

parts: In the context of snapcraft a part refers to one build entity. They describe where to get the source of the entity, how to build it, how to stage it into the final snap and which other parts are a dependency and need to be built first. A part is much like a "makefile" target.

interfaces: A way for a snap to talk to the outside world (or another snaps). Split into slots and plugs. Each of which has their own security permissions as a client may need to be able to do different things from a server. https://docs.snapcraft.io/interface-management

slot: The provider part of an interface. e.g. a kwin snap might have a wayland-client slot which exposes a way for clients to talk to kwin.

plug: The client part of an interface. e.g. an application may plug into the wayland-client slot of kwin to talk to it.

Core: A special snap containing the core aspects of any Linux OS (libc/libpthread/...). All snaps depend on exactly one core which provides the snap's understanding of what will be in "/" from the snap's POV. The core does not include a kernel! Kernels may be snaps.

Content Snap: Special kind of snap that implements the "content" interface. It's kind of like a shared dependency between snaps allowing one snap to be bound into the scope of another snap. For example the KF5 content snap may be used to share all of KF5 across multiple snaps.

Build Snap: Also a special kind of snap, it's the build-time variant of the Content Snap and contains header files etc. necessary to build against a Content Snap.

stage, staging: As part of snapcrafting parts get "staged". This kind of means the same as make install, but it's actually a separate step after make install. For the process of staging, snapcraft will copy all files created by make install into a stage directory. You may also exclude certain files or reorganize the files (e.g. rename, or move to different directory). The stage is available for parts ordered after the current one, meaning that they for example can link against a newly built library.

prime, priming: Is similar to staging but happens once all parts are built and staged. Priming is the process by which the snap tree is actually constructed. Priming, like staging, allows for excluding files (e.g. dev headers may be staged so other parts can build using them but later excluded from priming and thus left out of the final bundle).