diff options
author | Tom Smeding <tom@tomsmeding.com> | 2023-10-22 22:36:12 +0200 |
---|---|---|
committer | Tom Smeding <tom@tomsmeding.com> | 2023-10-22 22:36:12 +0200 |
commit | 1191a5d064617004251a1de04cccba328234f57a (patch) | |
tree | 91a167367d74857c94ed32244d51f2e87705b45d /haskell | |
parent | d19cc60f10a55af6a2e77a673ff70248ec7af149 (diff) |
Add haskell/cabal-lib post
Diffstat (limited to 'haskell')
-rw-r--r-- | haskell/cabal-lib.html | 134 | ||||
-rw-r--r-- | haskell/cabal-lib.md | 194 |
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 "What to do instead" 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 "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. +</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 "package". +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> 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> +</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 "GHC environment" 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> :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 "fix" this:</p> +<pre><code>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 +</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 "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. :)</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, "project" basically means "package", 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 "Library" and "yes" 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 "internal library", 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. |