🍝.❄
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-nix
to your flake input - Add
inputs.spago-nix.overlays.default
to youroverlays
where you importnixpkgs
- Initialize a project with
pkgs.spago-nix.spagoProject
. The only two required arguments to this function aresrc
andname
(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
extraSources
argument tospagoProject
spagoProject
returns aflake
attribute 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
import
statements inpackages.dhall
-
If you
import
a third-party Dhall package set (for example, a common set of dependencies to reduce repetition in differentpackages.dhall
with the same dependencies),spago.nix
will 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
purs
itself. - Spago is going to change dramatically soon
-
The
spago
tool itself is currently undergoing a rewrite from Haskell to Purescript. This will require changing a significant part of howspago.nix
works. 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 alegacySpagoProject
namespace.
spago.nix
was directly inspired by Justin Woo’s previous work on spago2nix.