Why is cabal install foo --lib frowned upon?

I know it ties up the global namespace by importing the library into GHC, but I typically use it to initialize a package database.

For instance, I just set up a new 8.4.4 installation, I tried cabal install acme-dont, then tried to ghci and :set -package acme-dont.

There is no package environment visible to ghci, so it returns an error message.

So, I then cabal install acme-dont --lib, and thus set up a package environment.

Everything else is then installed without --lib, only being made visible via :set -package acme-dont.


If this isnā€™t a smelly practice, is there then a space for a nullary package in Hackage? From what Iā€™ve seen, itā€™s possible to get cabal to build a library with NO modules at all specified.

i.e, hackage-debug-nullary-package would then be specified such that it has no modules at all, and can be used to spawn a package environment via cabal.

Has this been done before?

Whatā€™s the actual use case for this? Typically, I would do stack ghci --package PKG to get into a quick shell with access to a dep. Iā€™m sure cabal has similar functionality

1 Like

Yup, cabal repl --build-depends PKG.

3 Likes

I think this comes down to whether or not you want a package environment file.

The advantages of having a package environment file:

-Directly starting ghci is much faster than cabal repl --build-depends foo.

-Ability to load packages via :set -package foo; for instance, letā€™s say youā€™re using twain, and all of a sudden you decide to recycle the ghci window for flatparse, not losing any draft functions in ghci. With cabal repl --build-depends foo, you donā€™t have an environment file and therefore cannot :set -package flatparse.


In actuality, so far, I think the better way to do this might simply be:

cabal install base --lib.

Since base is already installed by default, this amounts to no occupation of the package or module namespace whatsoever, takes no download time, etc.

The only concern I might have is that there might still yet be a better way to spawn a package environment, an actually canonical one. Iā€™m going through the cabal docs right now for such.

Personally I find anything polluting global environmental objectionable.

What do you think of cabal script style of declaring dependency in the same file: 6. cabal-install Commands ā€” Cabal 3.4.0.0 User's Guide

Itā€™s a different workflow: you create a scratch pad file ā†’ declare some dependency and cabal flags in it ā†’ :!cabal repl %. Itā€™s some upfront ā€œsetup costā€, but at least your repl session is more versatile with stuff you need always loaded even after rebooting.

4 Likes

Itā€™s mostly frowned upon because it can cause cabal hell issues if itā€™s all you use for package management. So in a way, itā€™s historical more than an ironclad, proven rule imo.

I honestly donā€™t think thereā€™s anything wrong with globally installing libraries you commonly use with ghci.

Now, if youā€™re on NixOS, you might as well just install a ghcWithPackages and maintain it that way. Compared to the other options in this thread, this is the only ā€œmore pureā€ one I know of that actually matches globally installing packages (e.g. itā€™s straight ghci, you donā€™t have to build packages periodically).

If you always use cabal install --lib --package-env=. to employ a directory-local environment instead of the global one, you may avoid the main issue (the cabal hell) with polluting the global one.

By the way, the upcoming cabal 3.12 install --lib will feature some improvements that should accommodate a number of common use cases like bumping the version of a package that has already been added to the environment.

Still, install --lib has a number of limitations. The most prominent imo is inability to use profiling versions of libraries.

3 Likes

cabal-installā€™s solver give prefence to already installled packages. Therefore changing the global packages database can potentially change the build plan of all projects you build.

TBH this is a reproducibility failure mode I had not thought about before.

2 Likes

Iā€™m surprised v2 builds behave this way - feels contradictory toā€¦their whole sales pitch?

ĀÆ_(惄)_/ĀÆ

I didnā€™t have time to test this today but AFAIK the global package db is always in scope (because thatā€™s where you get base, for example)

Ninja edit: nix style builds are not nix builds though :slightly_smiling_face: Thereā€™s a bunch of system stuff that is not (and can not) be captured in cabal-hash.txt

sounds disturbingā€¦ I will give it a try later

1 Like

Iā€™d be interested to know your conclusion. You may also be interested in How can environment file get into invalid state? Ā· Issue #8473 Ā· haskell/cabal Ā· GitHub.

Here are some of my findings:

$ cabal install --lib tuple
$ ghci
Loaded package environment from /home/hellwolf/.ghc/x86_64-linux-9.6.2/environments/default
GHCi, version 9.6.2: https://www.haskell.org/ghc/  :? for help
Loaded GHCi configuration from /home/hellwolf/Applications/hwutils.repo/hw.dotfiles/files/ghci
Ī» :set -package tuple
package flags have changed, resetting and loading new packages...
Ī» import Data.Tuple.Curry
...
$ cat /home/hellwolf/.ghc/x86_64-linux-9.6.2/environments/default
clear-package-db
global-package-db
package-db /home/hellwolf/.cabal/store/ghc-9.6.2/package.db
package-id tuple-0.3.0.2-62430523e24213173449b37742193be893eca90faab2241d948f3c006f50683a
$ # errrrr, how to delete package ???
$ rm -f /home/hellwolf/.ghc/x86_64-linux-9.6.2/environments/default
$ ghc-pkg --package-db /home/hellwolf/.cabal/store/ghc-9.6.2/package.db list
... # this is fun too

