At ZuriHac 2024 we (@andreabedini, myself and others) talked with @ivanperez-keera and @fdedden about the user experience of using GHC and cabal for people who are not Haskell developers and are not interested in Haskell on its own, but rather use it to:
- build a package
- use a framework/library that requires Haskell (e.g. copilot)
These users are not interested in:
- nix
- Haskell
- cabal
- content addressable storage
- declarative environment configuration
The cabal v2
commands are assuming a lot of these things UX-wise.
We are well aware that there are ways to utilize store location and environment files to achieve something similar. But this misses a consistent UX experience.
We propose here an imperative workflow of managing haskell packages.
- the user asks cabal to
install
a package - cabal makes this package visible to
- ghc/ghci
- cabal itself
- building a project (and similar operations) will re-use the already installed packages
This follows the workflow of 90% of Linux distributions. It usually disallows having multiple versions of the same library.
The sandbox workflow is an extension of the imperative workflow. A sandbox is scoped to a specific directory (and its subdirectories). Installing packages inside the sandbox only makes them available within that directory. A sandbox can be conveniently created and conveniently destroyed. A user would rather destroy a sandbox and start from scratch rather than try to fix it. This is outlined in the following workflow:
- user clones e.g. copilot
- user creates a sandbox inside the copilot repo
- user installs the necessary Haskell libraries and the copilot library
- user uses a Makefile to link to copilot and other libraries (compare with bluespec, which does the same)
- user upgrades the project
- user destroys the sandbox
- repeats from step 2
A similar workflow can be imagined with an actual cabal package that is developed ad-hoc iteratively.
Users who want maximum caching and declarative configuration can use the existing -v2 interface instead.
We have considered the following:
- utilize -v1 to implement sandboxes (probably similar to the old implementation)
- utilize -v2 to implement sandboxes
A possible implementation would be:
cabal sandbox init
would e.g. augmentcabal.project.local
with several configurationswrite-ghc-environment-files: always
store-dir: .cabal-sandbox
(this doesn't seem to be supported yet through the cabal project file)package-env: .
(seems to be not supported either through cabal project file)
cabal install
would then- use the sandbox store dir
- use the current dir as package env and write the configuration file
- also add
constraints: <pkg-name> installed
tocabal.project.local
cabal sandbox destroy
would- delete the
.cabal-sandbox
directory - delete the ghc environment file
- delete
cabal.project.local
- delete the
This would ensure consistency between directly invoking ghc and using cabal to actually build packages.
It would also allow to move the created constraints to cabal.project
file instead. We could also imagine slight variants of this idea where we utilize yet another project file with a different suffix if cabal.project.local
is deemed inappropriate.
An alternative implementation could create a sandbox config file that then triggers sandbox behavior in cabal (through cabal checking for its existence). This is partly implemented here: hasufell/cabal#3 But I consider it more controversial. However it is more opaque.