haskell / cabal

Official upstream development repository for Cabal and cabal-install
https://haskell.org/cabal
Other
1.62k stars 693 forks source link

[UX] Current mechanism to install several libraries and expose them to the user? #9581

Open ivanperez-keera opened 10 months ago

ivanperez-keera commented 10 months ago

One of Haskell's strongest points is ability to create DSLs, including languages meant for users who did not need to or want to learn Haskell, or the intricacies of our tools.

Prior to the removal of sandboxes, DSLs defined in libraries could easily be made available to the user in a controlled way:

cabal v1-sandbox init
cabal v1-install all_subdirs # or package names
cabal v1-exec -- runhaskell DSLExample.hs

This was excellent especially for users who were not proficient in Haskell (nor did they need to be), since Haskell was just the vehicle to get things running and they only needed to install the libraries once. What they cared about was just being able to run their examples. Such users did not need to or care about cabal, nor did they need to learn it.

Modules were also simple, there were no haskellisms in them. See for example, a Copilot module: https://github.com/Copilot-Language/copilot?tab=readme-ov-file#examples

We could almost alias:

alias copilot='cabal v1-exec -- runhaskell'

Adjusting the PATH and SANDBOX_DIR, we could even enable that to run from anywhere. It was a great user experience with minimal effort on the user side.

If a user messed up, the way to guarantee that they were exactly back to the starting point was trivial: rm -rf .cabal-sandbox and re-install. With only two installation instructions (v1-sandbox init and v1-install <packages>), it didn't get much smoother than this. Doing so also did not affect any other Haskell packages installed on that machine.

Since the removal of sandboxes, we've struggled to deliver a similar user experience with v2-. To this day, I have no idea how to install copilot from the repository directly without exposing the users to added complexity and more haskellisms.

For example, we could ask them to create a cabal.project every time they want to run a Copilot file. But now there's another file they need to create, every time they want to run an example.

We could also tell them to do:

cabal v2-install --lib copilot

but AFAIK installing libraries globally is discouraged (?).

We could ask them to create a cabal file. But that is a HUGE ask and exposes them to an unbearable amount of complexity. Our target users will simply refuse to use Copilot if that's a requirement.

We could ask them to add the dependencies at the top of their file, with something like:

#!/usr/bin/env cabal
{- cabal:
build-depends: base, copilot
-}

but that now exposes a very ugly element to them that they neither understand nor they care about.

I've been trying to make the user experience simple for our users. Ideally, they could install the DSL libraries once and forget about them. (I've tried to create a Homebrew formula unsuccessfully).

Although I am using Copilot as an example, I think this applies beyond the specifics of that project. One of the reasons why we cannot offer more EDSLs is precisely because installation becomes a show-stopper.

The ability to support EDSLs has been one of Haskell's strongest suites for decades, but it only goes so far as the user experience surrounding such languages is devoid of complexity.

What is the current mechanism to enable such use cases?

Thanks!

ffaf1 commented 10 months ago

We could ask them to create a cabal file. But that is a HUGE ask and exposes them to an unbearable amount of complexity. Our target users will simply refuse to use Copilot if that's a requirement.

Could the package be created non-interactively?

cabal init -n --dependency=…

After that, running should be easy.

Other than that, an idea could be a wrapper that adds the relevant dependencies to file, so the user has just to put:

#! /usr/bin/env cabal-wrapped

(that is easier if dependencies are fixed, more complex if users want to change them. What do you think?

Thanks for opening this, UX experiences and suggestions are always welcome.

fgaz commented 10 months ago

We could also tell them to do:

cabal v2-install --lib copilot

but AFAIK installing libraries globally is discouraged (?).

It is, but nothing is stopping you from doing it if it suits your workflow. After all, removing the global environment is easy (remove ~/.ghc/*/environments/default). It's "only" a problem when you don't know/remember it's there and it's causing other stuff to fail. You can also create named or local environments with the --env flag. These are safer since they only have a localized effect.

Named environment:

cabal install --lib --env=foo-env foo
# requires the use of -package-env in ghc calls
runhaskell -package-env=foo-env UsesFoo.hs
# remove the named environment
rm ~/.ghc/$arch-$os-$version/environments/foo-env

Local environment:

cabal install --lib --env=. foo
# has to be called in the directory where the environment was created
runhaskell UsesFoo.hs
# remove the local environment
rm .ghc.environment.*

Finally, help towards improving the --lib experience is always welcome: #6481

fgaz commented 10 months ago

Oh, and the other caveat is that adding packages one by one (ie. modifying an existing environment) might produce an inconsistent environment. Adding them all in a single command (cabal install --lib foo bar baz...) is best. I thought we had an issue about that but the closest I can find are #5559 and #8483 (both closed). I think what I'm describing is https://github.com/haskell/cabal/issues/8483#issuecomment-1265457463.

edit: filed #9582

ulysses4ever commented 10 months ago

I double the local package environments based approach. There’s also cabal-env — a stand-alone tool with more polished interface I think. But I never used it.

cabal install --lib --env=. foo

wow, didn’t know about --env and used --package-env for ages — I wasted so many keystrokes!..

ivanperez-keera commented 9 months ago

the other caveat is that adding packages one by one (ie. modifying an existing environment) might produce an inconsistent environment. Adding them all in a single command (cabal install --lib foo bar baz...) is best.

Not that we need to do anything about it (at least not urgently), but this is not always possible when cabal does not understand that one of the packages provides a tool that another package needs. It'd be great if there was a way to tell cabal that a certain package has to be installed as early as possible.

Cabal has changed a lot in recent years, maybe specifying any tool dependencies is now possible. At least last time I checked, it only knew about specific tools.

fgaz commented 9 months ago

Cabal has changed a lot in recent years, maybe specifying any tool dependencies is now possible.

Indeed :)

okeuday commented 9 months ago

The removal of sandboxes (https://github.com/haskell/cabal/issues/6445) never had replacement functionality and that has impacted different cabal-install use in different ways. Ignoring the use in https://github.com/haskell/cabal/issues/8343 , I also found sandboxes helpful when testing source code with multiple ghc versions (each using separate cabal-install executables) because the dependencies remain isolated in a way that is very visible (for understanding problems and determining dependency requirements).

I do not understand the past cabal-install sandbox discussions that attempt to say "You don't need sandboxes" (the past discussions have shown there is need and there has never been clear justification for removal), but I understand that is the current direction of cabal-install.

I have used ghc --make directly instead of cabal-install, with Setup configure --package-db=${SANDBOX} to continue using sandboxes and that approach should not break in the future. That remains an option if you need to continue using sandboxes.

gbaz commented 9 months ago

The removal of sandboxes (https://github.com/haskell/cabal/issues/6445) never had replacement functionality

Again, the intended replacement functionality is cabal v2-build with local env files, as detailed above.

I understand that local env files have had problems and frustrations in the past, and we have been working to attempt to improve the user experience.

If there is a specific use case for sandboxes that cannot be replicated with local env files, please file a ticket about it directly.