Getting Python and wxPython apps into the Ubuntu app store

Have you ever dreamt of getting your own apps in the Ubuntu app store? Here’s how.

I wanted to get my UML tool for Python, Pynsource, into the Ubuntu/Snapcraft app store for a long time. This is the story of how I made that happen.

It certainly wasn’t easy. It took me a week of work, struggling with the concepts, documentation and various bugs in the tooling to finally get my app listed.

Hopefully I can save you some time getting your Python command line app or wxPython GUI app published.

This article’s Contents

  • Overview
  • Part I — How to build a simple Python (non-GUI) snap
  • Part II — Build a wxPython GUI app, understanding snapcraft.yaml
  • Part III — Debugging snaps
  • Part IV — Troubleshooting and Snapcraft Bugs

Overview

What we are aiming for

The aim is to get our Python app listed in the Ubuntu app store. This app can be a CLI (command line interface) non-GUI app that you use from the terminal, or a full blown GUI app.

Either way, you get you own app store page with screenshots and an install button. For example:

My Pynsource (UML for Python) Linux app store page on Snapcraft.io

The resulting app store page for Pynsource is here. Any such published app should also appear listed in the official Ubuntu Software program, which you launch from inside Linux. I could find my app by searching by its app name or by other relevant keywords, in this case I typed ‘UML’ and saw:

Pynsource now appears in the official Ubuntu app store

I didn’t have to get approval for my app to appear — I just created a Snapcraft developer account and pushed up my snap. Fantastic!

The installation experience for end users

Users view a page with juicy screenshots and descriptions, and that all important install button :-) How do they get to that page? You can give them the url to your app’s page on the Snapcraft.io store. Or they can search for your app in the Snapcraft store, or in the “Ubuntu Software” app inside Linux.

Clicking on the Snapcraft web page install button will launch the “Ubuntu Software” app installer inside Linux.

You can also install app store apps using the command line. For example to install Pynsource you would type snap install pynsource. To list all apps installed in your Linux system, type snap list.

Does your Application need approval?

You don’t usually need to get approval to push your app to the Ubuntu app store if it has the default ‘strict’ permissions, which make it safe. If your app uses the more unrestricted ‘classic’ permissions then it needs to be approved. The final permission mode is unrestricted ‘devmode’ which is for developer use — more information on the three snap confinement permissions here.

Getting your app featured in the curated lists and categories you see in the Ubuntu Software main page is a little unclear. Certainly you can set your app’s category on its Snapcraft.io page e.g. “Development”. Whilst this correctly affects searches on Snapcraft.io it does not affect how your app is listed in the categories of the “Ubuntu Software” app inside Linux 18.04 because the latter uses the “Developer” category —a bit of a mismatch— this post talks about this issue.

What’s a snap anyway?

Snapcraft and the snap store are Ubuntu’s new app store technologies. Snaps work on other Linux distros, too, which is exciting. There are a couple of other technologies that do the same thing e.g. Flatpack and AppImage, but I won’t be covering those.

A snap is an app — its your program and all its dependencies rolled into a kind of zip file

You shouldn’t need to change your source code in any way to create a .snap file. A .snap file is a compressed mini filesystem which contains your app files and all its dependencies.

The difference between the traditional Ubuntu app store app and a snap app is that snaps are more resilient and will work across releases and also across Linux distros — because they bundle all dependencies inside themselves. The price you pay is that they tend to be bigger in size than regular apps.

Snaps are distro independent because they are built and run against a common runtime, or “core”, and bundle the necessary libraries required to run everywhere. They do not run in a virtual machine. But you do need a virtual machine to build them (see later).

The ultimate goal for a developer is to build a .snap which is the file that you push up to the app store (or send people), you type snapcraft to build the snap — assuming you have created a snapcraft.yamlfile.

The overall process is:

  • install the snapcraft tool
  • create a file called snapcraft.yaml and build your .snap file
  • reserve a name on the app store and push up your .snap file
  • edit your official snap page to add images and a description.

How popular are snaps?

According to the official Canonical statistics page for snap usage,

there are over three million snap installs per month,

with over two thousand snap apps available — as of March 2019.

All the big name apps like Libre Office, Visual Studio Code, GitKraken, PyCharm, Sublime, Postman, Slack, Skype, VLC, Handbrake, Spotify, Plex, Gimp etc. have snaps. View the Canonical/Ubuntu/Linux/Snapcraft app store here.

Updating snaps

The snapd deamon process updates apps for you, or you can explicitly run snap refresh to get the latest version of all your snaps.

Part I — How to build a simple Python (non-GUI) snap

This is a high level overview of building a snap.

