Skip to content
forked from nmattia/snack

Nix-based incremental build tool for Haskell projects

Notifications You must be signed in to change notification settings

wildsebastian/snack

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

94 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Build Status built with nix

Snack

snack is a build tool that uses the power of Nix to build Haskell projects.

It will

  • use your existing Hpack file or a Nix-based config (described below).
  • build your project incrementally: running snack build will only rebuild the modules that have been modified since the previous build.
  • work in the Nix sandbox.
  • give you lots of cool Nix features for free: strong reproducibility guarantees, remote caching, remote builds, and more.
  • improve build performance in some cases, for instance:
    • all Haskell modules are built in parallel.
    • there is a single linking step performed (typically) on a fast tmpfs.

Excited? Check out the install and usage sections. Make sure to also check out the Caveat Emptor section.

Why should I use Snack?

There are plenty of Haskell build tools around (Cabal, Stack, Bazel, ...). Unfortunately none of these allow what I consider to be an ideal workflow:

  1. The same build tool is used by developers and on CI.
  2. The build tool guarantees that builds are reproducible.
  3. The builds are incremental, i.e. if a library contains 300 modules and I modify the main function, only the Main module will be rebuilt.

Using Cabal inside of Nix solves (2); however this means that the builds are not incremental anymore (3). This may not be a problem on CI but definitely is when developing locally. The way to work around that is to use Cabal inside a nix-shell locally and call cabal2nix on CI. This means that developers use a different tool locally than on CI (1). Moreover, a lot of projects nowadays use Stack and, somewhat more importantly, Stackage LTSs. This makes local builds quite easy (in spite of the occasional rebuild when changing flags) but in order to perform a Nix build one has to generate some Nix boilerplate through tools like stackage2nix or stack2nix (which do not always work on CI).

In comparison, snack performs the exact same build on the developer's machine as on CI. The builds are incremental, maybe more so than Cabal builds: if you depend on a snack package foo from package bar, and modify a module Foo from foo which isn't used in bar, no recompilation will occur. Moreover, you benefit from your CI's cache. Finally, because snack is just Nix (and works with the Nix sandbox) you have pretty good guarantees that your builds are reproducible.

Caveat Emptor

The snack library and executable are in their very early stages. They need a lot of testing and massaging. The main (advertised) features are there, but (1) may break for your particular project and (2) may break more in the future.

Now that this is out of the way, install snack, break it, and help me improve it!

Install

Assuming that Nix is installed on your machine, clone this repo and run:

$ nix-env -f ./default.nix -iA snack-exe

The snack executable is now in your PATH:

$ snack --help
Usage: snack ([-s|--snack-nix PATH] | [-p|--package-yaml PATH]) COMMAND

Available options:
  -h,--help                Show this help text

Available commands:
  build
  run
  ghci

Usage

You can use Hpack (for simple builds or if you already have a package.yaml) or Nix (if you need more control over your build).

The next two sections show an example config for each option. They use the following example project which displays the title of the top-rated post on the haskell subreddit (you can also find the code here):

.
├── app
│   └── Main.hs
└── src
    └── Lib.hs

src/Lib.hs :

module Lib where

import Control.Lens
import Network.Wreq
import Data.Aeson.Lens
import Data.Text (Text)

topReddit :: IO Text
topReddit =
    getWith opts url
      <&> (^. responseBody
      . key "data"
      . key "children"
      . nth 0
      . key "data"
      . key "title"
      . _String)
  where
    url = "https://www.reddit.com/r/haskell/top.json"
    opts = defaults
      & param "limit" .~ ["1"]
      & param "t" .~ ["all"]

app/Main.hs :

module Main where

import Lib

main :: IO ()
main = topReddit >>= print

Hpack

The project can have this minimal package.yaml:

name: snack-readme

dependencies:
    - lens
    - wreq

library:
    source-dirs: ./src

executable:
    main: Main.hs
    source-dirs: ./app
    dependencies:
        - snack-readme

default-extensions:
    - OverloadedStrings

This command will build the project and display the top-rated post's title:

$ snack run --package-yaml ./package.yaml

You can also build without executing:

$ snack build --package-yaml ./package.yaml

Alternatively you can load up the project in ghci:

$ snack ghci --package-yaml ./package.yaml
GHCi, version 8.2.2: http://www.haskell.org/ghc/  :? for help
[1 of 2] Compiling Lib              ( /home/nicolas/projects/nmattia/snack/tests/readme/src/Lib.hs, interpreted )
[2 of 2] Compiling Main             ( /home/nicolas/projects/nmattia/snack/tests/readme/app/Main.hs, interpreted )
Ok, two modules loaded.
*Main>

Nix

To build the project the following Nix config is sufficient:

let
  lib =
    { src = ./src;
      dependencies = [ "wreq" "lens" ];
      extensions = [ "OverloadedStrings"];
    };
in
  { main = "Main";
    src = ./app;
    packages = [ lib ];
    dependencies = [ "wreq" "lens" ];
  }

Building and running the project is as simple as

$ snack run # looks for a file called snack.nix by default

Alternatively, use $ snack build or $ snack ghci if you only want to build, or fire up ghci, respectively.

Advanced Nix Example

You may want custom builds that involve things such as archiving and base64 encoding entire directories.

snack builds itself, so its snack.nix is a good example of an advanced configuration. You can also check out the test folder.

Thanks

Big thanks to

  • zimbatm for brainstorming with me and improving the Nix code.
  • 2mol for showing me how to write understandable READMEs.
  • quite a few people at ZuriHac for giving me ideas and feedback.
  • whomever is willing to help, in advance.

About

Nix-based incremental build tool for Haskell projects

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Nix 63.3%
  • Haskell 29.4%
  • Shell 7.2%
  • JSONiq 0.1%