Introduction

In my experience stack1 works pretty well out-of-the-box when you use it to compile projects whose dependencies can be found in Stackage.

In fact, stack works so well that I could use it without understanding how it worked. All fine, until I wanted to use stack for standalone .hs files, or use a local library. All the information below is in stack’s excellent official documentation2, but I found it hard to put together.

Normal operation

It seems wise to document how I understand stack works, at least the bits which are relevant to the discussions below. The details of this might well be wrong: regard it less as an accurate description of stack than an approximation which might be helpful later.

Suppose we have some project which we want to compile with stack. To zeroth order it looks just like a normal cabal project. In particular, there’s a foo.cabal file which lists the targets, the dependencies and so on. None of that changes under stack. We do however, get a couple of new things in the project directory:

When we execute e.g. stack build stack compiles everything, dependencies and all, in the .stack-work directory. I think it’s worth reiterating that stacks know what to build, and which dependencies are needed by looking in the project’s .cabal file.

Project-free use

Of course there’s nothing to stop us invoking stack outside a project, but it slightly begs the question of what stack will do about the .cabal and stack.yaml files, and the .stack-work directory.

Let’s proceed by experimentation, and launch ghci in my home directory:

$ stack ghci								
Run from outside a project, using implicit global project config	
Using resolver: lts-5.15 from implicit global project's config file:	
  /Users/mjo/.stack/global-project/stack.yaml				
Error parsing targets: The specified targets matched no packages.	
Perhaps you need to run 'stack init'?					
Warning: build failed, but optimistically launching GHCi anyway		
Configuring GHCi with the following packages: 				
GHCi, version 7.10.3: http://www.haskell.org/ghc/  :? for help		
Ok, modules loaded: none.						
Prelude> 								

Well that explains what’s happening about stack.yaml: stack has set up a global-project directory under ~/.stack, and is using the stack.yaml file within it. Further experiments would show that this directory is used every time we invoke stack outside a project. Effectively, there’s a single ‘not-in-a-real-project’ project, though perhaps it should have been called global-noproject!

You might guess that this is where we’d find .stack-work too, and you’d be right:

$ ls -la ~/.stack/global-project/					
total 20								
drwxr-xr-x   6 mjo  staff  204  8 May 13:24 .				
drwxr-xr-x  12 mjo  staff  408  8 May 07:37 ..				
drwxr-xr-x   5 mjo  staff  170  8 May 07:36 .stack-work			
-rw-r--r--   1 mjo  staff  103  8 May 01:16 README.txt			
-rw-r--r--   1 mjo  staff  572  8 May 13:24 stack.yaml			

Finally we come to the missing .cabal file. This shouldn’t really be a surprise, because it’s precisely the situation we had in the pre-stack era. This does mean though that there’s nowhere to specify dependencies, so we’ll have to manage them manually as we used to do with cabal. Whether this means there’s potential for stack global-project hell isn’t clear to me!

As an example, suppose we want to play with Data.Digits3 in ghci. This module is part of the digits package, which is included in stackage. However, just trying to import it fails:

mjo$ stack ghci							
Run from outside a project, using implicit global project config	
...
GHCi, version 7.10.3: http://www.haskell.org/ghc/  :? for help		
Ok, modules loaded: none.						
Prelude> import Data.Digits						
									
<no location info>:							
    Could not find module ‘Data.Digits’					
    Perhaps you meant Data.Bits (from base-4.8.2.0)			
Prelude> 								
Leaving GHCi.								

The solution, just as it was with cabal is to install the package first (in stack’s global-project/.stack-work directory):

mjo$ stack install digits					
Run from outside a project, using implicit global project config	
Using resolver: lts-5.15 from implicit global project's config file:	
  /Users/mjo/.stack/global-project/stack.yaml				
tf-random-0.5: download							
tf-random-0.5: configure						
tf-random-0.5: build							
tf-random-0.5: copy/register						
QuickCheck-2.8.1: download						
QuickCheck-2.8.1: configure						
QuickCheck-2.8.1: build							
QuickCheck-2.8.1: copy/register						
digits-0.2: download							
digits-0.2: configure							
digits-0.2: build							
digits-0.2: copy/register						
Completed 3 action(s).

You can see that stack handled digits’ dependencies for us. Now, it works:

mjo$ stack ghci							
Run from outside a project, using implicit global project config	
...
GHCi, version 7.10.3: http://www.haskell.org/ghc/  :? for help		
Ok, modules loaded: none.						
Prelude> import Data.Digits 						
Prelude Data.Digits> 							

Local repositories

Sometimes we might want to use a repository outside stackage, and happily stack supports this. All the information you need is in the documentation4, though I found it fiddly to get right.

As an example, I want to use a repository on GitHub. At first assume that I’m using it from another package.

Firstly, we need to add the name of the distribution to the .cabal file:

...
  build-depends:       base >= 4.7 && < 5
                       , haskell-toys
...

Then we need to tell stack how to find it. Since this is information stack needs, we put it in the project’s stack.yaml file:

... # Local packages, usually specified by relative directory name
packages:
- location: '.'
- location:
    git: https://github.com/mjoldfield/haskell-toys.git
    commit: ebeb6d6ea490db92b5ec2f68e8075887c6a6994f
  extra-dep: true
...

At first I found the meaning of this rather confusing. Now I think of it as stack where to find .cabal files for packages outside of stackage:

Specifying a particular commit in the repo seems to work better with respect to upgrades than just specifying the master tarball.

There’s also the extra_dep: true line, which means that stack should treat the GitHub location as a dependency i.e. more like the things we get from stackage rather than as a addition to the local project files.

Having added those lines to slack.yaml, it all just works.

The extra-project case

As discussed above, stack handles project-free files by effectively putting them into a special global-project. So, you might think that all you need to do is add the location to the global-project/slack.yaml file. You do!

What you must not do is also add the location: . line as well. If you do slack seems to think that global-project is just a normal project, and so tries to read a .cabal file. This fails and slack tells you, but I didn’t quite understand the issue5.

Thanks to Michael Sloan for explaining my mistake so quickly.

Explicitly then, here are the steps I needed to take: