Skip to content

ngua/spago.nix

Repository files navigation

spago.nix

🍝.❄

Table of Contents

Introduction

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.

Stability

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!

Quick Start

  1. Add github:ngua/spago-nix to your flake input
  2. Add inputs.spago-nix.overlays.default to your overlays where you import nixpkgs
  3. Initialize a project with pkgs.spago-nix.spagoProject. The only two required arguments to this function are src and name (see the documentation for more)
  4. 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 to spagoProject
  5. spagoProject returns a flake attribute with some default packages, apps, and devShells, 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"; };
      }
    );
}

Motivation

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.

Documentation

Caveats

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 in packages.dhall
If you import a third-party Dhall package set (for example, a common set of dependencies to reduce repetition in different packages.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 how spago.nix works. In particular, since the Spago configuration format has changed from Dhall to YAML, the vast majority, if not all, of spago.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 a legacySpagoProject namespace.

Acknowledgments

spago.nix was directly inspired by Justin Woo’s previous work on spago2nix.

Releases

No releases published

Packages

No packages published