Packaging the Multipass Flutter GUI for NixOS
Table of Contents
Introduction #
The very first application I ever packaged in nixpkgs was Multipass. Multipass is, according to the website:
a CLI to launch and manage VMs on Windows, Mac and Linux that simulates a cloud environment with support for cloud-init.
I wrote in some detail about how I use Multipass and LXD on my workstation to create and manage VMs for testing and development. Last year Multipass shipped a new GUI written from the ground up in Flutter. It provides a clean, modern way to launch, manage and interact with VMs. When the GUI first shipped, I briefly attempted to get it to build with Nix, but some combination of my lack of knowledge, and the maturity of the Flutter tooling in nixpkgs
at the time meant I never finished it.
As version 1.15.0
was in its release candidate stage, I decided to have another go!
This post will break down the process into steps, but if you’d like the tl;dr, take a look at the pull request.
Housekeeping #
Before getting started on packaging the GUI, I did some housekeeping on the Multipass package. First order of business was to simply bump the version to 1.15.0
and ensure the package still built, which it did.
Last year, RFC 140 was introduced to simplify the directory structure of nixpkgs
, introducing a new pkgs/by-name
directory which will (eventually) render pkgs/top-level/all-packages.nix
useless. This new structure will make finding package definitions much easier, as they’ll all be arranged alphabetically by the first two characters of their name. Using the Multipass example, rather than pkgs/tools/virtualization/multipass/default.nix
, the new preferred path would be pkgs/by-name/mu/multipass/package.nix
. There was a talk at NixCon 2023 which summarised this nicely if you’d like more information.
As I was going to be carrying out some hefty work on Multipass by introducing the GUI, I took the opportunity to move to the new scheme as part of the changes.
Restructuring #
The Multipass package carries a few patches, and is already reasonably involved, so I decided that the best route forward was to create separate files for multipassd
/multipass
(the server & command line client binaries, written in C++, and already packaged) and multipass.gui
(the new Flutter application). Thus the new structure for the package looks like so:
|
|
Using symlinkJoin
#
This package now takes a more unusual shape. Because the GUI is essentially a entirely separate application, written with a different toolkit in a different language, but I wanted the two to be installed as part of the overall multipass
package, I decided to use two completely separate derivations and use symlinkJoin
to bundle the two together. According to the manual, symlinkJoin
:
can be used to put many derivations into the same directory structure. It works by creating a new derivation and adding symlinks to each of the paths listed. It expects two arguments, name, and paths. name (or alternatively pname and version) is the name used in the Nix store path for the created derivation. paths is a list of paths that will be symlinked. These paths can be to Nix store derivations or any other subdirectory contained within.
This is particularly useful for the Multipass package, because the Multipass GUI dynamically-links against libraries that are built as part of the multipassd
derivation, and linking the two derivations in this way ensures that all those files are located correctly at runtime without any extra plumbing.
This allowed me to keep the top-level package.nix
relatively simple (annotated below):
|
|
Building the GUI #
Normally the server, client and GUI binaries are all built as part of a single invocation of cmake
. We can’t do that here, so the early code I put into the multipass
package when I failed to package the GUI before still stood - essentially stopping cmake
from trying to build the Flutter GUI:
|
|
Next, I created gui.nix
which uses the pkgs.buildFlutterApplication
helper. The derivation is annotated below:
|
|
Update Script #
That was enough to ensure that the GUI worked correctly. My concern now lied with the maintenance of the package. The process for updating the derivation now involved:
- Bumping the version string
- Updating the source hashes
- Updating the
pubspec.lock.json
file - Updating the
gitHashes
of the Flutter dependencies
This seemed a little convoluted and error prone to handle manually each time, so I decided to implement an update script for the package to take care of future version bumps. Thankfully nixpkgs
has good provision for this, allowing package maintainers to specify passthru.updateScript
to a derivation, which I did here.
I won’t post the full update script here, as it’s lots of unsightly bash
! Essentially the script takes care of determining the latest version released on Github by using curl
, then updates the pubspec.lock.json
using a combination of curl
/yj
, then finally works through updating the source hash for the Multipass repo, gRPC fork it uses, and each of the git hashes for the Flutter dependencies.
This can be invoked from within the nixpkgs
repo with:
|
|
Summary #
Overall, this turned out cleaner than I expected, though it took me longer to figure out than this post might suggest. My hope is that this will a useful reference to anybody trying to package Flutter applications with Nix, and if you’re a user of the multipass
package, check out the shiny new GUI that’s available to you! The GUI is available from 1.15.0
onwards, which at the time of writing means you’ll need to install the package from nixos-unstable
or similar (sadly I didn’t land the change in time for NixOS 24.11).
In my opinion, the Multipass team have done a lovely job on the GUI - and for many users this will provide a much improved onboarding experience. Nice work 😎