Anyways, it doesnā€™t seem to be too magical, good to know how it works. But I donā€™t think I would ever use or rely on this.

References:

1 Like

tl;dr: yeah but nah, cabal install --lib does not install packages in the global packagedb; this is what Setup.hs and cabal v1-install used to do.

Letā€™s give this a go.

I am using GHC 9.4.6, and the global packagedb has mtl-2.2.2 preinstalled.

āÆ ghc-pkg list mtl
/home/andrea/.ghcup/ghc/9.4.6/lib64/ghc-9.4.6/lib/package.conf.d
    mtl-2.2.2

Note that a newer version, mtl-2.3.1, is available from Hackage. Letā€™s make a package that declares a dependency on mtl>=2.2.2.

āÆ cabal init --non-interactive --minimal --depend "base,mtl>=2.2.2"
[Log] Using cabal specification: 3.0
[Warning] unknown license type, you must put a copy in LICENSE yourself.
[Log] Creating fresh file CHANGELOG.md...
[Log] Creating fresh directory ./app...
[Log] Creating fresh file app/Main.hs...
[Log] Creating fresh file cabal-global-pkg-db.cabal...
[Warning] No synopsis given. You should edit the .cabal file and add one.
[Info] You may want to edit the .cabal file and add a Description field.

The solver prefers the installed version of mtl-2.2.2.

āÆ cabal build --dry-run -v3 | grep '^\['
["/home/andrea/.ghcup/ghc/9.4.6/bin/ghc-pkg-ghc-9.4.6","/home/andrea/.ghcup/ghc/9.4.6/bin/ghc-pkg-9.4.6","/home/andrea/.ghcup/ghc/9.4.6/bin/ghc-pkg","/home/andrea/.ghcup/bin/ghc-pkg"]
[__0] trying: cabal-global-pkg-db-0.1.0.0 (user goal)
[__1] trying: base-4.17.2.0/installed-4.17.2.0 (dependency of cabal-global-pkg-db)
[__2] trying: rts-1.0.2/installed-1.0.2 (dependency of base)
[__3] trying: ghc-prim-0.9.1/installed-0.9.1 (dependency of base)
[__4] trying: ghc-bignum-1.3/installed-1.3 (dependency of base)
[__5] trying: mtl-2.2.2/installed-2.2.2 (dependency of cabal-global-pkg-db)
[__6] next goal: transformers (dependency of mtl)
[__6] trying: transformers-0.5.6.2/installed-0.5.6.2
[__7] done

The solver knows that mtl-2.3.1 is available and we can explicictly ask for it.

āÆ cabal build --dry-run -v3 --constraint 'mtl==2.3.1' | grep '^\['
[__0] trying: cabal-global-pkg-db-0.1.0.0 (user goal)
[__1] trying: base-4.17.2.0/installed-4.17.2.0 (dependency of cabal-global-pkg-db)
[__2] trying: rts-1.0.2/installed-1.0.2 (dependency of base)
[__3] trying: ghc-prim-0.9.1/installed-0.9.1 (dependency of base)
[__4] trying: ghc-bignum-1.3/installed-1.3 (dependency of base)
[__5] next goal: mtl (dependency of cabal-global-pkg-db)
[__5] rejecting: mtl-2.2.2/installed-2.2.2 (constraint from command line flag requires ==2.3.1)
[__5] trying: mtl-2.3.1
[__6] next goal: transformers (dependency of mtl)
[__6] trying: transformers-0.5.6.2/installed-0.5.6.2
[__7] done

Now, if I do cabal install --lib mtl-2.3.1, what happens?

āÆ cabal install --lib mtl-2.3.1
Resolving dependencies...
āÆ ghc-pkg list mtl
/home/andrea/.ghcup/ghc/9.4.6/lib64/ghc-9.4.6/lib/package.conf.d
    mtl-2.2.2

Where did it go!?

It turns out I have been wrong all along. cabal install --lib does not install packages in the global packagedb!

  • ./Setup.hs install would register packages in the global packagedb by default:
  • cabal v1-install would register packages in the user packagedb (by default, I think you can pass --global)
  • and cabal install --lib registers packages in a ghc environment
2 Likes

As far as I know this only happened with v1- commands. v2- will only use installed packages if they happen to match the version in the build plan, which is produced independently from the package database

