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": "multipleComponents"

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.