haskellhaskell-stackcabal-install

Stack-style sandboxed Haskell/GHC builds without dependency pinning


I like a lot of things about Stack, and in particular that I can ask it to use a specific version of GHC and it will ensure that version is always used to build that particular instance of a project, including downloading it if necessary, regardless of what version of GHC happens to be in my path. This is what I believe it calls "sandboxed builds."

However, unless I'm mistaken, Stack appears to be designed such that every dependency must be pinned to a specific version via settings in stack.yaml, either implicitly via the snapshot/resolver, or explicitly via extra-deps.

During active development (as opposed to building a release version of something), I prefer to attempt builds with the latest versions of all my dependencies so that I am regularly testing new versions of them and finding out quickly if a new version is not compatible with my code. (When I find out there's an issue with a new version, I can either fix the issue or, failing that, explicitly mark—in package.yaml or the .cabal file—versions from the new one upward as not being compatible with my code.)

Is there some way to do this with Stack? If not, does anybody have suggestions for being able to automatically download requested versions of GHC and maintain the "sandboxed" quality of builds that Stack gives you using other means?

I've looked at GHCup for this, but while it seems to do a fine job of downloading and making available multiple versions of GHC and Cabal, it does not provide any facilities for sandboxing and in fact seems to work against it in a couple of ways:

  1. GHCup determines which version it provides when you type cabal, ghc etc. with a setting global to the user, so you can't be sure that when you do a cabal build in project A that it's using the correct settings for that project, and not the different settings for project B you were working on in another window.

  2. The ~/.ghcup/env script puts the GHCup directories at the end of the path, meaning that any system-installed Cabal and/or GHC will take precedence over what GHCup provides.

Currently, the only solution that comes to mind is to use GHCup but provide a build/test/etc. script in my project and tell developers, "Never run cabal/ghc/ghci/whatever directly, but always run the script with parameters asking it to run the tool you need. This is not really any more awkward than having to type stack before the command you want, but it's obviously not going to be in the "muscle memory" of users the way stack is, and it's essentially replicating a certain amount of the work that stack already does.


Solution

  • You can use cabal for this use case rather than stack. As pointed out in the comments, snapshots are inherent to the way stack works.

    To set a compiler version, create a file cabal.project at the root of your project with the path to a Haskell compiler:

    with-compiler: ghc-9.6
    

    Now cabal build within the directory will use the chosen compiler for builds and resolve dependencies accordingly.