The snap command and its update demon snapd should already live in recent versions of Ubuntu. If you are not using Ubuntu, see this installation documentation which covers other Linux distros and Mac. For example to list your existing snaps (Linux apps that have been installed from the app store) type snap list.

You typically build Linux snaps on a Linux machine. You can build snap apps on a Mac machine, though you can’t actually run the resulting snap apps on a Mac. A snap app is a .snap file which needs to be installed in Linux before it can be run.

  1. Install snapcraft e.g. snap install snapcraft on Linux or brew install snapcraft on Mac
  2. Create a snapcraft.yaml file ← this is the hard bit ;-)
  3. Run the command snapcraft which creates a .snap file
  4. Install your .snap file locally sudo snap install --devmode — dangerous *.snap
  5. Run and test your snap by invoking its command name e.g. yourapp
  6. If you are happy, reserve a name on the snap store snapcraft register yourapp
  7. Push and publish your snap e.g. snapcraft push --release=edge *.snap
  8. Edit your snap app’s web page with images and a descriptions. You can also provide a description inside the snapcraft.yaml file.
  9. Let everybody know the url of your snap or alternatively, let them know the terminal command command to install your snap e.g. snap install yourapp.

General conceptual considerations

The idea is to get your Python files into a .snap file, as well as any supporting libraries. And to specify a command to launch your program.

The command snapcraft looks at the snapcraft.yaml file and launches a VM (virtual machine) containing a clean, headless Ubuntu. The VM contains some core libraries (hence the core18 directive you put into thesnapcraft.yaml file). Packages are pulled off the internet and if necessary, built inside the VM.

A snapcraft.yaml file is like a Makefile, and consist of various sections. A part is a section in the snapcraft.yaml file that builds something. There used to be an ability to refer to remote parts which are preconfigured parts (lists of install commands and package lists) which others have developed, but I believe this is no longer supported when you use the now recommended base: image approach, which are are following in this article . Plugins are extensions to the snapcraft.yaml file format that mean you can create or utilise parts that do specialised things. The only plugins I have used are the python plugin, and the dump plugin which helps you copy files into the snap. Developers can develop their own plugin (which happen to be written in Python), but this would be a rare thing to do.

There are several build related directories inside the VM like project, stage, build, prime etc. The prime directory represents the final contents of the snap, and is ultimately the most important. You need to ensure everything that will be deployed ends up in the prime directory. The prime directory even has subdirectories like prime/etc, prime/lib/, prime/lib/python3.6/site-packages, prime/usr/include, prime/usr/lib, prime/var/lib/ etc.

You can shell into a VM and check the contents of these various build directories, including prime by dropping into a bash shell after a snapcraft build with the command snapcraft --shell-after or at any time with the multipass shell YOUR_VM_NAME command. If you don’t know the name of your VM, list them with multipass list.

You can run Python inside the VM when you are shelled into it, but you won’t be able to run GUI apps, because its a headless Linux install. And running your CLI app inside the VM is not quite the same as running it as a properly installed snap app because your Python paths etc. are not the same. You can hack around with PYTHONPATH prior to launching python, to mitigate this.

A snap file is built from the exact contents of the prime directory inside the virtual machine of your snapcraft project. A .snap file is actually secretly a directory — like an .app file is on Mac OS (which on Mac, you can explore using finder’s Show Package Contents).

A .snap file is a just a compressed hierarchy of files, a filesystem if you will.

Most commands in the snapcraft.yaml implicitly refer to the prime directory inside the VM, which, as I’ve mentioned, ends up as the root of your snap filesystem inside the resulting .snap file. You can explicitly refer to the prime directory in the VM as $SNAP — this is especially useful when constructing the launch command specification, as we shall see later.

Once your .snap file is built, it’s ready to install locally, run (to test it) and deploy (to the app store). The contents of the .snap can be listed using unsquashfs -l *.snap|less which is great for seeing what files are where. Remember, this listing will exactly match the recursive contents of the virtual machine’s prime directory. Each of your snap apps will be named differently and have their own virtual machine, and their own resulting final .snap.

Python considerations

Strangely, in my opinion, the snapcraft Python plugin assumes you have a setup.py file and that your app’s functionality is installed via distutils. The official Snapcraft Python tutorial assumes this approach. If your apps don’t use a setup.py (mine typically don’t) then you need to explicitly copy your source code into the VM prime directory using asnapcraft.yaml dump plugin command (which simply copies files). I’ll be using this technique throughout this article.

