Testing GHC release candidates with Stack

by Taylor Fausak on

Yesterday Ben Gamari announced the second GHC 8.2.1 release candidate on behalf of the GHC team. Release candidates allow the community to test new versions before they’re officially released. Using the new compiler or interpreter directly is easy enough, but what about building an entire project? How can a Haskell developer test their code with the latest release candidate? I’m going to show you how to use Stack to do just that.

Compiler version

Assuming your project already uses Stack, your stack.yaml probably looks something like this:

resolver: lts-8.14

You may be using a different resolver or have some other stuff in there. That’s fine! We’re going to update your stack.yaml to include information about the release candidate. For starters we’ll tell it to use the GHC 8.2.1-rc2 compiler.

# Add this to your project's existing stack.yaml file.
compiler: ghc-8.2.0.20170507
compiler-check: match-exact

Don’t be fooled by the compiler version! ghc-8.2.0.20170507 is actually GHC 8.2.1-rc2. Note that we tell Stack to match the compiler version exactly. If we didn’t, it would try to use the previous release candidate if you had it installed.

Setup information

At this point you won’t be able to build you project. Let’s see what happens when you try.

$ stack test
No compiler found, expected exact version ghc-8.2.0.20170507 (x86_64) (based on resolver setting in .../stack.yaml).
To install the correct GHC into .../x86_64-linux/, try running "stack setup" or use the "--install-ghc" flag.

$ stack setup
No information found for ghc-8.2.0.20170507.
Supported versions for OS key 'linux64': GhcVersion 7.8.4, GhcVersion 7.10.1, GhcVersion 7.10.2, GhcVersion 7.10.3, GhcVersion 8.0.1, GhcVersion 8.0.2

Stack is telling us that we don’t have the correct compiler installed. Furthermore it doesn’t know how to install the version we want. That’s okay! We can tell Stack where to find the compiler we want.

# Add this to your project's existing stack.yaml file.
setup-info:
 ghc:
  linux64:
   8.2.0.20170507:
    url: https://downloads.haskell.org/~ghc/8.2.1-rc2/ghc-8.2.0.20170507-x86_64-deb8-linux.tar.xz
  macosx:
   8.2.0.20170507:
    url: https://downloads.haskell.org/~ghc/8.2.1-rc2/ghc-8.2.0.20170507-x86_64-apple-darwin.tar.xz
  windows64:
   8.2.0.20170507:
    url: https://downloads.haskell.org/~ghc/8.2.1-rc2/ghc-8.2.0.20170507-x86_64-unknown-mingw32.tar.xz

Installation

Now that we’ve told Stack where to find the compiler we can run stack setup to download and install it.

$ stack setup
Preparing to install GHC to an isolated location.
This will not interfere with any system-level installation.
No sha1 found in metadata, download hash won't be checked.
Downloaded ghc-8.2.0.20170507.               
Unpacking GHC into .../ghc-8.2.0.20170507.temp/
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

Let’s make sure everything worked by running ghc and ghci through Stack.

$ stack exec -- ghc --version
The Glorious Glasgow Haskell Compilation System, version 8.2.0.20170507

$ stack exec ghci
GHCi, version 8.2.0.20170507: http://www.haskell.org/ghc/  :? for help
Prelude> :quit
Leaving GHCi.

Great! The new compiler works.

Resolvers

Before we move on, a brief note about resolvers. We’re telling Stack to use the latest compiler along with a resolver meant for the previous compiler. This is convenient because most packages will build against the latest compiler without changing anything, but it is a little sloppy. We can tell Stack to only use built-in packages by setting resolver: ghc-8.2.0.20170507. Then we can add other packages using stack solver. However I won’t be walking through how to do that because it’s pretty tedious.

Version constraints

If we try to build our project we’ll probably see something like this.

$ stack build
Error: While constructing the build plan, the following exceptions were encountered:

In the dependencies for array-0.5.1.1:
    base-4.10.0.0 must match >=4.5 && <4.10 (latest applicable is 4.9.1.0)
needed due to rattletrap-2.2.4 -> array-0.5.1.1

Plan construction failed.

The exact message will differ based on your project’s dependencies, but the idea is the same. The new version of the compiler ships with a new version of the base package. Many other packages claim they won’t work with that. Fortunately we can tell these packages to ignore their version constraints and try to build against the new version anyway. Simply add this to your stack.yaml.

# Add this to your project's existing stack.yaml file.
allow-newer: true

Now when you try to build your project you’ll see some warnings.

$ stack build
WARNING: Ignoring out of range dependency (allow-newer enabled): base-4.10.0.0. array requires: >=4.5 && <4.10
...

But hopefully it will start downloading, configuring, and building some dependencies.

Troubleshooting

It’s pretty much guaranteed that you’ll try to build the deepseq package. Unfortunately it won’t build with this release candidate out of the box. You’ll see this error.

.../deepseq-1.4.2.0/Control/DeepSeq.hs:420:10: error:
    • Illegal instance declaration for ‘NFData TypeRep’
        (All instance types must be of the form (T t1 ... tn)
         where T is not a synonym.
         Use TypeSynonymInstances if you want to disable this.)
    • In the instance declaration for ‘NFData TypeRep’
    |
420 | instance NFData TypeRep where
    |          ^^^^^^^^^^^^^^

You may be thinking that we’re stuck, but we actually have two ways out of this. The first way is to do what GHC suggests and tell deepseq to enable the TypeSynonymInstances language extension. We can do this by adding the following to our stack.yaml.

# Add this to your project's existing stack.yaml file,
# but prefer the next approach.
ghc-options:
 deepseq: -XTypeSynonymInstances

This enables the language extension for the entire package, so it’s a pretty heavy hammer. If we had to do this with an extension that could change program semantics, like OverloadedStrings, then this probably wouldn’t work because it would introduce other build failures.

That brings us to our second way out of this: pulling the dependency from Git. This problem has already been fixed in deepseq but a new version hasn’t been released. Fortunately we can tell Stack to grab it from GitHub.

# Add this to your project's existing stack.yaml file.
packages:
 - .
 - extra-dep: true
   location:
    git: https://github.com/haskell/deepseq
    commit: 0b22c9825ef79c1ee41d2f19e7c997f5cdc93494

You should prefer this solution because it’s generally more robust and can solve any problem, not just ones that can be fixed by GHC options.

Remember that you can always fork a package on GitHub to add support for a release candidate and point Stack to your fork. In fact, this is a great way to contribute to the Haskell ecosystem. Once your fork works with the release candidate, open a pull request against the original repository. Then the next person won’t have to fix it themselves!

Conclusion

With that, you should be able to test your Haskell project against GHC 8.2.1-rc2 using Stack. If your project has a lot of dependencies, you may need to add a lot of extra-deps to get it building. If you run into any problems with the release candidate, be sure to report a bug!