How to Flatpak a Haskell App into Flathub
You’ve modeled your life around Kleisli composition. On your right arm is a tattoo that reads, “point free or die.” You can tie the knot in your sleep. Zygohistomorphic prepromorphism is your go-to recursion scheme. You looked out from atop the Haskell pyramid and found the view boring. Now comes the hard part — distributing your Haskell app to Linux users.
Once that last commit goes in, it’s time to get your app out there by opening up a distribution channel. One viable Linux app distribution channel is Flathub.
Flathub aims to be the place to get and distribute apps for Linux. It is powered by Flatpak which allows Flathub apps to run on almost any Linux distribution. — https://flathub.org/about
To add your app to Flathub, you must first make a Flatpak manifest. I’ll make this concrete by following along with everyone’s favorite functional video player Movie Monad and its induction into Flathub.
First things first, get a working virtual machine of the latest Fedora up and running. Geared for Flatpak, Fedora will simplify things.
While Flatpak is not officially affiliated with the Fedora Project, or GNOME Foundation, it is perhaps best supported by Fedora and the GNOME desktop environment. This is because Fedora releases 23 and later have all had Flatpak in their official repositories. The GNOME Software application also has support for installing Flatpaks. — https://fedoraproject.org/wiki/Flatpak
With the virtual machine running, make sure you can build and run your Haskell app on Fedora — outside of the Flatpak sandbox system. Identify any issues you come across and any packages you had to install.
The manifest is a JSON file that describes the app, its run time, its permissions, its dependencies, its build procedure, and the command needed to run it.
There are different available run times that come with their own set of packages. Choose the run time that provides the greatest number of dependencies your app needs. For anything needed outside the run time, you’ll have to provide it yourself inside the modules section of the manifest.
Since we’re dealing with Haskell, you’ll need GHC to build your app inside the sand-boxed build environment. Flatpak doesn’t make any demands about what must be built from source. Flathub, however, does require that you build your app from source.
Note the prefix used in the image above. Everything you add to modules should go under “/app”. This is a Flathub rule.
Inside the sand-boxed build environment, we have no network access. Everything that needs to be downloaded can only be downloaded by flatpak-builder. So anything we want downloaded and available to us inside the sandbox has to go under sources for each module. This makes building Haskell apps much more complicated as it rules out options like Stack or cabal-install.
To get around the lack of network access, we’ll have to perform some of what Stack and Cabal does. The first thing we need is the list of dependencies we’ll have to download and build. I recommend building your app (outside the Flatpak system) with Cabal — using a fresh sandbox — and making note of which dependencies it builds, their exact version numbers, and the order it builds them in.
The order is important. I don’t know of any Stack or Cabal command that will list the dependencies, with their exact version number, in dependent order. We need the dependencies in order as flatpak-builder runs the modules in the order you list them. If you attempt to build Cabal package B but it depends on A and A is listed below B, B will fail to build and flatpak-builder will exit.
With the list of dependencies (and their version numbers) sorted by dependence, we need to generate the list of modules. We could do this by hand but instead we’ll script it.
The script up above downloads the dependency from Hackage, calculates it hash, and outputs the JSON block to be copied into the manifest for each dependency we feed it. For most Cabal packages, the build commands are the same using the typical setup, configure, build, and install procedure.
For some Cabal packages, you’ll run into revisions. The tar files downloaded from Hackage are not up-to-date with any revisions. In some cases, this will cause the build to break and you’ll have to apply the revisions yourself. You could download the revisions from Hackage (listing the files in the sources section) and overwrite the revised files. Instead, I used sed as you can see in the image above. In this particular case, I had to increase the upper bound limit from <4.9 to <5 for base.
When it comes time to build and install your app, you must copy the AppData, desktop, and icon file into their appropriate places under /app.
For each file, it must be in reverse domain name notation. So
- my-app.appdata.xml becomes tld.domain.my-app.appdata.xml
- my-app.desktop becomes tld.domain.my-app.desktop
- and my-app.svg becomes tld.domain.my-app.svg.
If your app lives on GitHub, you could use com.github.username.my-app.
Inside the AppData file, the <id/> section must also use the reverse domain name notation. Flathub has a quirk where the <id/> must end in .desktop. If it doesn’t, your app will not show up on the Flathub website but will still appear in GNOME software. Normally, the <id/> section doesn’t need to end with .desktop. I believe this is fixed now, however.
While you’re editing your AppData, run it through appstreamcli validate. This will point out any errors present and make suggestions.
Make sure the Icon= key value entry in the desktop file also uses the reverse domain name notation.
In the same module block that builds and installs your app, you’ll want to specify a post-install procedure. It is here that you can remove any files not necessary to run your app. This will reduce the final file size making for a quicker download of your app from Flathub.
flatpak-builder my-app tld.domain.my-app.json --force-clean --install
flatpak run tld.domain.my-app
With your manifest complete, it is now time to build and run it. If you need to debug it, you can do the following. This will give you a shell inside the sand-boxed environment.
flatpak-builder -- run my-app tld.domain.my-app.json sh
The Pull Request
You’ve written your manifest and have tested it on your local virtual machine running Fedora. If everything works, you can now fork Flathub and submit your pull request to add your app. Make sure to title the pull request “Adds tld.domain.my-app”.
After you submit your pull request, the friendly Flathub maintainers will review it, close it, open up a repository for it under Flathub, and give you write access to the new repository. With this new repository, you can update the manifest with any version changes necessary. Each time you push to master, a build of your Flatpak will start. Check Buildbot to make sure the build succeeded and deployed.
flatpak remote-add flathub https://flathub.org/repo/flathub.flatpakrepo
flatpak install flathub tld.domain.my-app
Using the commands above, any Flatpak user can now install your app.
Flatpak not your thing? Want to be in more app stores? Check out How to Snap your Awesome (Haskell) App.