You can tell snapcraft to use your existing requirements.txt as well as install Python packages more explicitly using apt install commands inside your snapcraft.yaml. These Python site packages are pulled off the internet and built inside the virtual machine, and finally copied into the VM’s prime directory, e.g. if you have requests in requirements.txtthen /root/prime/lib/python3.6/site-packages/pip/_vendor/requests gets created and ends up in the final . snap file.

The version of Python seems to depend on the base snap image you choose, which currently is core18 which means Ubuntu 18.04 which in turn means Python 3.6. You may be able to override the Python version — see more info below.

When a snap is properly installed and run by a user, the PYTHONPATH of the Python shipped in the snap correctly contains both the $SNAP directory and the Python site packages directory. Your launch command specification in the snapcraft.yamlcan add other directories within the snap to the PYTHONPATH or PATH if you need to.

Example — A simple Python (non GUI) app

Here is a simple Python program that prints a few messages, lists the contents of the current directory and makes an internet GET request. It’s a CLI app not a GUI app, thus is much simpler to build, and you install and run it in the Linux terminal. Let’s get it into the app store!

Its actually ok to put your junk test apps in the app store — they have the same importance as big important apps, so you might want to prefix your app name with something like ‘test’ to help reduce the pollution of the single snapcraft namespace.

Example test Python CLI app for the Ubuntu app store

You don’t need to change your source code in any way to create a .snap file app. Here is a snapcraft.yaml file for the above:

The list of stage-packages within the andy-py-cli part above (which is also tagged with the python plugin) are just the libraries that Python depends on. The above list should work perfectly well for Python 3.6 as of April 2019, though I had to spend a day chasing the libslang2 dependency and adding it to the list. When there is a missing package your snap won’t run, and you will have to figure out the sometimes mysterious package library name to add to the list. Usually the error message will be the major clue and together with some googling you should be able to find the name of the missing entry.

I wish snap technology could utilise what PyInstaller does — and automatically find all the needed dependencies.

Your requirements.txt for this example, should contain a single line: requests Your directory structure for this sample application is:

|-- main.py
|-- requirements.txt
`-- snap
`-- snapcraft.yaml

Make sure your Python app runs OK outside of snapcraft e.g. python3 main.py then when happy, simply run snapcraft. The .snap file will be generated. If you get errors, see the bugs and troubleshooting section later in this article. View the contents of your snap (optional) with unsquashfs -l *.snap|less.

Then install and run the snap locally with

sudo snap install --devmode --dangerous *.snap
andy-py-cli

To publish you need to reserve your app store name and push to the app store. Use your Ubuntu login.

snapcraft register andy-py-cli
snapcraft push --release=edge *.snap
Processing...|                                                                                                                                                                                                                                                                     
Ready to release!
Revision 1 of 'andy-py-cli' created.
Track Arch Channel Version Revision
latest amd64 stable - -
candidate - -
beta - -
edge 0.1 1

Now you can view your app store page which should now list this new app! And you can install your app on any Linux machine with snap install --edge andy-py-cli. If I had released to the stable channel, the --edge parameter would not have been needed.

Once I am happy to release to the stable channel, I don’t need to re-upload anything, I simply look at the Revision number of my app (see above output) which is 1 and say snapcraft release andy-py-cli 1 stable which marks this release as stable.

Go on, try it — I’ve really pushed this app into the app store and you should be able to install and run it:snap install andy-py-cli then andy-py-cli.

What a great way to distribute apps and utilities — no worrying about Python installations, versions or requirements, just a single command to install.

Github repo for this project is here. You won’t be able to publish this app yourself because I have already reserved the name andy-py-cli so change the name of your app in your yaml file and register that name instead.


Part II — Build a wxPython GUI app, understanding snapcraft.yaml

Let’s look at what’s inside the yaml file, then look at building and deploying a wxPython GUI app.

The mighty snapcraft.yaml

This is where the effort lies. This file can get complex and throw a lot of people off. It could benefit from additional documentation, for example a definitive grammar/syntax , and a complete list of possibilities listed. Yes there are documentation pages approaching this level of detail, but no definitive grammar. I have found missing documentation entries and multiple versions of some documentation pages, which can get confusing. But fear not, I’m going to explain the essential steps here.

Your snapcraft.yaml for a wxPython app needs to contain:

  • a name:
  • a version:
  • a base: directive
  • a part named after your program, which uses the plugin: python syntax
  • a part named copy-stuff (or whatever), which uses the plugin: dump syntax to copy in your Python source code.
  • a requirements: part that refers to your requirements.txt file
  • a python-packages: part which installs wxPython
  • a desktop-gtk3: part which installs all of wxPython’s dependencies — a long list which you can just copy and paste
  • an apps: part with a subsection named after your program which provides the main launch command and a reference to your .desktop file
  • a copy-desktop-icon-stuff: (or whatever), which uses the plugin: dump syntax to copy in your .desktop file and icon .png

