How to Build Snap with Stack

March 7, 2017

I prefer Stack’s focus on repeatable builds, but Snap (a Haskell web framework) doesn't give any instructions for how to build with Stack. Here's how.

A close up picture of a snap. class=
© 2007 Beatrice Allée for Wikimedia Commons

Tools used in this tutorial:

  1. Stack 1.3.2, Git revision 3f675146590d
  2. Snap, retrieved Jan 25, 2017

All code for this blog entry can be found on github.

Create initial Snap project with snap init.

$ cd sandbox/snap-stack
$ snap init barebones
$ tree
.
├── log
│   └── placeholder
├── snap-stack.cabal
└── src
    └── Main.hs

2 directories, 3 files
$

This creates the Snap hello world app, which is described in detail in the Snap API Introduction.

Use Snap’s cabal configuration to initialize Stack.

$ stack init
Looking for .cabal or package.yaml files to use to init the project.
Using cabal packages:
- snap-stack.cabal

Selecting the best among 10 snapshots…

Downloaded lts-8.4 build plan.
Fetching package index …remote: Counting objects: 2259, done.
remote: Compressing objects: 100% (98/98), done.
remote: Total 2259 (delta 810), reused 785 (delta 785), pack-reused 1369
Receiving objects: 100% (2259/2259), 1.92 MiB | 359.00 KiB/s, done.
Resolving deltas: 100% (906/906), completed with 374 local objects.
From https://github.com/commercialhaskell/all-cabal-hashes
   93b8c00..98d707f  hackage    -> origin/hackage
 - [tag update]      current-hackage -> current-hackage
Fetched package index.
Populated index cache.
* Matches lts-8.4

Selected resolver: lts-8.4
Initializing configuration using resolver: lts-8.4
Total number of user packages considered: 1
Writing configuration to file: stack.yaml
All done.
$

Stack looks at the dependencies .cabal (created by Snap) and tries If it can’t find an LTS snapshot, it will try a nightly snapshot. to find a long-term support stackage snapshot that satisfies these dependencies. In this case, it picked LTS 8.4, a set of 2,220 packages that was published yesterday.

Install the Haskell compiler.

For Stack, repeatable builds mean the compiler must be the same as well as the package set. If you try to build, and don’t have the correct compiler already installed by Stack, you get this output:

$ stack build
No compiler found, expected minor version match with ghc-8.0.2 (x86_64) (based on resolver setting in /Users/mark/src/markbucciarelli.com/sandbox/snap-stack/stack.yaml).
To install the correct GHC into /Users/mark/.stack/programs/x86_64-osx/, try running "stack setup" or use the "--install-ghc" flag.
$

LTS 8.4 I think stackage was having server load issues; the first seven times I tried running stack setup it hung on the download. I finally got it after waiting until the next morning. goes with compiler ghc-8.0.2 and I don’t have it installed, so use stack setup to install.

$ stack setup
Preparing to install GHC to an isolated location.
This will not interfere with any system-level installation.
Downloaded ghc-8.0.2.
Installed GHC.
stack will use a sandboxed GHC it installed
For more information on paths, see ’stack path’ and ’stack exec env’
To use this GHC and packages outside of a project, consider using:
stack ghc, stack ghci, stack runghc, or stack exec
$

Note that ghc is not installed on your path, to run the interpreter, you must run stack ghci.

Confirm the project builds with Stack.

$ stack build
[1 of 2] Compiling Main             ( /Users/mark/.stack/setup-exe-src/setup-mPHDZzAJ.hs, /Users/mark/.stack/setup-exe-src/setup-mPHDZzAJ.o )
[2 of 2] Compiling StackSetupShim   ( /Users/mark/.stack/setup-exe-src/setup-shim-mPHDZzAJ.hs, /Users/mark/.stack/setup-exe-src/setup-shim-mPHDZzAJ.o )
Linking /Users/mark/.stack/setup-exe-cache/x86_64-osx/tmp-Cabal-simple_mPHDZzAJ_1.24.2.0_ghc-8.0.2 …
bytestring-builder-0.10.8.1.0: configure
call-stack-0.1.0: download
integer-logarithms-1.0.1: download
bytestring-builder-0.10.8.1.0: build
network-2.6.3.1: configure
bytestring-builder-0.10.8.1.0: copy/register
network-2.6.3.1: build
primitive-0.6.1.0: configure
primitive-0.6.1.0: build
mtl-2.2.1: configure
mtl-2.2.1: build
old-locale-1.0.0.7: configure
old-locale-1.0.0.7: build
clock-0.7.2: configure
clock-0.7.2: build
call-stack-0.1.0: configure
old-locale-1.0.0.7: copy/register
[… deleted 100 lines …]
Completed 34 action(s).
$

Success!

Convert project to use a standard Stack directory structure.

