Zero to Snap – Rev up your packaging

by Alan Pope on 24 January 2019

Software developers often have enough to worry about. Their focus is creating the best application they can, often without having to consider packaging.

We recently discovered Akira, which illustrates this well. The small team of developers are hard at work on the application, so software packaging isn’t a high priority at the moment.

Akira UI Mock-up

Akira is a fledgling UI & UX design toolkit for Linux. The developers are running a crowdfunding campaign to finance further development of the application.

We’re not specifically endorsing Akira, or recommending you back their campaign. We figured this was a good example to illustrate packaging software for a small team.

If the campaign is successful, they’ll be keen to get fast feedback from intrepid users who are willing to test out bleeding-edge builds.

This sounds like a job for snapcraft!

I created a snap of Akira and thought I’d use this opportunity to explain the process I went through. Developers looking to package their application might learn from the steps I took, and the resulting snapcraft.yaml.

The snap store has a concept of channels. The edge channel is typically hooked directly up to a git repo, delivering daily or per-commit builds of software to the store. Adventurous users can accept this risk and install software from the edge channel, knowing the latest build may sometimes contain bugs.

Start at the beginning

I started by forking the upstream project on GitHub, and made a branch to work in.

After a few iterations I had a successful build of the application and submitted a pull request to the upstream developers. Note though, that Akira is very early in development, so don’t expect a finished product if you follow along and build this particular snap yourself. It’s still a good learning exercise though!

I’ve put links to documentation through this post so you can find out more about the topics covered. You can also join the community over on the snapcraft forum, where a friendly group of snapcraft developers and users hang out.

What’s in a name

Every snap needs some basic metadata. Here we set the snap name as it will be registered in the store, a short summary and description. The description may contain multiple lines.

It will be the text which appears in the snap store, and in the output of the snap info command. It can be modified and embellished later, so I’ve just used a simple description to start with. The icon setting points to the existing icon file in the project source tree.

name: akira
summary: Akira
description: |
 Native Linux App for UI and UX Design built in Vala and Gtk
icon: data/assets/icons/com.github.alecaddd.akira.png

Version therapy

Snapcraft has multiple ways to set the version number of a project at build time. It can be hard coded with version: x where “x” is a human-readable string. Alternatively version: git will determine the version automatically using Git. version-script: is more flexible, in that you can run any arbitrary command to determine the version string. For example, you could cat or grep the version string from a file in the source tree.

I used adopt-info which tells snapcraft to get the version string from one of the parts via a snapcraftctl set-version command.  I just had to nominate the part that was going to be driving the version string for this snap.

adopt-info: akira

Heavy on the base

The base determines the version of the core snap used when Akira is being run. This dictates the version of libc and some other low-level fundamental libraries which we can consider a slim runtime. The core18 base can be considered to map to Ubuntu 18.04. Previously the default core base mapped to Ubuntu 16.04, and can still be used.

base: core18

Living in a box

Strict confinement provides the snap with a restricted runtime environment. No need to change this default.

confinement: strict

Spare parts

Parts are typically where the bulk of work is done constructing a snap. A simple command-line snap may contain just one part, the application itself. Graphical applications require more interdependent components to be built will often need a bit more work.

In the case of Akira – a GNOME application – I needed separate parts for Akira itself, an up to date build of the granite library, a bundle of GNOME desktop libraries, and a set of scripts to successfully launch Akira on any desktop environment, GPU and Linux distribution.

parts:
 akira:
    after: [granite, desktop-gnome-platform]

The after line refers to the additional parts that are listed separately from this part. Each of those parts could also have an after stanza to fulfil their build requirements, but in this case they didn’t need them.

    plugin: meson
    meson-parameters:
     - --prefix=/usr
    source: .

Akira is written in Vala, uses meson to build, and requires a few additional libraries. All of these are listed as build-packages (below). We use the meson plugin and specify a prefix as a meson build parameter. As the snapcraft.yaml is intended to live in the upstream tree, the source line merely points to the current directory, but the source code could be elsewhere, in a git, bzr or mercurial repo, for example.

    override-build: |
     snapcraftctl build
     sed -i 's|Icon=com.github.alecaddd.akira|Icon=${SNAP}/data/assets/icons/com.github.alecaddd.akira.png|' ${SNAPCRAFT_PART_INSTALL}/usr/share/applications/com.github.alecaddd.akira.desktop

The override-build feature of snapcraft allows us to modify the default build process. We can use this to inject additional commands, or tweak the compile process to override standard options. Here an additional step is added post-build. When snapped the application icon is located in a different directory from the standard most Linux desktop expect. This simply modifies the .desktop file to adjust the path to the desktop icon.

