hs
Towards a Common Haskell Development Environment
One of the projects we have been pursuing at the Haskell foundation is a unified Haskell installer. Hitherto things have been a little fragmented when it comes to Haskell installers, not covering Windows very uniformly and, of course, requiring different installers depending upon whether you want to work with stack
or cabal-install
. (I distinguish between the Cabal
API used by both stack
and the cabal-install
application, using the package names to keep everything straight.)
This is really unfortunate for many reasons.
Poor Windows coverage restricts access to Haskell, setting up a viscous cycle whereby the poorer the Windows tooling, the smaller the pool of Windows Haskell developers available to improve or even maintain it.
It reinforces a tendency to silo developers with those using Posix/Windows or
stack
/cabal-install
, contributing to a growing incomprehension of each other’s problems, even for those that actively want to see more uniformity in development environments. It is difficult to overstate the problems this causes.Even for those that are prepared to build their own unified development environment it is not currently practical to do so based on the commonly used tools — unless you are prepared to patch the tools and/or install wrapper scripts (see below) as the current tools will install toolchains without regard those that are currently installed. It is of course easy enough to install one along side the other for a specific use but they will manage their own duplicate toolchains without constant intervention.
Writing satisfactory Haskell introductory tutorials is nearly impossible to do. You have to either pick one of the build systems and cover that properly, most likely on Posix, or try to to cover everything in detail which will immediately restrict your audience, or abstract away from the details and your audience will get culled when it comes to trying things out.
This list isn’t comprehensive and you can probably add to it.
How to fix?
[Update 2021-07-14: I should have made clear in the original posting that what follows is merely my own technical proposal for integrating cabal-install
and stack
and not in any way a Haskell Foundation adopted proposal.]
Given the above, ideally, all Haskell installers like stack
and ghcup
should install both cabal-install
and stack
in such a way that they will use a toolchain that either installs, and while allowing the developer to control which tool-chain installer gets invoked regardless of whether you are building with stack
or cabal-install
from a clean checkout. (Currently only stack
will auto-install a missing toolchain but there is no reason why cabal-install
could not trigger an installation with the right hooks.)
Cross-installing the build tools is almost trivial (ghcup
is already unofficially doing it) — getting them to manage the toolchains coherently thereafter is the hard part.
A pragmatic approach could be taken as stack
and ghcup
each use entirely straightforward and stable organisations of their installed toolchains. Before each is about to install another toolchain they could check whether the other had already installed it and link it in rather than downloading and installing another GHC bindist. Perhaps predictably, nobody was keen on this solution, as they would be relying on undocumented interfaces going forward, clearly risking problems down the line.
I have come to see this is correct — we should build proper interfaces. It also opens up the opportunity to give the developer full control of which system manages the toolchain installations.
hs
: a tooolchain installation broker
Whenever cabal-install
or stack
wish to locate a toolchain (say, ghc-8.10.4
), and they have been configured to do so, they will call out to a new hs
broker service:
(Here we are exec
ing hs, but an internal hs
library call is also available.)
hs
responds with the location of the root of the installed bindist if it is already installed (in the case above, we clearly have a stack managed toolchain).
If the toolchain is not installed then, depending upon how hs
is configured, it will yield an error indicating that no such toolchain could be located, or it will auto-download-and-install the toolchain with the designated default toolchain installer, or it will ask the developer if they would like to download and install the required GHC bindist.
The build manager can specify this behaviour explicitly.
$ hs whereis --install ghc-8.10.4
$ hs whereis --no-install ghc-8.10.4
$ hs whereis --ask-install ghc-8.10.4
hs
: a PATH interface for cabal-install
Traditionally, cabal-install
uses the PATH
to resolve the location of the tools. If ghc-pkg
for GHC 8.10.4 is required then ghc-pkg-8.10.4
will be exec
ed. Exploiting this we can install a bunch of wrapper scripts on the PATH
containing variants of the following:
Instead of yielding the location of the bindist hs run ghc-pkg-8.10.4
runs the command directly passing through all of the arguments after the --
without further interpretation. The wrapper scripts can rely on the configured installation behaviour or specify it explicitly.
Of course, for this to work these wrapper scripts (in ~/.hs/bin
by default) must be on the path ahead of anything else that might have ghc-pkg-8.10.4
and friends.
configuring hs
and listing installations
The installation manager priorities, the default install mode and default toolchain can be configured or displayed (by specifying no argument) as follows:
# ghcup to carry out installations requested from the build tools
$ hs use ghcup stack
# ask before installing any missing toolchains
$ hs use-install-mode ask-install
# specify the deafult toolchain when no version specified
$ hs use-compiler 8.10.4
To list the installation in your development environment:
$ stack list
8.6.5 stack /Users/chris/.stack/programs/x86_64-osx/ghc-8.6.5
8.8.3 stack /Users/chris/.stack/programs/x86_64-osx/ghc-8.8.3
8.8.4 stack /Users/chris/.stack/programs/x86_64-osx/ghc-8.8.4
8.10.4 stack /Users/chris/.stack/programs/x86_64-osx/ghc-8.10.4
8.10.5 ghcup /Users/chris/.ghcup/ghc/8.10.5
9.0.1 ghcup /Users/chris/.ghcup/ghc/9.0.1
configuring the build tools
Currently stack
will use its internal GHC installations while cabal-install
will rely on the path. It would be great if their global configurations would allow the developer or the bootstrap Haskell installer to configure them to defer to specified broker as follows:
This would be bigger departure for cabal-install
but it should be straightforward for cabal-install
to make a call out to the broker when the need for a sandboxed toolchain is discovered with a configured broker, at which point cabal-install
could internally put the toolchain located by the broker on its path, thereby – when a broker is configured – relieving the developer of the need to manage the PATH
to capture the relevant toolchain.
Both build systems would have converged on the same behaviour when it comes to toolchain management.
a working prototype
I have hs
written, a patch for stack
and wrappers for cabal-install
, yielding a unified system in which all of the toolchains are made available to both systems as installations are demanded and added by stack
or ghcup
according to the hs
configuration. (This is Unix-only for now – Windows coverage should be straightforward and is in progress.)
Got an issue with any of this? Please share or drop me a line (see below).