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:
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:
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?
No! 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.yaml
file.
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.
- Install snapcraft e.g.
snap install snapcraft
on Linux orbrew install snapcraft
on Mac - Create a
snapcraft.yaml
file ← this is the hard bit ;-) - Run the command
snapcraft
which creates a .snap file - Install your .snap file locally
sudo snap install --devmode --dangerous *.snap
- Run and test your snap by invoking its command name e.g.
yourapp
- If you are happy, reserve a name on the snap store
snapcraft register yourapp
- Push and publish your snap e.g.
snapcraft push --release=edge *.snap
- Edit your snap app’s web page with images and a descriptions. You can also provide a description inside the
snapcraft.yaml
file. - 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 we are going to follow 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 used to build your snap: 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 build process, 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.txt
then /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.yaml
can 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.
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 *.snapProcessing...|
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 2020: The latest versions of the snapcraft
tool (certainly in 2020) support a simpler way to specify a gtk3 app — now all you need to do is add extensions: [gnome-3–28]
and you don’t need the entire desktop-gtk3:
part listed above — just delete all those lines and be happy! Incidentally 3–28
refers to the version of Gnome shipped in Ubuntu 18.04, which corresponds to the base: core18
VM we are using to build our snap.
There are other simplifications as a result of the extensions: [gnome-3–28]
entry, like less things to list in plugs, no need for desktop-launch in the launch command, and no need for LD_LIBRARY_PATH being set (thanks to Galgalesh for pointing out these simplifications), thus my new simplified pynsource entry is now:
apps:
pynsource:
command: python3 $SNAP/pynsource-gui.py
extensions: [gnome-3-28]
plugs: [unity7, audio-playback, home, network]
Thank goodness, because finding the exact list of gtk3 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
for the icon and follow the steps outlined.
You will need to arrange your source code directory structure so that the snapcraft.yaml
file is located in a directory snap
and the .desktop and .png files are located in snap/gui
. Always run the snapcraft
command from the root of your project (e.g. where your README.md is).
.
|-- README.md
|-- pynsource_0+git.8095336_amd64.snap
|-- requirements.txt
|-- snap
| |-- gui
| | |-- pynsource.desktop
| | `-- pynsource.png
| `-- snapcraft.yaml
`-- src
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 files in your snap/gui/
folder: pynsource.png
and pynsource.desktop
get into the /root/prime/meta/gui/
directory is done magically for you by snapcraft, assuming you have followed the steps in this documentation blog post. Do not have a desktop:
entry explicitly referring to your .desktop file
— otherwise those files will not be copied.
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, which has been updated in 2020 to use the simplified magic of the extensions: [gnome-3–28]
entry:
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. Update 2021: You can actually clear out a specific part if it is causing problems, by specifying the part name, which is much faster than a wholesale snapcraft clean
— simply run e.g. snapcraft clean copy-stuff
to clear out the cached part ‘copy-stuff’.
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 thesnapcraft.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 theunsquashfs
command/snap/YOURAPPNAME/x3/
when the snap is installed on your Linux system. Thex
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!
Update: As this helpful post points out, “the variable $SNAP
is meant to be used after installation of your snap. It points to the directory of your snap after it is installed. You should not use it during build. These are some of the variables to use during build: https://snapcraft.io/docs/parts-lifecycle#heading--parts-directories.” Since the snapcraft stages are a little confusing, its sometimes hard to know what part of the snapcraft.yaml
file is used in which stage. It seems to me that at the very least, launch commands that have an environment:
entry could validly refer to the variable $SNAP
because launching happens after everything is installed.
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 --debug
or snapcraft --shell-after
is 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.yaml
fragment to see how I expose this python script.
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 list
and 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 alist 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 withmultipass list
) withmultipass stop your_vm_name
, finally followedsnap 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 run
multipass 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:
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 dosnapcraft clean
because 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 nuancedsnapcraft 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 !
My Other Cool Software
- GitUML — generate UML diagrams instantly from any GitHub repository containing Python code (web app)
- Pynsource — UML for Python (desktop app)
- Python to RPN — Run Python 3 on vintage HP calculators (web app)
- Print42 — Electron-Python app, non-invasive log file annotation tool that supports thermal printers
About Andy Bulka
- Main website and blog www.andypatterns.com
- Medium article: Building a deployable Python-Electron App — Using Electron as a GUI toolkit for Python.
- Medium article: Async/await for wxPython — Python 3 GUI apps with asynchronous functionality.
- Medium article: Getting Python and wxPython apps into the Ubuntu app store
- Github https://github.com/abulka