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:
- Stack 1.3.2, Git revision 3f675146590d
- 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
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. $
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
$ 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
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_188.8.131.52_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-184.108.40.206: configure bytestring-builder-0.10.8.1.0: copy/register network-220.127.116.11: 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-18.104.22.168: configure old-locale-22.214.171.124: build clock-0.7.2: configure clock-0.7.2: build call-stack-0.1.0: configure old-locale-126.96.36.199: copy/register [… deleted 100 lines …] Completed 34 action(s). $
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
$ 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
$ 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
and then make the following changes to
- add dependencies from
- globally replace
- 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: email@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~$