🍝.❄
spago.nix is tool that aims to provide ergonomic Nix integration for Purescript projects built with Spago. It aims to supersede tools such as spago2nix and provide the following features:
- Zero autogenerated Nix code for users (but see the caveats below)
- A flakes-first workflow
- Support for easily generating flakes outputs from Spago projects
See the Motivation below for more information on why spago.nix was created.
spago.nix is currently fairly experimental – things may not work correctly for your project and there are almost certainly many edge cases still lurking within. If you find that’s the case, please open an issue and I’ll do my best to try to fix it!
- Add
github:ngua/spago-nixto your flake input - Add
inputs.spago-nix.overlays.defaultto youroverlayswhere you importnixpkgs - Initialize a project with
pkgs.spago-nix.spagoProject. The only two required arguments to this function aresrcandname(see the documentation for more) - Optional: If your Spago project depends on any third-party dependencies (i.e. not in an upstream package set), add them to your flake inputs and include them in the
extraSourcesargument tospagoProject spagoProjectreturns aflakeattribute with some defaultpackages,apps, anddevShells, along with functions for creating more outputs (see the documentation for more details)
Example:
{
description = "Simple purescript.nix example";
inputs = {
nixpkgs.follows = "spago-nix/nixpkgs";
spago-nix.url = "github:ngua/spago.nix";
flake-utils.url = "github:numtide/flake-utils";
# Additional Purescript dependencies can be pinned in your flake `inputs`
# and then provided to `spagoProject` via `extraSources` (see below)
lattice = {
url = "github:Risto-Stevcev/purescript-lattice/v0.3.0";
flake = false;
};
properties = {
url = "github:Risto-Stevcev/purescript-properties/v0.2.0";
flake = false;
};
};
outputs =
{ self
, nixpkgs
, spago-nix
, flake-utils
, ...
}@inputs:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = import nixpkgs {
inherit system;
# This is necessary to access the various functionality that `spago.nix`
# provides. The entire interface is exposed via the `spago-nix` prefix
# in the resulting package set
overlays = [ spago-nix.overlays.default ];
};
# `spago-nix.spagoProject` is the key function for building your Spago
# project with Nix. It provides various attributes, some of which are
# demonstrated below (see `./docs/reference.org` for all of them)
project = pkgs.spago-nix.spagoProject {
name = "spago-nix-example";
src = ./.;
# These are third-party dependencies that don't exist in the upstream
# package set. The pinned inputs *must* match the exact revision that
# is described in your `packages.dhall`, otherwise your project likely
# won't compile
extraSources = { inherit (inputs) lattice properties; };
# This is used to generate a `devShell`. See `./docs/reference.org` for
# all of the available options
shell = {
tools = {
psa = { };
purescript-language-server = "0.17.1";
purs-tidy = "latest";
};
shellHook = ''
echo 'Welcome to your spago project!'
'';
};
};
in
{
# `spagoProject` returns, among other things, a `flake` attribute with
# some pre-built outputs for your convenience
devShells = { inherit (project.flake.devShells) default; };
# `flake.packages` contains the compiled `output` and `docs`. Since
# Spago does has no mechanism for defining components in your config,
# `spagoProject` also returns functions for creating the derivations
# that cannot be generated for you automatically
packages = project.flake.packages // {
bundled-module = project.bundleModule { main = "Main"; };
bundled-app = project.bundleApp { main = "Main"; };
node-app = project.nodeApp { main = "Main"; };
};
# Similarly, `flake.apps` contains a `docs` app that serves the documentation
# from a webserver on `localhost` (provided that the `withDocs` argument to
# `spagoProject` is `true`, its default value)
apps = project.flake.apps // {
# `spago-nix.utils` has some helpers for reusing existing `packages`
# to create `apps`
node-app = pkgs.spago-nix.utils.apps.fromNodeApp {
app = self.packages.${system}.node-app;
};
};
checks.default = project.runTest { testMain = "Main"; };
}
);
}The status quo for building Purescript projects with Nix is unfortunately quite lackluster. Neither Spago nor its chosen configuration language, Dhall, are particularly amenable to working in pure environments such as the Nix build sandbox. Spago’s package format does not include the hashes for declared dependencies, meaning that these must be calculated somehow before fetching the sources for each dependency.
The current default choice for Purescript users wanting to build with Nix is spago2nix, which is affected by these limitations. spago2nix approaches the lack of hashes by calling nix-prefetch-git for each dependency (as does spago.nix, but in a different step that does not directly affect users). This also prevents spago2nix from being run in a pure environment, however. This could be worked around by using fixed-output derivations with spago2nix, but that would lead to an unpleasant interface.
Because of this fundamental limitation, spago2nix requires generating and committing Nix code (its spago-packages.nix). Obscure build errors can arise when users forget to run spago2nix generate, which is not especially rare in my experience. spago2nix also provides a fairly limited interface that is quite far from that of spago – if users wish to build project documentation, for example, they must write derivations by hand. Its interface for building a Spago project consists of a single derivation – build-spago-style – that does not allow for any control over or customization of the build process (it calls purs directly with the provided sources). spago2nix also does not use spago internally, which means that the experience of building the same project might differ depending on the context (i.e either inside or outside of Nix).
Most of the time, a user’s spago-packages.nix will primarily contain the same Purescript packages from upstream package sets. Instead of requiring the user to always generate Nix package sets containing hashes for each dependency, we can generate them and then store them centrally in a repository. This emulates package sets like nodePackages and, most importantly, allows us to create a suitable package set for users in a pure environment, thus freeing them from needing to generated Nix code. See how spago.nix works for more details on its approach.
The docs provide a brief overview of how spago.nix works. There are some consequences to the approach it uses, however, and spago.nix might not work with your Spago project. spago.nix is also under heavy development and some of its present limitations may be resolved in the future. In the meantime, the following major caveats apply:
- No custom package sets can be used with
importstatements inpackages.dhall -
If you
importa third-party Dhall package set (for example, a common set of dependencies to reduce repetition in differentpackages.dhallwith the same dependencies),spago.nixwill not work properly. The import will be extracted, but ignored. For example:-- OK, this is an official package set and will work let upstream = https://github.com/purescript/package-sets/releases/download/psc-0.x.x/packages.dhall sha256:0000000000000000000000000000000000000000000000000000000000000000 -- Will not work :( let special-packages = https://example.com/foo/bar/special-packages.dhall sha256:0000000000000000000000000000000000000000000000000000000000000000
- Alternate backends aren’t supported
-
Currently, using alternate Purescript backends is not supported (e.g. purescript-native). This may change in the future, although these backends are generally not up-to-date with
pursitself. - Spago is going to change dramatically soon
-
The
spagotool itself is currently undergoing a rewrite from Haskell to Purescript. This will require changing a significant part of howspago.nixworks. In particular, since the Spago configuration format has changed from Dhall to YAML, the vast majority, if not all, ofspago.nix’s Haskell component will no longer be required.spago’s behavior will certainly change as well, and it will take some time before compatibility with the new version is achieved. Even after that is done, however, I plan on maintaining compatibility with legacy Spago for some time, perhaps under alegacySpagoProjectnamespace.
spago.nix was directly inspired by Justin Woo’s previous work on spago2nix.