From 1191a5d064617004251a1de04cccba328234f57a Mon Sep 17 00:00:00 2001 From: Tom Smeding Date: Sun, 22 Oct 2023 22:36:12 +0200 Subject: Add haskell/cabal-lib post --- haskell/cabal-lib.html | 134 ++++++++++++++++++++++++++++++++++ haskell/cabal-lib.md | 194 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 328 insertions(+) create mode 100644 haskell/cabal-lib.html create mode 100644 haskell/cabal-lib.md diff --git a/haskell/cabal-lib.html b/haskell/cabal-lib.html new file mode 100644 index 0000000..90bab1c --- /dev/null +++ b/haskell/cabal-lib.html @@ -0,0 +1,134 @@ +

About cabal install --lib

+

TL;DR: Don't use it, add the library to your package-name.cabal or package.yaml instead, or use a cabal script. After you learn more about the downsides, you can reconsider. See the "What to do instead" section below.

+
+

Suppose you are new to Haskell, or at least new to the current (2023) Haskell tooling, and would like to install a program written in Haskell. +For example, say you would like to install a Haskell formatter, say fourmolu, and find that installing Haskell packages uses a tool called cabal. +Hopeful, you try:

+
cabal install fourmolu
+
+

and, if you are patient, this may well succeed and give you a fourmolu executable.

+

So now you want to write some Haskell! +But you want to use a library, say brick, for making a terminal user interface (TUI). +So you go:

+
cabal install brick
+
+

which seems to proceed as before, compiling a bunch of dependencies. +(Note that in the past, this was a common way to install Haskell libraries for use in your own code, and quite a number of READMEs of older libraries still recommend this command.) +But at the end it prints this warning: (as of cabal-install 3.10.1.0)

+
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+@ WARNING: Installation might not be completed as desired! @
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+The command "cabal install [TARGETS]" doesn't expose libraries.
+* You might have wanted to add them as dependencies to your package. In this
+case add "brick" to the build-depends field(s) of your package's .cabal file.
+* You might have wanted to add them to a GHC environment. In this case use
+"cabal install --lib brick". The "--lib" flag is provisional: see
+https://github.com/haskell/cabal/issues/6481 for more information.
+
+

which looks scary, using the same kind of @@@@ banner as ssh reporting a possible man-in-the-middle attack (a changed host key, really). +But you want to use this library, after all, and you're just working in a single .hs file and aren't planning on creating a "package". +So you try the second suggestion:

+
cabal install --lib brick      # note, don't try this at home
+
+

and that seems to work -- and if you had let the previous cabal install brick command run to completion, it doesn't even seem to do much. +That much is true: it hasn't done much, but what it has done is probably not what you wanted.

+

For example, let's try to sanity-check our Haskell installation and start a REPL:

+
$ ghci
+Loaded package environment from /home/tom/.ghc/x86_64-linux-9.4.7/environments/default
+GHCi, version 9.4.7: https://www.haskell.org/ghc/  :? for help
+ghci> 1 + 2
+
+<interactive>:1:3: error:
+    Variable not in scope: (+) :: t0 -> t1 -> t
+ghci> print "hi"
+
+<interactive>:3:1: error:
+    Variable not in scope: print :: base-4.17.2.0:GHC.Base.String -> t
+ghci>
+
+

I mean, that doesn't look good, doesn't it?

+

And if that did not scare you enough, suppose that in the future, you want to use a newer version of brick and try to install that using cabal install --lib brick again. +What you'll see is this:

+
$ cabal install --lib brick-1.9
+Error: cabal: Packages requested to install already exist in environment file
+at /home/tom/.ghc/x86_64-linux-9.4.7/environments/default. Overwriting them
+may break other packages. Use --force-reinstalls to proceed anyway. Packages:
+brick
+
+

