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.
© 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~$
    

Tags: haskell stack