1 Like

No reason to panic, cabal install --lib does not touch the global packagedb.

I am afraid thatā€™s not really how it works. The solver does take into account the pre-installed packages in the global package db. Thatā€™s where the familiar rejecting: base-4.18.0.0/installed-4.18.0.0 comes from! :joy:

Donā€™t get me wrong, the solver will still pick a source package if it is needed to satisfy a constraint. In the example above, cabal-global-pkg-db.cabal says it is ok with mtl>=2.2.2 so the solver thinks ā€œif the installed mtl-2.2.2 is good why bother downloading and compiling a different (newer in this case) versionā€.

Letā€™s test this by messing up the global packagedb. I used GHCup to get an extra installation of GHC so not to mess up with the one I use everyday.

āÆ ghcup install ghc 9.4.7 --isolate ~/Scratchpad/ghc-9.4.7-temp
...
[ Info  ] GHC installation successful

As before:

āÆ /home/andrea/Scratchpad/ghc-9.4.7-temp/bin/ghc-pkg list mtl
/home/andrea/Scratchpad/ghc-9.4.7-temp/lib64/ghc-9.4.7/lib/package.conf.d
    mtl-2.2.2

āÆ cabal build --dry-run --with-compiler /home/andrea/Scratchpad/ghc-9.4.7-temp/bin/ghc -v3 | grep '^\[_'
[__0] trying: cabal-global-pkg-db-0.1.0.0 (user goal)
[__1] trying: base-4.17.2.0/installed-4.17.2.0 (dependency of cabal-global-pkg-db)
[__2] trying: rts-1.0.2/installed-1.0.2 (dependency of base)
[__3] trying: ghc-prim-0.9.1/installed-0.9.1 (dependency of base)
[__4] trying: ghc-bignum-1.3/installed-1.3 (dependency of base)
[__5] trying: mtl-2.2.2/installed-2.2.2 (dependency of cabal-global-pkg-db)
[__6] next goal: transformers (dependency of mtl)
[__6] trying: transformers-0.5.6.2/installed-0.5.6.2
[__7] done

Now letā€™s install mtl-2.3.1 in the global packagedb (v1-install doesnā€™t use cabal.project so I used --with-compiler for both v1 and v2 commands and --global will cause install to use /usr/local as a prefix so I had to specify something else).

āÆ cabal v1-install --global --with-compiler /home/andrea/Scratchpad/ghc-9.4.7-temp/bin/ghc --prefix=/home/andrea/Scratchpad/ghc-9.4.7-temp-prefix mtl-2.3.1
...
Completed    mtl-2.3.1

āÆ /home/andrea/Scratchpad/ghc-9.4.7-temp/bin/ghc-pkg list mtl
/home/andrea/Scratchpad/ghc-9.4.7-temp/lib64/ghc-9.4.7/lib/package.conf.d
    mtl-2.2.2
    mtl-2.3.1

And indeed:

āÆ cabal clean && cabal build --dry-run --with-compiler /home/andrea/Scratchpad/ghc-9.4.7-temp/bin/ghc -v3 | grep '^\[_'
[__0] trying: cabal-global-pkg-db-0.1.0.0 (user goal)
[__1] trying: base-4.17.2.0/installed-4.17.2.0 (dependency of cabal-global-pkg-db)
[__2] trying: rts-1.0.2/installed-1.0.2 (dependency of base)
[__3] trying: ghc-prim-0.9.1/installed-0.9.1 (dependency of base)
[__4] trying: ghc-bignum-1.3/installed-1.3 (dependency of base)
[__5] trying: mtl-2.3.1/installed-C6BZ4nXXpU71PMyBnd8tMy (dependency of cabal-global-pkg-db)
[__6] next goal: transformers (dependency of mtl)
[__6] trying: transformers-0.5.6.2/installed-0.5.6.2
[__7] done
2 Likes

So, in general, I can use cabal install base --lib to generate an environment file (unless thereā€™s a safer way to do so), then use cabal install foo without --lib to populate it?

Not only am I calling ghci, Iā€™m also calling ghc directly to compile files, with --package flags, for simple and dumb little scripts that in my view, donā€™t warrant the ceremony of cabal or stack init and setting up the foo.cabal or package.yaml, and the associated cabal.project etc files.

Why canā€™t you use cabal/stack script? You just add a shebang and a comment to the file and executing the file will run it through cabal/stack with the given deps

1 Like

Cabal actually has an appreciable startup time on Arch, at least with my tooling. Thereā€™s also the appeal of minimalism; i.e, doing it with minimal tooling.

Thatā€™s fair. To your first point, I would hope that cabal script would cache the eecutable and reuse it. Does it not?