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.
Tools used in this tutorial:
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:
tmp.cabal
totmp.cabal.orig
snap-stack.cabal
tosnap-stack.cabal.orig
and then make the following changes to snap-stack.cabal
:
- add dependencies from
tmp/tmp.cabal
tosnap-stack.cabal
- globally replace
tmp
withsnap-stack
- delete reference to
README.md
(or create the file) - 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~$