Over the last few years we have been working on implementing support for
multiple components (sometimes referred to as multiple home units) across
various parts of the ecosystem. This is a feature which allows you to load
multiple components, which may depend on each other, into a single GHC session.
For instance, you can load both a library and a testsuite into a single GHCi
session, and :reload
commands will pick up changes across both components. In
this post we will discuss how you can take advantage of this support in the
Haskell Language Server.
For the motivation behind the feature and a sample of the problems it solves, see Hannes’ blog post. We previously discussed the particulars of the GHC implementation and the details of our work teaching cabal to make use of this feature. With the release of cabal-install 3.12, this feature is finally available for broad adoption, hopefully representing a major improvement to the Haskell development workflow.
Prerequisites
You need the following prerequisites to be able to take advantage of the multiple component support in HLS:
- GHC version 9.4 or later
- cabal-install version 3.12.0.0 or later
- HLS version 2.6.0.0 or later
In addition, the setup dependencies of your project, or any of its transitive dependencies, must use version 3.12.0.0 or newer of the Cabal library (which means any packages with Custom setup scripts in your dependency tree must be compatible with this version).
Enabling multi component support in HLS
HLS 2.6 and 2.7 will automatically try to use multi component support if the prerequisite versions of GHC and cabal-install are available. Starting with HLS 2.8, the support is disabled by default and has to be enabled by setting the following option in your LSP configuration:
"sessionLoading": "multiComponent"
We disabled the feature by default because we discovered that HLS did not handle
the setup dependency requirement gracefully: it would fail if any package in
your dependency tree did not support version 3.12.0.0 of the Cabal library. In
due course, we hope to re-enable this feature by default as more libraries
become compatible with newer versions of the Cabal
library.
The need for cabal-install 3.12
HLS has long supported a rather hacky implementation of multi package support, swapping out the environment that GHC consults for each package through careful usage of the GHC API. This was put on much firmer ground when explicit support for multiple packages was added to GHC in version 9.4, and HLS has been using the official mechanisms that GHC provides since.
The implementation in HLS nonetheless had several shortcomings, which could not
be resolved until we had support from the build tool (cabal-install
). To see
why, consider a project structure consisting of 3 distinct packages (or
components) A
, B
and C
, where A
depends on B
, and B
depends on C
.
Whenever you load a file into HLS, HLS consults cabal-install
to resolve any
dependencies and learn the GHC options necessary for loading the entire
component to which the file belongs. HLS has no visibility into the dependencies
and the build process other than through the GHC flags which cabal-install
provides.
If you start HLS by loading package C
, then package B
, and then perhaps
package A
, things will just work and you will not notice any issues. However,
if you load package C
and A
into HLS, but not package B
, you will quickly
run into trouble. The issue is that any changes to package C
should force a
recompilation of the files in package B
, but since this package is not loaded
into HLS, HLS has no idea how to compile package B
, or even locate the source
files for package B
.
In general of course there may be an arbitrary number of packages in between
package C
and A
which need to be recompiled: all components in between
components which are loaded into GHC must also be loaded into GHC. We refer to
this property as the “closure property”, and it can only be upheld by the the
build system; cabal-install
3.12.0.0 is the first version of cabal-install
that is able to do this, and hence provide HLS with the correct package
environment no matter the order that the user loads components into HLS.
Conclusion
We hope that this feature will make your Haskell developer experience much more pleasant and seamless. Please make sure to report any bugs or issues that you may encounter to the cabal issue tracker or the HLS issue tracker. Well-Typed is also available for commercial support for these tools, either through bespoke constracts for specific features, or through our general Haskell Ecoystem Support Packages; feel free to contact us at info@well-typed.com for more details.
We are grateful to Hasura and the HLS Open Collective for sponsoring the work described in this blog post.