Haste and Cabal files
When creating a Haskell project using Haste, it’s not all that uncommon to want that project – or at least parts of it – to be buildable with vanilla GHC as well. Considering that Haste.App, which is shipped with the standard Haste distribution, more or less requires this functionality to work at all, this is quite a reasonable requirement.
Since Cabal (or stack if you want to be all cool and up to date and stuff) is the build system for Haskell projects, you probably have a
.cabal file for your project:
name: myapp version: 0.1.0.0 author: Jane Doe maintainer: firstname.lastname@example.org build-type: Simple cabal-version: >=1.10 executable myapp main-is: Main.hs build-depends: base >= 4.8 && < 4.9, haste-lib >= 0.5 && < 0.6 default-language: Haskell2010
Your application builds just fine when using
haste-cabal, but when you try to build it using vanilla
cabal, you get the following message:
Resolving dependencies... Configuring myapp-0.1.0.0... cabal: At least the following dependencies are missing: haste-lib ==0.5.*
That’s not good! What really went wrong here, and how can we fix it?
A stupid design (non-)decision
Once upon a time, we were in quite a rush to get Haste.App out the door in time for a paper deadline. In our haste, we did not spend too much time thinking about the package structure of its libraries. And so it came to pass that the server implementation of Haste.App ended up together with the compiler itself in the
haste-compiler package. Meanwhile, the client implementation of Haste.App found itself in the
haste-lib package, to avoid forcing users to install the
haste-compiler package twice, with its dependencies on GHC and a multitude of other packages with highly questionable relevance to web development.
At the time, this was not much of an issue: the paper was accepted, the programming model was well received, and everything was rainbows and unicorns. Unfortunately, one glaringly obvious and exceptionally annoying implication of this design decision (or rather, non-decision) became clear as soon as people started actually using Haste.App: Cabal will become very cross indeed when it fails to find
haste-lib on the server and refuse to compile the server part of Haste.Apps!
Fortunately however, the issue is quite easy to work around. Cabal lets you use conditionals over a number of properties – compile time flags, build platform, compiler used, etc. – in order to tailor your build to the present situation. When building a package with Haste, the Cabal expression
impl(haste) evaluates to
true, which, needless to say, it does not when building with vanilla GHC.
We can use this to tailor the dependencies of our package to the compiler being used:
name: myapp version: 0.1.0.0 author: Jane Doe maintainer: email@example.com build-type: Simple cabal-version: >=1.10 executable myapp main-is: Main.hs build-depends: base >= 4.8 && < 4.9 if impl(haste) build-depends: haste-lib >= 0.5 && < 0.6 else build-depends: haste-compiler >= 0.5 && < 0.6 default-language: Haskell2010
Note the new
if impl(haste) ... stanza. If we’re being built with Haste, we still want to depend on
haste-lib, while we want to depend on
haste-compiler instead if we’re being compiled by GHC. Any dependencies that are shared by the client and the server go into the first, unconditional
Similarly, it’s also possible to add branch on the particular version of Haste being used. By replacing
if impl(haste) ... with
if impl(haste > 0.4) ... for instance, we can different sets of dependencies, compiler flags, etc. depending on which version of Haste is in use. While not relevant to this particular workaround, different sets of dependencies or compiler flags may not work equally well for all versions of Haste (or GHC, or any other Haskell compiler for which Cabal implements this functionality for that matter); having this in the back of your head may come in handy one day.
…and that just about sums it up. An error caused by a silly oversight two years ago, still going strong! This just goes to show that you should always think (at least!) twice about the structure of your libraries. If you’re unlucky, you may well be stuck with some embarrassing mistakes for quite some time.