The build-packages stanza details the packages required at build time to compile this part. This list was taken from the upstream build documentation.

    build-packages:
     - libgtk-3-dev
     - valac
     - libgranite-dev
     - libjson-glib-dev
     - libgudev-1.0-dev
     - libevdev-dev
     - libgtksourceview-3.0-dev
     - libxml2-dev
     - libglib2.0-dev

Voyage of discovery

Often when making snaps, time can be spent iterating over a build, adding missing libraries. This can be time consuming, so I made a little script to reduce those iterations. I wrote a script called lddtostage  that automates the discovery of required libraries and prints them out in a simple format that can be pasted into the snapcraft.yaml at the stage-packages stanza.

The lddtostage script requires an Akira binary to run ldd against. If there was already an executable available, I could have used that to base my stage-packages on. However, I only had the source tree of Akira, so instead I built the snap once with no stage-packages specified to get a binary I could interrogate.

Once built and installed, I ran lddtostage /snap/akira/current/usr/bin/com.github.alecaddd.akira to produce the stage-packages list you see below. I removed libc6 from the list as that package is already included in the core mentioned above.

    stage-packages:
     - libdatrie1
     - libdbus-1-3
     - libepoxy0
     - libexpat1
     - libffi6
     - libgcrypt20
     - libgee-0.8-2
     - libgpg-error0
     - libgraphite2-3
     - libharfbuzz0b
     - liblz4-1
     - liblzma5
     - libmount1
     - libpcre3
     - libpixman-1-0
     - libpng16-16
     - libthai0
     - libwayland-client0
     - libwayland-cursor0
     - libwayland-egl1-mesa
     - libx11-6
     - libxau6
     - libxcb1
     - libxcb-render0
     - libxcb-shm0
     - libxcomposite1
     - libxcursor1
     - libxdamage1
     - libxdmcp6
     - libxext6
     - libxfixes3
     - libxi6
     - libxinerama1
     - libxkbcommon0
     - libxrender1
     - zlib1g

I also removed the following stage packages because they’re also staged as part of the gnome-3-28-1804 snap covered in the “Slots and Plugs” section below. Staging these libraries is unnecessary and will cause the snap to be approximately 30MB larger if we do.

      - libatk1.0-0
     - libatk-bridge2.0-0
     - libatspi2.0-0
     - libblkid1
     - libbsd0
     - libcairo2
     - libcairo-gobject2
     - libfontconfig1
     - libfreetype6
     - libgdk-pixbuf2.0-0
     - libglib2.0-0
     - libgtk-3-0
     - libpango-1.0-0
     - libpangocairo-1.0-0
     - libpangoft2-1.0-0
     - libselinux1
     - libsystemd0
     - libuuid1
     - libxrandr2

The second required part is granite, a library used by Akira. This will be built first, and the akira part will build second due to the after option in the akira part. Granite uses the cmake build system, also available as a snapcraft plugin. I pointed the source at the upstream tarball, and provided the necessary build-packages needed to compile the library.

The configflags parameter for the CMake plugin enabled me to pass some compilation flags into the build process. I discovered which parameters to pass from another GTK application whose snapcraft.yaml was available on github. Typically the developer of the library will have documentation on supported flags though.

  granite:
    plugin: cmake
    source: https://github.com/elementary/granite/archive/5.2.2.tar.gz
    source-type: tar
    configflags: [-DCMAKE_BUILD_TYPE=Release, -DCMAKE_INSTALL_PREFIX=/usr, -DCMAKE_INSTALL_LIBDIR=/usr/lib]
    build-packages:
       - build-essential
       - libgee-0.8-dev
       - libgirepository1.0-dev
       - libgtk-3-dev
       - cmake
       - gobject-introspection

A donsey of GNOMEs

The next part to define is desktop-gnome-platform to pull in helper scripts and common libraries needed by most GTK applications. The snap needs to setup environment variables and directories on first launch. Rather than re-implement those myself in a custom launcher script, I’m using an existing part maintained in github by the Ubuntu Desktop team. This is a handy re-use of components to save us some work, and ensure consistency across GTK applications.  This part includes the desktop-launch script you’ll see mentioned in the next section, below.

  desktop-gnome-platform:
    source: https://github.com/ubuntu/snapcraft-desktop-helpers.git
    source-subdir: gtk
    plugin: make
    make-parameters: ["FLAVOR=gtk3"]
    build-packages:
     - build-essential
     - libgtk-3-dev
    override-build: |
     snapcraftctl build
     mkdir -pv $SNAPCRAFT_PART_INSTALL/gnome-platform

These are all the the parts required to build Akira. At this point, my snapcraft.yaml is complete enough that it should successfully build the application and incorporate the other parts into a snap. However, the application won’t actually run yet. There are still a few more stanzas to add.

Getting exposure