(I simulated the situation by installing an older version instead. I can't time-travel, unfortunately.)

+

Or suppose that you now also want to use another library, say eigen:

+
$ cabal install --lib eigen
+Resolving dependencies...
+Error: cabal: Could not resolve dependencies:
+[__0] next goal: brick (user goal)
+[__0] rejecting:
+brick-1.10/installed-1f76dfaf75736c0f6e2a4a2cf992bb12da05f6bbc7985f9787547739947e4696
+(package is broken, missing dependency
+bimap-0.5.0-2ee7565ca29f0edc78a3c8fe09c9eef36c96d08473660eec8a944cef16ac4d86)
+[__0] trying: brick-1.10
+[__1] trying: vty-5.39 (dependency of brick)
+...
+...
+
+

The error message is much longer than this, but I cut it off to save some space. +Apparently it claims our installation of brick is actually broken, despite it working okay in ghci, but in any case this didn't work.

+

What happened?

+

Note the line printed by ghci:

+
Loaded package environment from /home/tom/.ghc/x86_64-linux-9.4.7/environments/default
+
+

This file is the "GHC environment" from above that cabal wrote to. +It now contains this:

+
clear-package-db
+global-package-db
+package-db /home/tom/.cabal/store/ghc-9.4.7/package.db
+package-id brick-1.10-1f76dfaf75736c0f6e2a4a2cf992bb12da05f6bbc7985f9787547739947e4696
+
+

This means that when starting ghci, these, and no others, are the packages that are in scope:

+
$ ghci
+Loaded package environment from /home/tom/.ghc/x86_64-linux-9.4.7/environments/default
+GHCi, version 9.4.7: https://www.haskell.org/ghc/  :? for help
+ghci> :show packages
+active package flags:
+  -package-id brick-1.10-1f76dfaf75736c0f6e2a4a2cf992bb12da05f6bbc7985f9787547739947e4696
+
+

This does not include base; this is what produced the broken ghci above (which couldn't find (+) nor print).

+

You can "fix" this:

+
ghci> :set -package base
+package flags have changed, resetting and loading new packages...
+ghci> 1 + 2
+3
+ghci> :show packages
+active package flags:
+  -package base
+  -package-id brick-1.10-1f76dfaf75736c0f6e2a4a2cf992bb12da05f6bbc7985f9787547739947e4696
+
+

and you can even make that change permanent with cabal install --lib base, which adds (in my case) a line package-id base-4.17.2.0 to the aforementioned default file.

+

In short, a GHC environment file was created by cabal that contains a list of packages in scope for ghc when you're accessing GHC through cabal in the context of a project. +You need to either manage this file manually or through some helper tool, and you will need to, because cabal won't resolve conflicts for you. +You're back to manual, imperative management of dependencies.

+

Furthermore, this way of installing dependencies is fundamentally separate from the code that uses those dependencies, and is just one such list. +So if you have multiple scripts that you'd like to work globally, outside of a project, their dependency sets had better be compatible.

+

How to fix the situation

+

If you got yourself in a pickle due to an unintended use of cabal install --lib, you can undo its effects (apart from having used some disk space in compiling the packages in question) by removing the default file mentioned above. +This is in ~/.ghc/architecture-OS-ghcversion/environments/default.

+

As mentioned, the compiled packages are still around (in ~/.cabal/store/ghc-version/), but removing those is tricky -- do not try it, cabal likes to maintain its own consistent set of packages in the "store". +Removing the entire store folder for a particular GHC version is safe, however -- even though this does of course mean that you may need to recompile a lot of things later. :)

+

What to do instead

+

Create a project! +The intended mode of operation of the modern Haskell tooling, that is cabal and stack, is to always work inside of a project. +Often, "project" basically means "package", but you can have projects with multiple packages in them (using a cabal.project file, see the docs).

+

Creating a package is easily done using cabal init --simple inside a fresh directory. +If you like to be asked more questions, you can also opt for cabal init instead. +Then, you can declaratively add dependencies in the build-depends field of your executable/library in the package-name.cabal file that was generated. +Put a comma (,) between the package names in the build-depends field.

+

Note that if you selected "Library" and "yes" for generating a test suite (the default option), there will be two build-depends blocks in your package-name.cabal file, one for each component. +A package (a single thing in the package repository, should you decide to upload it to Hackage at some point) can contain multiple components: possibly one public library, as well as zero or more executables, test suites, or benchmarks. +(There is also the concept of an "internal library", for which see the documentation, but don't worry about that.)

+

You can start writing code in the app/Main.hs file (or src/MyLib.hs file if you selected Library) and run using cabal run (or build using cabal build in the case of a library). +cabal will automatically ensure that a consistent set of versions is compiled and made available, if at all possible. +You can add version bounds to your dependencies if you want to apply some proper software engineering principles.

+

(If you want to use stack instead of cabal, try their getting started guide.)

+

An even lighter-weight alternative

+

An alternative to creating a project is to make a cabal script: this allows you to effectively make a self-contained project inside a single Haskell file. +You specify the dependencies in a special comment block at the top of the file. +See the documentation for more details.

diff --git a/haskell/cabal-lib.md b/haskell/cabal-lib.md new file mode 100644 index 0000000..acb2bf1 --- /dev/null +++ b/haskell/cabal-lib.md @@ -0,0 +1,194 @@ +## About `cabal install --lib` + +**TL;DR: Don't use it, add the library to your *package-name*.cabal or `package.yaml` instead, or use a [cabal script](https://cabal.readthedocs.io/en/3.10/getting-started.html#run-a-single-file-haskell-script). After you learn more about the downsides, you can reconsider. See the "What to do instead" section below.** + +--- + +Suppose you are new to Haskell, or at least new to the current (2023) Haskell tooling, and would like to install a program written in Haskell. +For example, say you would like to install a Haskell formatter, say `fourmolu`, and find that installing Haskell packages uses a tool called `cabal`. +Hopeful, you try: + +```sh +cabal install fourmolu +``` + +and, if you are patient, this may well succeed and give you a `fourmolu` executable. + +So now you want to write some Haskell! +But you want to use a library, say `brick`, for making a terminal user interface (TUI). +So you go: + +```sh +cabal install brick +``` + +which seems to proceed as before, compiling a bunch of dependencies. +(Note that in the past, this _was_ a common way to install Haskell libraries for use in your own code, and quite a number of READMEs of older libraries still recommend this command.) +But at the end it prints this warning: (as of cabal-install 3.10.1.0) + +``` +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@ WARNING: Installation might not be completed as desired! @ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +The command "cabal install [TARGETS]" doesn't expose libraries. +* You might have wanted to add them as dependencies to your package. In this +case add "brick" to the build-depends field(s) of your package's .cabal file. +* You might have wanted to add them to a GHC environment. In this case use +"cabal install --lib brick". The "--lib" flag is provisional: see +https://github.com/haskell/cabal/issues/6481 for more information. +``` + +which looks scary, using the same kind of `@@@@` banner as `ssh` reporting a possible man-in-the-middle attack (a changed host key, really). +But you want to use this library, after all, and you're just working in a single `.hs` file and aren't planning on creating a "package". +So you try the second suggestion: + +```sh +cabal install --lib brick # note, don't try this at home +``` + +and that seems to work -- and if you had let the previous `cabal install brick` command run to completion, it doesn't even seem to do much. +That much is true: it hasn't done much, but what it _has_ done is probably not what you wanted. + +For example, let's try to sanity-check our Haskell installation and start a REPL: + +``` +$ ghci +Loaded package environment from /home/tom/.ghc/x86_64-linux-9.4.7/environments/default +GHCi, version 9.4.7: https://www.haskell.org/ghc/ :? for help +ghci> 1 + 2 + +:1:3: error: + Variable not in scope: (+) :: t0 -> t1 -> t +ghci> print "hi" + +:3:1: error: + Variable not in scope: print :: base-4.17.2.0:GHC.Base.String -> t +ghci> +``` + +I mean, that doesn't look good, doesn't it? + +And if that did not scare you enough, suppose that in the future, you want to use a newer version of `brick` and try to install that using `cabal install --lib brick` again. +What you'll see is this: + +``` +$ cabal install --lib brick-1.9 +Error: cabal: Packages requested to install already exist in environment file +at /home/tom/.ghc/x86_64-linux-9.4.7/environments/default. Overwriting them +may break other packages. Use --force-reinstalls to proceed anyway. Packages: +brick +``` + +(I simulated the situation by installing an older version instead. I can't time-travel, unfortunately.) + +Or suppose that you now also want to use another library, say `eigen`: + +``` +$ cabal install --lib eigen +Resolving dependencies... +Error: cabal: Could not resolve dependencies: +[__0] next goal: brick (user goal) +[__0] rejecting: +brick-1.10/installed-1f76dfaf75736c0f6e2a4a2cf992bb12da05f6bbc7985f9787547739947e4696 +(package is broken, missing dependency +bimap-0.5.0-2ee7565ca29f0edc78a3c8fe09c9eef36c96d08473660eec8a944cef16ac4d86) +[__0] trying: brick-1.10 +[__1] trying: vty-5.39 (dependency of brick) +... +... +``` + +The error message is much longer than this, but I cut it off to save some space. +Apparently it claims our installation of `brick` is actually broken, despite it working okay in `ghci`, but in any case this didn't work. + + +## What happened? + +Note the line printed by `ghci`: + +``` +Loaded package environment from /home/tom/.ghc/x86_64-linux-9.4.7/environments/default +``` + +This file is the "GHC environment" from above that `cabal` wrote to. +It now contains this: + +``` +clear-package-db +global-package-db +package-db /home/tom/.cabal/store/ghc-9.4.7/package.db +package-id brick-1.10-1f76dfaf75736c0f6e2a4a2cf992bb12da05f6bbc7985f9787547739947e4696 +``` + +This means that when starting `ghci`, these, and no others, are the packages that are in scope: + +``` +$ ghci +Loaded package environment from /home/tom/.ghc/x86_64-linux-9.4.7/environments/default +GHCi, version 9.4.7: https://www.haskell.org/ghc/ :? for help +ghci> :show packages +active package flags: + -package-id brick-1.10-1f76dfaf75736c0f6e2a4a2cf992bb12da05f6bbc7985f9787547739947e4696 +``` + +_This does not include `base`;_ this is what produced the broken `ghci` above (which couldn't find `(+)` nor `print`). + +You can "fix" this: + +``` +ghci> :set -package base +package flags have changed, resetting and loading new packages... +ghci> 1 + 2 +3 +ghci> :show packages +active package flags: + -package base + -package-id brick-1.10-1f76dfaf75736c0f6e2a4a2cf992bb12da05f6bbc7985f9787547739947e4696 +``` + +and you can even make that change permanent with `cabal install --lib base`, which adds (in my case) a line `package-id base-4.17.2.0` to the aforementioned `default` file. + +In short, a _GHC environment file_ was created by `cabal` that contains a list of packages in scope for `ghc` when you're accessing `GHC` through `cabal` in the context of a project. +You need to either manage this file manually or through some [helper tool](https://github.com/phadej/cabal-extras), and you will need to, because `cabal` won't resolve conflicts for you. +You're back to manual, imperative management of dependencies. + +Furthermore, this way of installing dependencies is fundamentally separate from the code that _uses_ those dependencies, and is just one such list. +So if you have multiple scripts that you'd like to work globally, outside of a project, their dependency sets had better be compatible. + + +## How to fix the situation + +If you got yourself in a pickle due to an unintended use of `cabal install --lib`, you can undo its effects (apart from having used some disk space in compiling the packages in question) by removing the `default` file mentioned above. +This is in ~/.ghc/*architecture*-*OS*-*ghcversion*/environments/default. + +As mentioned, the compiled packages are still around (in ~/.cabal/store/ghc-*version*/), but removing those is tricky -- do not try it, cabal likes to maintain its own consistent set of packages in the "store". +Removing the entire store folder for a particular GHC version is safe, however -- even though this does of course mean that you may need to recompile a lot of things later. :) + + +## What to do instead + +Create a project! +The intended mode of operation of the modern Haskell tooling, that is `cabal` and `stack`, is to always work inside of a _project_. +Often, "project" basically means "package", but you can have projects with multiple packages in them (using a `cabal.project` file, see [the docs](https://cabal.readthedocs.io/en/3.10/cabal-project.html)). + +Creating a package is easily done using `cabal init --simple` inside a fresh directory. +If you like to be asked more questions, you can also opt for `cabal init` instead. +Then, you can **declaratively** add dependencies in the `build-depends` field of your executable/library in the *package-name*.cabal file that was generated. +Put a comma (`,`) between the package names in the `build-depends` field. + +Note that if you selected "Library" and "yes" for generating a test suite (the default option), there will be _two_ `build-depends` blocks in your *package-name*.cabal file, one for each _component_. +A package (a single thing in the package repository, should you decide to upload it to Hackage at some point) can contain multiple components: possibly one public library, as well as zero or more executables, test suites, or benchmarks. +(There is also the concept of an "internal library", for which see [the documentation](https://cabal.readthedocs.io/en/3.10/cabal-package.html#sublibs), but don't worry about that.) + +You can start writing code in the `app/Main.hs` file (or `src/MyLib.hs` file if you selected Library) and run using `cabal run` (or build using `cabal build` in the case of a library). +`cabal` will automatically ensure that a consistent set of versions is compiled and made available, if at all possible. +You can add [version bounds](https://cabal.readthedocs.io/en/3.10/cabal-package.html#pkg-field-build-depends) to your dependencies if you want to apply some proper software engineering principles. + +(If you want to use `stack` instead of `cabal`, try [their getting started guide](https://docs.haskellstack.org/en/stable/GUIDE/).) + + +### An even lighter-weight alternative + +An alternative to creating a project is to make a _cabal script_: this allows you to effectively make a self-contained project inside a single Haskell file. +You specify the dependencies in a special comment block at the top of the file. +See [the documentation](https://cabal.readthedocs.io/en/3.10/getting-started.html#run-a-single-file-haskell-script) for more details. -- cgit v1.2.3-54-g00ecf