Building a Rust snap by Example
by Alan Pope on 13 December 2019
There’s plenty of official documentation which details how to create snaps. We live in the copy/paste generation though. So lets walk through a real-world example you can use as a template, or selectively yoink sections from.
This is the first in a series of posts which break down how to build snaps based on published works in the Snap Store. This week we’re looking at “System Syzygy” , a graphical puzzle game written using Rust and SDL2.
Let’s start with the easy part, the human-readable bit. Here we set out the name of the snap as it would appear in the Snap Store web user interface, and on the command line with
snap info syzygy. As the summary is displayed in the command line output of
snap info it’s worth keeping it short and sweet. The description can be more meaty, covering details about the application, command line options, keyboard shortcuts and links to help.
Once published however, the summary and description can also be maintained via the Snap Store listing page, but this is a good starting point.
summary: System Syzygy - A narrative meta-puzzle game
System Syzygy is a puzzle game, in the style of Cliff Johnson's
The Fool's Errand and 3 in Three, and of Andrew Plotkin's System's
The version number can often be seen either with a
version-script: line. However we have used
adopt-info to inform snapcraft that the version will be set by a part, further down the yaml. The part name is specified. We’ll come back to this later.
The base indicates which runtime this snap should be run against. When the syzygy snap is installed on a system, the core18 base snap will be automatically installed too. Essentially this determines the version of libc the application was built against. The core18 base snap is built from Ubuntu 18.04 (LTS) and as such, we should build this snap in a VM or container running the same release.
Other (older) bases are available and in the future newer bases will be made, but today, this is an appropriate base to build with.
Both the snapcraft build service and launchpad have the capability to build snaps for numerous architectures. However, not all applications may successfully build or run on all of them. Sometimes the dependencies or build tools aren’t available for every CPU architecture, or perhaps the tool-chain is not supported on anything but mainstream architectures.
In the case of System Syzygy, it only builds for 64-bit and 32-bit Intel CPU architectures. Specifying the supported architectures here means the build systems won’t waste time attempting to compile the game on CPUs we know it won’t succeed on.
- build-on: [amd64,i386]
System Syzygy is a simple, non-networked game. It needs access to take input from the keyboard and mouse, display things on the screen, and play back audio. As such it can be strictly confined.
Grade is an indicator of the quality of this snap. Initially we might set this to
devel while it’s in development. However, in order to be published in the stable or candidate channels in the store, we should set this to
stable. Only applications with a stable grade are permitted in the stable and candidate channels.
Parts are the meat of the snap. Here’s where we outline what actually goes inside the package. As System Syzygy is a game written in Rust, and is hosted externally on github, we can list the plugin and the url to the source repository here.
Many applications are not written with relocatability in mind. The override-pull section may contain anything we might want to do, to manipulate the source code before building it.
System Syzygy makes an assumption about where to load some of the required libraries or assets. We can override this by simply patching the line in the source such that it now points into the directory the snap will be installed to. These are shell commands in a list.
The snapcraftctl pull statement instructs snapcraft to do whatever is required to obtain the source as defined by the plugin and source lines above. Then we patch the source files as necessary.
override-pull: | snapcraftctl pull sed -i 's|usr/lib/syzygy|/snap/syzygy/current|' src/gui/loader/path_linux.c
Most applications use standard build tools and common patterns for compiling and installing files. However sometimes they miss out steps we assume as part of a build. The developer may omit installation steps for example, assuming the user will simply run the application directly from the build folder.
In the case of System Syzygy, we need to copy the game assets (fonts, sprites and background images) into the right place. We use the build-time variable
SNAPCRAFT_PART_INSTALL as our target. Later in the snapcraft lifecycle the contents of this folder will be assimilated into the final snap.
override-build: | snapcraftctl build mkdir $SNAPCRAFT_PART_INSTALL/data cp -a data/fonts data/sprites data/backgrounds $SNAPCRAFT_PART_INSTALL/data cp -a target/release/syzygy $SNAPCRAFT_PART_INSTALL/bin snapcraftctl set-version $(git -C ../src describe --tags | sed 's/v//')
Finally we use git to extract the version number via git and assign that to the snap with the
snapcraftctl statement. This bubbles up via the
adopt-info line above to become the version number
System Syzygy has a very small set of build dependencies outside those specified in the rust
Cargo.toml. We list the packages which fulfil those build dependencies in the
build-packages section of the part. These will be installed by snapcraft before the build starts.
build-packages: - libsdl2-dev
Once built, the game leverages a few libraries to draw on the screen and play audio. If we didn’t specify these, the game would not work even if the libraries existed on the host OS. The snap is strictly confined, so cannot see any external libraries, so we list them here to bundle them with the game.
We can discover which packages to add by running snapcraft without specifying any at all. Snapcraft will introspect the binaries shipped in the snap, and list a best-guess array of required packages.
stage-packages: - libglu1-mesa - libsdl2-2.0-0 - libpulse0
The apps stanza is where we setup the environment and expose the binary inside the snap to the outside world. This includes ensuring the binary can find the GL drivers required to paint the game window, and extend the library search path to include the pulseaudio libraries for audio playback.
In addition, the game has an internal environment variable which should point to the game data folder. We use the SNAP runtime environment variable to construct the correct path to the game files configured earlier.
Finally we specify the required plugs to enable the game to draw on the screen, play audio, access the GPU via opengl, and suppress the screensaver.
Building and publishing snaps of applications written in Rust is pretty straightforward. The example above highlights how we can be flexible if the build system requires tweaking, or where an application needs minor patching. A non-graphical Rust app would be simpler than this example by virtue of not needing the SDL, GL and pulse libraries and environment configuration.
Building and distributing a game written in any language is mostly similar to what’s outlined above. Only the part definition changes, the environment and plugs configuration will be mostly common.
We welcome new games and applications in the Snap Store. The developers of snapd, snapcraft and the Snap Store hang out over on the snapcraft forum. Join us there if you have any questions or comments about this article or need assistance building a new snap.