Stack organizes your sources into libraries, test specs and application sources.

Create an empty Stack project in the sub directory tmp.

$ stack new tmp
Downloading template "new-template" to create project "tmp" in tmp/ …

The following parameters were needed by the template but not provided: author-email, author-name, category, copyright, github-username
You can provide them in /Users/mark/.stack/config.yaml, like this:
templates:
  params:
    author-email: value
    author-name: value
    category: value
    copyright: value
    github-username: value
Or you can pass each one as parameters like this:
stack new tmp new-template -p "author-email:value" -p "author-name:value" -p "category:value" -p "copyright:value" -p "github-username:value"

Looking for .cabal or package.yaml files to use to init the project.
Using cabal packages:
- tmp/tmp.cabal

Selecting the best among 10 snapshots…

* Matches lts-8.4

Selected resolver: lts-8.4
Initialising configuration using resolver: lts-8.4
Total number of user packages considered: 1
Writing configuration to file: tmp/stack.yaml
All done.
$ cd tmp

The standard Stack directories:

$ tree tmp
tmp
├── LICENSE
├── Setup.hs
├── app
│   └── Main.hs
├── src
│   └── Lib.hs
├── stack.yaml
├── test
│   └── Spec.hs
└── tmp.cabal

3 directories, 7 files
$

No change in stack.yaml:

$ diff stack.yaml tmp/stack.yaml
$

Move directories around.

$ mv src app
$ mv tmp/src .
$ mv tmp/test .

Update cabal file.

The Stack one is bigger, so start with that.

$ ls -l snap-stack.cabal
-rw-r--r--  1 mark  staff  960 Mar  7 21:44 snap-stack.cabal
$ ls -l tmp/tmp.cabal
-rw-r--r--  1 mark  staff  1158 Mar  8 06:13 tmp/tmp.cabal

Copy:

  1. tmp.cabal to tmp.cabal.orig
  2. snap-stack.cabal to snap-stack.cabal.orig

and then make the following changes to snap-stack.cabal:

  1. add dependencies from tmp/tmp.cabal to snap-stack.cabal
  2. globally replace tmp with snap-stack
  3. delete reference to README.md (or create the file)
  4. delete reference to LICENSE (or create the file)

We end up with the following diff:

$ diff -uw tmp/tmp.cabal.orig snap-stack.cabal
— tmp/tmp.cabal.orig    2017-03-08 06:41:24.000000000 -0500
+++ snap-stack.cabal    2017-03-08 06:59:30.000000000 -0500
@@ -1,16 +1,14 @@
-name:                tmp
+name:                snap-stack
 version:             0.1.0.0
 -- synopsis:
 -- description:
-homepage:            https://github.com/githubuser/tmp#readme
+homepage:            https://github.com/githubuser/snap-stack#readme
 license:             BSD3
-license-file:        LICENSE
 author:              Author name here
 maintainer:          example@example.com
 copyright:           2017 Author name here
 category:            Web
 build-type:          Simple
-extra-source-files:  README.md
 cabal-version:       >=1.10

 library
@@ -19,23 +17,28 @@
   build-depends:       base >= 4.7 && < 5
   default-language:    Haskell2010

-executable tmp-exe
+executable snap-stack-exe
   hs-source-dirs:      app
   main-is:             Main.hs
   ghc-options:         -threaded -rtsopts -with-rtsopts=-N
   build-depends:       base
-                     , tmp
+                     , snap-stack
+                     , bytestring                >= 0.9.1 && < 0.11
+                     , monad-control             >= 1.0   && < 1.1
+                     , mtl                       >= 2     && < 3
+                     , snap-core                 >= 1.0   && < 1.1
+                     , snap-server               >= 1.0   && < 1.1
   default-language:    Haskell2010

-test-suite tmp-test
+test-suite snap-stack-test
   type:                exitcode-stdio-1.0
   hs-source-dirs:      test
   main-is:             Spec.hs
   build-depends:       base
-                     , tmp
+                     , snap-stack
   ghc-options:         -threaded -rtsopts -with-rtsopts=-N
   default-language:    Haskell2010

 source-repository head
   type:     git
-  location: https://github.com/githubuser/tmp
+  location: https://github.com/githubuser/snap-stack

Run web server

Start web server on localhost …

$ stack exec snap-stack
no port specified, defaulting to port 8000
Listening on http://0.0.0.0:8000

… and test.

$ curl localhost:8000
hello world~$

If you see an error or something that could be improved, please let me know. This is a blog about me learning, so I expect I will get some stuff wrong. The best way to reach me is by email: mkbucc1234@gmail.com (after deleting all the numbers).

To make a comment, check for a thread on the haskell subreddit and if there isn't one, then start one up.

Follow on Twitter: @mbucc

Back to the index.