Use the following fragments as templates and then modify it as needed.

Name your app

name: YOURAPPNAME
version: git

Instead of using the current git commit as a version, you can also use any string you want e.g. ‘v0.1’.

Add the base

The base: core18 directive means you are building using an Ubuntu 18.04 VM with some core libraries already supplied. There is a way to run snapcraft without specifying this time saving option, but I’m not covering that legacy approach in this article.

You will find examples on the web which don’t use the base directive, where builds assume a Ubuntu 16.04 system. That is old legacy snapcraft stuff, which works but is not recommended anymore.

By default core18 means running Python 3.6.7. You can also run Python 2 — we’ll specify the python 2/3 version later.

base: core18

If you want another version of Python 3, you will need to add a new `part` to the yaml file — I have yet to figure out how to do that.

Pick your Python version and source code location

Then have your app YOURAPPNAME specify which version of Python (by which I mean legacy version 2 or the current version 3) and where the source code lives:

YOURAPPNAME:
plugin: python
python-version: python3
source: ./src

The big trap for Python developers

Note, the above part will not copy any source code, even though you are specifying where your source code is. Tricky huh!?

You need your source code to be shipped in the snap (sorry, no source code protection in Snaps, sadly).

The reason the ‘python plugin’ section specified above doesn’t actually copy your source code is that it assumes that you are providing a setup.py which assumes your python project is configured to leverage distutils file copying in a setup.py.

You see, Canonical suggest that the “easiest way” to use the python plugin is to configure your python project with distutils in a setup.py. I disagree, I think

most developers do not usually set up their app’s functionality to be installed and run as a site package.

So to fix this, create an extra section (or ‘part’ in snapcraft speak), that explicitly copies (hence the use of the ‘dump’ plugin) all your source code into the snap. No need for setup.py or any other distutils shenanigans. The name copy-stuff is arbitrary, I just made it up.

copy-stuff:
plugin: dump
source: ./src

This will copy everything in the src/ directory into the $SNAP root directory, where it is needed. The files and subdirectores in src/ will end up in the $SNAP root directory. In most situations you probably simply want to specify source: .if your source code is in the root of your project.

If you want to be even more organised, you could copy your Python source code into a subdirectory under $SNAP by using the organize syntax of the dump plugin (its really a `copy` plugin) — just make sure your main launch command (specified in the snapcraft.yaml file under the apps: section, see below) targets the correct path of your Python source code’s main module e.g. main.py or whatever. Also ensure that your PYTHONPATH is set up correctly (you can set this up within the same launch command, using the environment: specification, examples of which are listed further below in this article).

Requirements.txt

To specify that you want to pip install from requirements.txt add this:

requirements:
- /root/project/requirements.txt

Note that I had to add the /root/project/ prefix because that is where the requirements file ends up inside the VM filesystem during the snap build process. Tricky, huh. The project/ directory is one of the build directories, all of which live in the VM under /root (e.g. project, stage, build, prime etc), and contains the entire directory of your project, copied into the VM.

The launch command — main entry point

Now we need to specify the ultimate command to launch your app:

apps:
pynsource:
command: desktop-launch python3 $SNAP/pynsource-gui.py
plugs: [x11, unity7, pulseaudio, home, gsettings, network]

Here I have have said that the command pynsource will run python3 on the file pynsource-gui.py — simple. The desktop launch stuff is what you need if you are running a GUI app.

The plugs keyword specifies which areas you want to have accessible to your app, e.g. networking, audio etc.

WxPython considerations

To install wxPython into your snap, you need to add the part below. You can’t simply add wxpython into requirements.txt, unfortunately. It’s a Linux limitation.

You must pick a wxPython tar url which matches your version of Python, and version of wxPython that you want. The choices are listed here. Just change the url to suit.

I’ve also added wxasync as a dependency here, python-packages section, since I use it a lot with wxPython — see my article Async/await for wxPython also published on Medium.

I suppose I could have kept the wxasync dependency in requirements.txt but I’ve found that wxasync tends to install its own dependencies, one of which is wxPython. When installing things manually on Linux, I typically run pip3 install wxasync --no-dependencies but am not sure how to do that in a requirements.txt file. I’m aware there is the pip install --no-deps -r requirements.txt but 1) not sure I want to stop all my other pip packages from installing their dependencies and 2) doubt if there is any equivalent syntax that the snapcraft.yaml would recognise. So, to be safe, I simply moved the wxasync out of requirements.txt into the python-packages section, above. Presumably, theoretically, I could list all the pip package dependencies in the python-packages section instead, rather than using requirements.txt.