Snaps typically contain libraries and binaries. Many of the binaries will only be used internally by the application, and are not run directly by the user. Snaps only expose the binaries listed in an apps stanza to the host computer. These can be long-running services, simple one-shot command-line utilities, or full graphical applications. For each binary that needs to be exposed to the user, we have to specify an entry.

apps:
 akira:
    command: desktop-launch $SNAP/usr/bin/com.github.alecaddd.akira
    plugs:
     - desktop
     - desktop-legacy
     - opengl
     - x11
    desktop: usr/share/applications/com.github.alecaddd.akira.desktop
    environment:
     GSETTINGS_SCHEMA_DIR: $SNAP/share/glib-2.0/schemas
    slots: [ dbus-akira ]

The command line is what actually runs when the user executes akira on the command line, or clicks the icon in their menu launcher. Please note we’re running the binary built by the akira part above, but prefixed with two important pieces. The desktop-launch is the helper script from the desktop-gnome-platform part mentioned earlier, which sets up the environment for a typical GTK application. The $SNAP environment variable points to the location where the snap is installed. In this case that would be /snap/akira/current.

Akira is built as a strictly confined snap, which by default will not have access to the display. Snaps use a concept of Interfaces to connect applications inside snaps to resources provided by other snaps. Interfaces comprise plugs and slots. The plugs listed here connect to slots provided by the core18 snap.

In the section above are listed the minimum necessary plugs to enable the snap to paint a GTK window. If Akira grew the capability to access network resources, we might add the network plug here.

The .desktop file modified in the akira part is referenced here. This ensures that once installed, Akira will show up in the menus and launchers of common desktop environments.

The environment section enables us to add or override environment variables set at runtime. In this case, we need to point to where the GSettings schemas are located inside the snap.

The dbus-akira slot is mentioned here, but defined in the next section.

Slots and Plugs

D-Bus is a messaging system for applications. Modern applications commonly need to register themselves on the D-Bus “session bus”. Strictly confined applications are unable to do this by default. The following section (combined with the line in the apps stanza above) will allow the application to register on the session bus if needed. I set the slot name to an appropriate setting of dbus-akira and the name to whatever the application tries to register on the session bus as.

slots:
 dbus-akira:
    interface: dbus
    bus: session
    name: com.github.alecaddd.akira

The next section adds some plugs that will connect to slots available in the gnome-3-28-1804 snap. This enables us to save space in the snap, having theme, icons and sound components stored elsewhere. When the Akira snap is installed from the store, the gnome-3-28-1804 snap will also be installed, and the plugs here will be automatically connected to the slots in that snap.

plugs:
 gnome-3-28-1804:
    interface: content
    target: $SNAP/gnome-platform
    default-provider: gnome-3-28-1804
 gtk-3-themes:
    interface: content
    target: $SNAP/data-dir/themes
    default-provider: gtk-common-themes
 icon-themes:
    interface: content
    target: $SNAP/data-dir/icons
    default-provider: gtk-common-themes
 sound-themes:
    interface: content
    target: $SNAP/data-dir/sounds
    default-provider: gtk-common-themes

Building mighty oaks

With all these in one yaml, the snap can be built with snapcraft. I built the Akira snap on my KDE Neon 18.04 laptop, but you could use an Ubuntu VM or build in a cloud service such as Travis or CircleCI.

Early build of Akira

On a Linux system with snap support enabled, install snapcraft and multipass, and you have what you need to build Akira, or indeed any other snap.

Next steps

This post has focused on snapping Akira and used specific build tools such as meson and cmake. The broad steps could be re-used to snap other GTK based applications and make them available via the Snap Store. So if there’s a GNOME application you love, and would like to see snapped, take these notes and have a go!

If you have any questions or comments about this post, or need help snapping the next big app, do join us on the snapcraft forums.

Photo by Francisco Requena on Unsplash

Newsletter Signup

Related posts

We wish you RISC-V holidays!

There are three types of computer users: the end user, the system administrator, and the involuntary system administrator. As it happens, everyone has found themselves in the last group at some point or another; you sit down to perform a task relevant to your needs or duties, but suddenly the machine does not work as […]

Creating Snaps on Ubuntu Touch

This article was written in collaboration with Alfred E. Neumayer of the UBports Project. Tablets, phones and current technology’s capabilities are phenomenal. Who would have thought a thin, light, barely 10 inch device would provide all the power necessary to run Virtual Machines, wherever one desires while powered on battery? That a sma […]

Managing software in complex network environments: the Snap Store Proxy

As enterprises grapple with the evolving landscape of security threats, the need to safeguard internal networks from the broader internet is increasingly important. In environments with restricted internet access, it can be difficult to manage software updates in an easy, reliable way. When managing devices in the field, change management […]