summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Smeding <tom@tomsmeding.com>2023-10-22 22:36:12 +0200
committerTom Smeding <tom@tomsmeding.com>2023-10-22 22:36:12 +0200
commit1191a5d064617004251a1de04cccba328234f57a (patch)
tree91a167367d74857c94ed32244d51f2e87705b45d
parentd19cc60f10a55af6a2e77a673ff70248ec7af149 (diff)
Add haskell/cabal-lib post
-rw-r--r--haskell/cabal-lib.html134
-rw-r--r--haskell/cabal-lib.md194
2 files changed, 328 insertions, 0 deletions
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 @@
+<h2>About <code>cabal install --lib</code></h2>
+<p><strong>TL;DR: Don't use it, add the library to your <code><em>package-name</em>.cabal</code> or <code>package.yaml</code> instead, or use a <a href="https://cabal.readthedocs.io/en/3.10/getting-started.html#run-a-single-file-haskell-script">cabal script</a>. After you learn more about the downsides, you can reconsider. See the &quot;What to do instead&quot; section below.</strong></p>
+<hr />
+<p>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 <code>fourmolu</code>, and find that installing Haskell packages uses a tool called <code>cabal</code>.
+Hopeful, you try:</p>
+<pre><code class="language-sh">cabal install fourmolu
+</code></pre>
+<p>and, if you are patient, this may well succeed and give you a <code>fourmolu</code> executable.</p>
+<p>So now you want to write some Haskell!
+But you want to use a library, say <code>brick</code>, for making a terminal user interface (TUI).
+So you go:</p>
+<pre><code class="language-sh">cabal install brick
+</code></pre>
+<p>which seems to proceed as before, compiling a bunch of dependencies.
+(Note that in the past, this <em>was</em> 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)</p>
+<pre><code>@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+@ WARNING: Installation might not be completed as desired! @
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+The command &quot;cabal install [TARGETS]&quot; doesn't expose libraries.
+* You might have wanted to add them as dependencies to your package. In this
+case add &quot;brick&quot; 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
+&quot;cabal install --lib brick&quot;. The &quot;--lib&quot; flag is provisional: see
+https://github.com/haskell/cabal/issues/6481 for more information.
+</code></pre>
+<p>which looks scary, using the same kind of <code>@@@@</code> banner as <code>ssh</code> 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 <code>.hs</code> file and aren't planning on creating a &quot;package&quot;.
+So you try the second suggestion:</p>
+<pre><code class="language-sh">cabal install --lib brick # note, don't try this at home
+</code></pre>
+<p>and that seems to work -- and if you had let the previous <code>cabal install brick</code> command run to completion, it doesn't even seem to do much.
+That much is true: it hasn't done much, but what it <em>has</em> done is probably not what you wanted.</p>
+<p>For example, let's try to sanity-check our Haskell installation and start a REPL:</p>
+<pre><code>$ 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&gt; 1 + 2
+
+&lt;interactive&gt;:1:3: error:
+ Variable not in scope: (+) :: t0 -&gt; t1 -&gt; t
+ghci&gt; print &quot;hi&quot;
+
+&lt;interactive&gt;:3:1: error:
+ Variable not in scope: print :: base-4.17.2.0:GHC.Base.String -&gt; t
+ghci&gt;
+</code></pre>
+<p>I mean, that doesn't look good, doesn't it?</p>
+<p>And if that did not scare you enough, suppose that in the future, you want to use a newer version of <code>brick</code> and try to install that using <code>cabal install --lib brick</code> again.
+What you'll see is this:</p>
+<pre><code>$ 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
+</code></pre>
+<p>(I simulated the situation by installing an older version instead. I can't time-travel, unfortunately.)</p>
+<p>Or suppose that you now also want to use another library, say <code>eigen</code>:</p>
+<pre><code>$ 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)
+...
+...
+</code></pre>
+<p>The error message is much longer than this, but I cut it off to save some space.
+Apparently it claims our installation of <code>brick</code> is actually broken, despite it working okay in <code>ghci</code>, but in any case this didn't work.</p>
+<h2>What happened?</h2>
+<p>Note the line printed by <code>ghci</code>:</p>
+<pre><code>Loaded package environment from /home/tom/.ghc/x86_64-linux-9.4.7/environments/default
+</code></pre>
+<p>This file is the &quot;GHC environment&quot; from above that <code>cabal</code> wrote to.
+It now contains this:</p>
+<pre><code>clear-package-db
+global-package-db
+package-db /home/tom/.cabal/store/ghc-9.4.7/package.db
+package-id brick-1.10-1f76dfaf75736c0f6e2a4a2cf992bb12da05f6bbc7985f9787547739947e4696
+</code></pre>
+<p>This means that when starting <code>ghci</code>, these, and no others, are the packages that are in scope:</p>
+<pre><code>$ 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&gt; :show packages
+active package flags:
+ -package-id brick-1.10-1f76dfaf75736c0f6e2a4a2cf992bb12da05f6bbc7985f9787547739947e4696
+</code></pre>
+<p><em>This does not include <code>base</code>;</em> this is what produced the broken <code>ghci</code> above (which couldn't find <code>(+)</code> nor <code>print</code>).</p>
+<p>You can &quot;fix&quot; this:</p>
+<pre><code>ghci&gt; :set -package base
+package flags have changed, resetting and loading new packages...
+ghci&gt; 1 + 2
+3
+ghci&gt; :show packages
+active package flags:
+ -package base
+ -package-id brick-1.10-1f76dfaf75736c0f6e2a4a2cf992bb12da05f6bbc7985f9787547739947e4696
+</code></pre>
+<p>and you can even make that change permanent with <code>cabal install --lib base</code>, which adds (in my case) a line <code>package-id base-4.17.2.0</code> to the aforementioned <code>default</code> file.</p>
+<p>In short, a <em>GHC environment file</em> was created by <code>cabal</code> that contains a list of packages in scope for <code>ghc</code> when you're accessing <code>GHC</code> through <code>cabal</code> in the context of a project.
+You need to either manage this file manually or through some <a href="https://github.com/phadej/cabal-extras">helper tool</a>, and you will need to, because <code>cabal</code> won't resolve conflicts for you.
+You're back to manual, imperative management of dependencies.</p>
+<p>Furthermore, this way of installing dependencies is fundamentally separate from the code that <em>uses</em> 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.</p>
+<h2>How to fix the situation</h2>
+<p>If you got yourself in a pickle due to an unintended use of <code>cabal install --lib</code>, you can undo its effects (apart from having used some disk space in compiling the packages in question) by removing the <code>default</code> file mentioned above.
+This is in <code>~/.ghc/<em>architecture</em>-<em>OS</em>-<em>ghcversion</em>/environments/default</code>.</p>
+<p>As mentioned, the compiled packages are still around (in <code>~/.cabal/store/ghc-<em>version</em>/</code>), but removing those is tricky -- do not try it, cabal likes to maintain its own consistent set of packages in the &quot;store&quot;.
+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. :)</p>
+<h2>What to do instead</h2>
+<p>Create a project!
+The intended mode of operation of the modern Haskell tooling, that is <code>cabal</code> and <code>stack</code>, is to always work inside of a <em>project</em>.
+Often, &quot;project&quot; basically means &quot;package&quot;, but you can have projects with multiple packages in them (using a <code>cabal.project</code> file, see <a href="https://cabal.readthedocs.io/en/3.10/cabal-project.html">the docs</a>).</p>
+<p>Creating a package is easily done using <code>cabal init --simple</code> inside a fresh directory.
+If you like to be asked more questions, you can also opt for <code>cabal init</code> instead.
+Then, you can <strong>declaratively</strong> add dependencies in the <code>build-depends</code> field of your executable/library in the <code><em>package-name</em>.cabal</code> file that was generated.
+Put a comma (<code>,</code>) between the package names in the <code>build-depends</code> field.</p>
+<p>Note that if you selected &quot;Library&quot; and &quot;yes&quot; for generating a test suite (the default option), there will be <em>two</em> <code>build-depends</code> blocks in your <code><em>package-name</em>.cabal</code> file, one for each <em>component</em>.
+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 &quot;internal library&quot;, for which see <a href="https://cabal.readthedocs.io/en/3.10/cabal-package.html#sublibs">the documentation</a>, but don't worry about that.)</p>
+<p>You can start writing code in the <code>app/Main.hs</code> file (or <code>src/MyLib.hs</code> file if you selected Library) and run using <code>cabal run</code> (or build using <code>cabal build</code> in the case of a library).
+<code>cabal</code> will automatically ensure that a consistent set of versions is compiled and made available, if at all possible.
+You can add <a href="https://cabal.readthedocs.io/en/3.10/cabal-package.html#pkg-field-build-depends">version bounds</a> to your dependencies if you want to apply some proper software engineering principles.</p>
+<p>(If you want to use <code>stack</code> instead of <code>cabal</code>, try <a href="https://docs.haskellstack.org/en/stable/GUIDE/">their getting started guide</a>.)</p>
+<h3>An even lighter-weight alternative</h3>
+<p>An alternative to creating a project is to make a <em>cabal script</em>: 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 <a href="https://cabal.readthedocs.io/en/3.10/getting-started.html#run-a-single-file-haskell-script">the documentation</a> for more details.</p>
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 <code>*package-name*.cabal</code> 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
+
+<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](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 <code>~/.ghc/*architecture*-*OS*-*ghcversion*/environments/default</code>.
+
+As mentioned, the compiled packages are still around (in <code>~/.cabal/store/ghc-*version*/</code>), 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 <code>*package-name*.cabal</code> 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 <code>*package-name*.cabal</code> 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.