And to make sure you pull in all of wxPython’s dependencies (and associated gtk3 viz. Gnome GTK bits), you need to add this part:

Update: Snapcraft is working on a simpler way to specify a gtk3 app — in the future you will only need to write extensions: [gnome].

Thank goodness, because finding the exact list of package dependencies can be a nightmare hunt.

Aside: During my research, I started with these wxPython examples which were helpful but old, Ubuntu 16 based, and did not use core18 snap technology. I finally figured out the snapcraft.yaml and created my own core18 based example to help others, for a building minimal wxPython snap.

GUI Desktop Icons

There are instructions for setting up a desktop icon in this documentation blog post. Basically you have to create a .desktop text file and a .png and follow the steps outlined.

However the only way I got the desktop icon to work was, in addition to the documentation steps, to explicitly copy the .desktop and .png files using the dump plugin.

copy-desktop-icon-stuff:
plugin: dump
source: snap/gui
organize:
pynsource.png: usr/share/applications/pynsource.png
pynsource.desktop: usr/share/applications/pynsource.desktop

The copy-desktop-icon-stuff is just a name I made up, to house the copy commands. I also had to “organize” the files, otherwise the two files end up in $SNAP not $SNAP/usr/share/applications/ (or if you want to speak in terms of virtual machine build directories instead of snapcraft.yaml path specifications: they end up in /root/prime not /root/prime/usr/share/applications/ unless you organize them).

I also had to rearrange my source code directory structure so that the snapcraft.yaml file was located in a directory snap and the .desktop and .png files were located in snap/gui.

.
|-- README.md
|-- pynsource_0+git.8095336_amd64.snap
|-- requirements.txt
|-- snap
| |-- gui
| | |-- pynsource.desktop
| | `-- pynsource.png
| `-- snapcraft.yaml
`-- src

I had previously kept my snapcraft.yaml in the root of my project, and simply had a gui subdirectory containing the .desktop and .png files. This failed to create the entries

squashfs-root/meta/gui/pynsource.png
squashfs-root/meta/gui/pynsource.desktop
squashfs-root/snap/gui/pynsource.png
squashfs-root/snap/gui/pynsource.desktop

and as such, my app’s icon was blank. My pynsource.desktop file is:

[Desktop Entry]
Name=Pynsource
GenericName=UML application
Comment=Pynsource UML reverse engineer Python source into diagrams
Exec=pynsource
Icon=${SNAP}/meta/gui/pynsource.png
Terminal=false
Type=Application
Categories=Development;
StartupNotify=true

Notice the reference to Icon=${SNAP}/meta/gui/pynsource.png in pynsource.desktop.

The way the file pynsource.png gets into the /root/prime/meta/gui/ directory seems to be done magically for you by snapcraft, assuming you have followed the steps in this documentation blog post. It’s unfortunate that additional, undocumented dump/copy commands (as listed above) are needed to copy those files into the VM's /prime/usr/share/applications/ directory.

Notice also the reference to Exec=pynsource in pynsource.desktop. This is the desktop icon launch command, which simply refers to your app name in you snapcraft.yaml.

The final GUI snapcraft.yaml

Here is the final snapcraft.yaml that we have been constructing above, for Pynsource.

For comparison, here is another example snapcraft.yaml for a simpler, one file wxPython app called rubber-band-async (which you can install in Linux via the snap store here).

Building the GUI based snap

Once you have your snapcraft.yaml just run snapcraft and wait for the .snap file to be built. This will take some time.

You may occasionally need to run snapcraft clean to clear out old build attempts. Unfortunately partial cleans via the snapcraft clean -s flag are currently not implemented for a core18 based build, which is the recommended way to use snapcraft. On my VM on a powerful iMac I was faced with one hour builds, because after a snapcraft clean the Ubuntu virtual machine needs to be re-downloaded again, and everything built from scratch. Your mileage may vary re build times.

Once built, install the snap locally with sudo snap install --devmode --dangerous *.snap then run it with name of the command e.g. pynsource.

For a GUI app, you will see warnings about Gtk-Message: Failed to load module “canberra-gtk-module” — just ignore these, apparently everyone gets these and they can be ignored. Nobody knows how to get rid of them. For a GUI app, users won’t see any such messages anyway.

Publishing the GUI snap

When pushing you can of course specify the exact name of the snap, rather than using *.snap.

snapcraft login (use ubuntu one auth)
snapcraft register YOURAPPNAME
snapcraft push --release=edge *.snap

Then view your snap in the store https://snapcraft.io/store

Pushing a stable release

You need to push with the command parameter --release=stable (or no release parameter at all, as it defaults to stable) instead of--release=edge and you also must ensure that your snapcraft.yaml contains grade: stable not grade: devel

Thus:

snapcraft push *.snap

look at the number returned e.g. 2 and specify that in your next command

snapcraft release YOURAPPNAME 2 stable

Now your app is in the appstore as a stable release. Users can choose from a dropdown whether to install a stable release or an edge release.


Part III — Debugging snaps

I don’t know of a way to debug a deployed snap, you can only really debug the build process. If you get errors when building or debugging, there are different approaches.

When building, you might want to shell inside the VM if an error happens. You can do this during the build process by specifying snapcraft --shell-after and you will be dropped inside the VM. You can look in the prime directory to see what the final snap filesystem looks like. There are other directories here, which represent other stages of the build process. You can find more info here.

Whilst you can invoke Python inside the VM — for a GUI app that’s not going to do much good, because you cannot run GTK in a headless way. But you can run plain Python. However be warned that the PYTHONPATH is not set up properly, especially with regards to pointing to the site-packages directory inside the VM. You can however work around issues like this by specifying what you want PYTHONPATH to be on the same line as invoking Python:

PYTHONPATH=/root/parts/rubber-band-async/install/lib/python3.6/site-packages/ python3 -c"import wx"

The VM is not really meant to be a place you run things, its a place where things are built.

A key insight and AHA! moment

The next bit of information really helped me get an understanding of the movement of files and the whole snap workflow. Perhaps it will help you too.

The contents of a snap file are exactly what is in the virtual machine /root/prime build directory. You can look inside your .snap with unsquashfs -l *.snap|less which is great for seeing what files are where.

Thus, in a sense, the following are equivalent:

  • $SNAP in the snapcraft.yaml file
  • /root/prime in the build VM
  • the root of the .snap file’s internal file hierachy / filesystem
  • squashfs-root/ of the .snap file’s internal file hierachy / filesystem when listing the snap contents with theunsquashfscommand
  • /snap/YOURAPPNAME/x3/ when the snap is installed on your Linux system. The x means local install, and the e.g.3 is the revision of the build, and constantly increments

When referring to $SNAP in snapcraft.yaml, you are referring to the /root/prime directory in the VM being used to build your snap file. Those /root/prime directory files are then copied into root of the filesystem inside the .snap being produced.

I think the snapcraft documentation would benefit having this “directory equivalence information” in the documentation. It’s only after working and researching snaps for several days that you figure this stuff out — at least that’s what happened to me!

Shell inside after the build is done

Whilst you can’t look inside around the snap with a bash shell, now that we know what corresponds to what, we can do the next best thing and shell into the prime directory of the associated VM:

multipass start snapcraft-pynsource
multipass shell snapcraft-pynsource
sudo bash
cd /root/prime
# look around, use less and vi and ls etc...
exit
multipass stop snapcraft-pynsource

Note I had to become root once inside, otherwise I couldn’t cd into the build result directory, viz. prime.

Remember though that usually, snapcraft --debugor snapcraft --shell-afteris a better way to get a bash shell, and drops you straight in, in the proper dir with proper permissions, but of course invoking via a snapcraft command does trigger a build — which if there is nothing to do, should be reasonably fast.

Current directory

The current directory from the point of view of a running snap is your actual host filesystem, not the secret filesystem inside the snap. This is as it should be.

If you want to access the secret filesystem inside the snap (for example to access some resource your program needs), your program can look at the environment variable SNAP and this will be set to the path you need to look in. See this article for an example of some C code doing this. For Python you would

if "SNAP" in os.environ:
snap_root_dir = os.environ["SNAP"]

You can also use this technique from one of your Python files: os.path.dirname(os.path.realpath(__file__)).

Debugging pushing up your snap

If you push as edge and then later attempt to push that same .snap file as stable, or vice versa — the push will fail as the ‘binaries do not differ’ viz. A file with this exact same content has already been uploaded. Snapcraft say:

This is “working as designed”. When you push a snap to the store, you can optionally “release” it to a channel. Once that snap is in the store you can later release it to another channel, no need to re-upload. The channel really is just a pointer. So to release to stable you’d “snapcraft release (snapname) (revision) (channel)”, e.g. “snapcraft release mysnap 3 stable”. This is instant, and doesn’t require a rebuild and re-upload.

I’ve run in to the issue a few times where I accidentally push my edge .snap with the release parameter set to stable, which rightly gets rejected (the release is rejected, not the push).

Revision 6 of 'andy-testsnap-py' created.
invalid-field: Revision 6 (strict) cannot target a stable channel (stable, grade: devel)

Attempting to correct my mistake with another push & release — this time to edge — annoyingly gets rejected because it’s already uploaded.

Why can’t the push just fail silently and move on with the release part of the command — why crash out?

To get around this, I used to make a small change in either my code or the snapcraft.yaml file (to make the snaps binaries different), rebuild my snap, push and release again. It turns out that this is unnecessary. Since the binary is already pushed and living in ‘purgatory’ (neither edge or stable) you can still release it with a simple, fast release command e.g.

snapcraft release andy-testsnap-py 6 edge

The number 6 comes from the previous push command and is the revision of the snap. Each time you push a snap or each time you install a snap locally, the revision increments — so keep a close eye on those revision numbers!

Debug via sub-command scripts

A snap can have more than one entry point. This is really useful for debugging because you can add additional helper scripts to debug or support your main app.

You can actually have multiple command entry points to your app.

Assuming you have called your app YOURAPPNAME you invoke these sub entry points using the name of your YOURAPPNAME.subcommand, using the terminal. E.g.

$ YOURAPPNAME.report
$ YOURAPPNAME.subcommand2

All commands entry points live under the apps: sections of your snapcraft.yaml.

apps:
YOURAPPNAME:
command: desktop-launch python3 $SNAP/YOURAPPMAIN.py
    report:
command: python3 $SNAP/report_snap_env.py
    subcommand2:
command: python3 $SNAP/whatever.py

The first entry point YOURAPPNAME in the snapcraft.yaml file under the apps: section is your main entry point viz. invoked via$ YOURAPPNAME. The rest are optional ‘sub’ entry points.

If you want to tweak your PYTHONPATH or PATH then simply add an environment section beneath each command e.g.

apps:
....
report-add-site:
command: python3 $SNAP/report_snap_env.py
environment:
PYTHONPATH: $PYTHONPATH:$SNAP/lib/python3.6/site-packages

The above environment command is actually unnecessary since site-packages is added automatically when running a Python script. Both the $SNAP directory (which is the root of the deployed snap filesystem, and typically the root of your source code) and the Python site-packages directory are automatically added to PYTHONPATH, as you would expect — without you needing to mess with having an environment section like this.

I shipped a custom python script called report_snap_env.py with my snap project

A star trek ‘probe’ command :-)

and exposed it via a sub-command called report (again, arbitrary name) in order to see the environmental situation I am dealing with when running a snap. See the above snapcraft.yamlfragment to see how I expose this python script.

A handy ‘probe’ utility sub-command to ship with your snap

Then, after installing the snap, running YOURAPPNAME.report instead of running the usual YOURAPPNAME, in a terminal, runs my report_snap_env.py script and tells me what is going on with the Python environment inside the snap, which I find useful.

The output will appear in the Linux terminal via regular print commands. Run snap list to ensure your snap is installed locally on your development machine. Add as many diagnostic or supplementary scripts as you like, using this technique.


Part IV — Troubleshooting and Snapcraft Bugs

Multipass

The snapcraft technology builds via virtual machines managed by multipass. These VMs don’t have any GUI and are reasonably slim, compared to the usual VM you run in GUI mode using Vmware or Virtualbox.

Running snapcraft should install multipass for you, when needed. You will usually not need to run multipass commands but its handy to know a few key commands. To list all your virtual machines used for building snaps, multipass listand use start and stop to manage them.

multipass list
multipass start snapcraft-pynsource
multipass stop snapcraft-rubber-band-async

You may find it useful to shell into a VM using multipass shell YOURVMNAME e.g. multipass shell snapcraft-rubber-band-async. Then you get a bash prompt and can look around. You should find the prime snap build directory via cd /root/prime though you will need to sudo bash first. Sure, the snapcraft --shell-after is more convenient because it drops you in the correct directory and there are no permission problems, but its only available during a snapcraft build, whereas using multipass shell can be issued at any time to peek inside any VM you have lying around.

Troubleshooting multipass

There are various bugs I encountered with Multipass:

  • The multipass virtual machine system has a bug where it randomly stops responding to snapcraft, giving timeouts, which means the snapcraft command sometimes fails. E.g. you get a 
    list failed: cannot connect to the multipass socket
    error or other various timeout errors. Perhaps it was my VM. The solution seems to be one or more of the following
  • 1) simply wait for a while and retry the snapcraft command
  • 2) use multipass list to view the running VMs or VMs that are in delayed shutdown mode, shut them down manually with e.g. multipass stop snapcraft-andy-py-cli then try running snapcraft again
  • 3) restart multipass with snap restart multipass. More detail: After reporting this issue in the multipass project on Github, I got some assistance and the suggested workaround is to stop any multipass VMs (list them with multipass list) with multipass stop your_vm_name, finally followed snap restart multipass. Thensnapcraft hopefully runs ok.
  • 4) Other possible solutions mentioned here.
  • Multipass sometimes gives errors the first time you use it. Issues here and here. They seem to settle down after you runmultipass launch snapcraft:core18. If not, there are several other debugging commands you can run, listed here.

If desperate, you can always uninstall and re-install multipass (I’ve been down this road). Linux: snap remove multipass. Since you can build snaps on a Mac (cos building is virtual machine based) here are the uninstall instructions for Mac: sudo sh /Library/Application\ Support/com.canonical.multipass/uninstall.sh followed by brew uninstall snapcraft. To install multipass again, simply run the snapcraft command — on Mac and Linux multipass will be automatically installed as needed. Alternatively, run sudo snap install multipass --beta --classic (on Linux).

How to do snapcraft development inside a Linux Virtual Machine

This is mainly what I do. My iMac is my host machine, and I run Ubuntu 18.04 inside Vmware. Ironically within my Linux world, snapcraft uses multipass virtual machines. So that’s virtual machines within a virtual machine :-)

You can even build linux .snap releases on a Mac, because it is all virtual machine driven. A windows version of snapcraft is coming too.

Note that if you are trying to run snapcraft inside Linux inside a virtual machine, you must use Vmware not Virtualbox. This is because the multipass VM technology relies on some hypervisor feature that only Vmware supports. Configure your Ubuntu 18 Linux VM like this:

The needed hypervisor setting for Vmware

Update: Snapcraft have told me that an upcoming release of snapcraft will fix this issue, to make it easier to build snaps inside VMs you already have.

Snapcraft Bugs

I ran into my share of bugs — a minefield of them. It is March 2019 as I write this. Though, it is possible to navigate through them and build and publish a snap — so, don’t give up!

  • A snap built on Ubuntu 18.04 would not install on the Ubuntu 16.04 LTS using the app store — it is only possible via terminal commands e.g. sudo snap install pynsource. See this issue. I would have thought that one of the primary purposes of snaps is that they can install in multiple versions of Linux, so
It’s ironic/sad that a snap built in Canonical’s Ubuntu 18.04 cannot be installed via the UI app store in their own product, Ubuntu 16 LTS.
  • Installing a snap on the older Ubuntu 16.04 LTS (long term support) via terminal luckily does work but currently requires an extra command sudo snap install core to compensate for a bug where a needed ‘core’ snap is not automatically downloaded as a dependency. See this issue.
  • Using version of Python other than the default Python 3.6.7 that comes with Ubuntu 18 is not documented. Vague references to Python 3.7 ‘snaps’ lead to an abandoned project, and whilst a forum question helpfully points to an example snapcraft.yaml — its part of a larger complex project and its not clear how to extract the needed Python 3.7 bit of the snapcraft.yaml, let alone how it would interact and coordinate with the wxPython install stage (which is Python version specific). I’m still to follow up on this. This should be documented clearly with examples IMO. See this issue.
  • The huge workflow speedup of using the command snap try prime/ is broken with core18 based snaps, (and ironically, core18 are the recommended snaps). Sigh.
Needing to invoke ‘snapcraft clean' a lot — results in a painful re-download of what seems like the entire Ubuntu VM and associated libraries which can take over an hour per build
  • Snapcraft is pretty smart about only rebuilding what it needs to. However, too often a you are forced to do a snapcraft clean before building a .snap via thesnapcraft command. This results in a painful re-download of what seems like the entire Ubuntu VM and associated libraries which can take an hour (in my Vmware VM on a powerful iMac host, with fast broadband internet). Annoyingly, I am forced to do snapcraft cleanbecause of bugs in the Python plugin — see the pip not found bug or must give at least one requirement bug. The status of the latter bug is “Can anyone that speaks Python look into it?” as of Dec 2018.
  • Whilst the snapcraft command supposedly supports partial cleaning (thus supposedly avoiding the wasteful, one hour, starting from scratch process of the brutal snapcraft clean), the more nuanced snapcraft clean -s is actually broken see this issue which a Snapcraft employee has confirmed is a bug, for core18 based snaps, which ironically are the recommended snaps!

Conclusion

It feels really satisfying to have successfully built a snap. I feel like I’ve been through a quest where everything that can go wrong has gone wrong. But hey — that’s programming, and to a lesser degree: that’s open source and that’s Linux. Hopefully this article helps Python developers get a wxPython GUI app into the Ubuntu/Canonical/Snapcraft app store.

Please check out my Pynsource UML reverse engineering tool for Python on the Snapcraft Pynsource app store page here !